自己最近的項目是基于react的,于是讀了一遍react的文檔,做了一些記錄(除了REFERENCE部分還沒開始讀)
文章也可以直接訪問我的前端網站來查看
quick start
Tutorial
建立一個模塊
我們可以使用React.createClass()來創建一個React模塊。我們通過一個JS對象傳了一些方法給這個函數,最重要的方法是render,返回了最終會變成HTML的部分。
render里面的div不是真正的DOM節點,他們是React的div components的實例化,不直接產生HTML,所以XSS保護是默認的。
我們使用ReactDom.render()來實例化入口框架,這個方法必須在最下面模塊定義好了才執行,這個方法來真正的產生DOM,其他的平臺的話有其他的生成方式,比如React Native。
HTML tags在生成的時候會調用React.createElement(tagName)來創建。
this.props
由父模塊傳遞給子模塊的數據,我們可以在子模塊中通過this.props來訪問得到。
dangerouslySetInnerHTML
如果我們想要直接插入html進入文檔中(這一點需要自己來保證XSS的問題),我們可以使用dangerouslySetInnerHTML這個屬性來添加。
遍歷輸出
var commentListNodes = this.props.comList.map(function(comment){
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
我們可以通過map方法來得到數組,然后使用的時候直接{commentListNodes}就能輸出了。
state
目前,依賴于props,所有的組件都是被刷新了一次,props是不變的,這個被父組件傳過來的且被父組件擁有。所以出現了state這個屬性,我們使用這個可變的私有的值來改變狀態。我們可以使用setState()來rerender這個組件。getInitialState這個方法會在初始化的時候執行一次來初始化組件的初始狀態。
componentDidMount
這個方法會在組件第一次初始化之后被調用
控制組件的輸入
這里推薦的也是在this.state里面存這個value,在input的onChange時調用修改this.setState來改變。包括submit方法的話就是監聽onSubmit。我們可以先調用e.preventDefault()來阻止默認行為。
從子組件傳數據給父組件
我們可以在調用子組件的時候,將callback綁在props上,然后調用子組件的props上面的方法來調用父組件相應的方法。
我們在寫一些回調的時候要綁定好this
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this)
});
//就像這樣綁定好this
Tip
這里還給了一個小建議,我們在添加了某一項之后,可以直接加進list,如果ajax失敗了再重置回去
Thinking in React
創建react的步驟
我們分解成react要尊崇單一職責原則,盡量把交互與展示分開,展示也根據種類分細點
然后我們先不考慮交互,不考慮傳輸的數據,先創建一個靜態的項目出來
react是單項綁定的
然后我們要計算出最小的state我們需要什么,通過父組件傳的,不變的,能夠通過其他state計算得到的都不應該是state
我們需要得出哪一塊需要數據,如果我們實在沒法區分的時候,我們可以專門在其上用一個組件來專門管理state
社區資源
這個就先不看了
Guides
why react
React就像是MVC的view層,react主要解決的是大項目數據不斷變化的場景。我們只需要修改數據,react會幫我們進行所有的UI更新。
他只更新被改變的部分。
Displaying Data
他的組件是很封閉的,所以更容易重用,更容易測試以及分離相關。
Components更像是方法,接受props和state來生成HTML。(react模塊只能生成一個簡單的根節點,如果想要返回大量的節點的話,得用一個根節點包裹起來)
React允許我們用js對象的形式來創建HTML,如`React.createElement('a',{href:''},'Hello!')。為了簡便,我們還可以使用createFactory的形式來創建。但是使用比較少,還是JSX比較方便。
JSX時間上只是一種語法,并不是使用React的必需品,不用的話,就得像上面一樣使用create的形式慢慢創建。
Jsx in Depth
我們在JSX里面使用HTML tag的時候,用小寫的形式。如果是HTML屬性的話,使用駝峰的寫法。并且因為class和for都作為XML的本身的屬性名,所以我們分別用className和htmlFor來代替。
我們在版本0.11以上可以使用namespaced components,就是在某個組件之下進行申明。
當我們在屬性里想要使用js的表達式的時候,用一個大括號包裹起來,替代雙引號。
boolean attribute,如disable,readonly,checked,required直接省略屬性值和{true}一樣,如果不寫和值為{false}效果一樣。
js表達式也可以用來申明生成哪個子節點,比如{ifShow ? <Nav/> : <Login/>}。
注釋的話使用這種形式是可以的{/* */}。
Jsx 延展屬性
我們可以使用延展屬性,來講一個對象所有的屬性復制到另一個對象上。我們可以多次使用,或者和普通的屬性一起用,不過得注意順序,后面的會覆蓋前面的。這里是React直接支持。
var props = {foo:"check"}
var component = <Component {...props} foo='name'/>
console.log(component.props.foo);
//name
延展屬性在ES6的效果主要是將數組類型的一個個推出來,就像是apply方法一樣,支持情況當然不好啦,但是Bebal可以轉。
JSX 一些陷阱
如果想插入·這種符號的話,我們可以直接插入,但是如果想插入動態的話,我們就會以這樣的形式包裝起來{'·'},然后就沒法顯示了
我們可以直接以UTF-8的形式寫個點,或者我們可以寫unicode的編碼\u00b7,或者我們用個span包起來,例如{['First ', <span>·</span>, ' Second']}
,或者我們可以直接dangerouslySetInnerHTML={{__html: 'First · Second'}}這樣來強行插入原始的HTML。
原生的HTML的自定義屬性必須以data開頭,自定義的節點的屬性可以是任意的,aria-hidden這種aria-開頭的屬性是可以正常render的。
交互以及動態UI
他自動把方法綁定在模塊上,是使用的事件代理,完全綁在了根元素上。
模塊就是狀態機。
通過setState來將data merge進入this.state中,這里的merge只有一層,如果想深層次的merge,使用那個immutability helpers
所以我們應該有許多stateless的模塊來負責渲染,然后在其上有一個stateful的模塊來將state通過props傳給這個模塊。
計算過的數據,react模塊以及與props里面重復的數據都不應該在State里面出現。
Multiple Components
動機主要是將關注點分離。
組件自己沒法修改props,這樣可以保證組件是始終如一的。Owner負責修改以及傳遞狀態。
注意Owner和Parent是不一樣的,<Parent><Child/></Parent>
這個是parent,而owner是React.createClass。我們可以在Parent里面使用this.props.children來操作。
我們最好不要用hide來隱藏,我們應該直接讓他們消失,對于list生成的我們需要給每一個一個唯一的key來保證他們正常且不別破壞。
數據流是單向綁定的。
JS執行的速度是非常快的,所以基本上沒有性能瓶頸。主要的瓶頸是DOM的渲染,而這點React幫我們通過批處理以及臟檢測來優化過了。
當我們真的感覺到性能問題的時候,我們可以重寫shouldComponentUpdate來讓他返回false就可以了,但是其實不是很需要。
Reusable Components
設計接口的時候,將那些簡單設計的元素分解為可重復使用的,良好設計的接口。下次就可以復用了。
為了保證我們的組件被正確的使用,我們可以通過設置propTypes來限制用戶傳入的數據,但是這個東西只有在development模式
才會有效。
default prop values
我們可以在用戶沒有傳的時候設置一個默認值,調用getDefaultProps就可以設置默認值了,如果用戶設置過的話,就會被忽略
Transferring Props: A Shortcut
有的時候我們想要從父元素傳遞props給子元素,我們可以直接使用spread syntax來簡寫,比如直接{...this.props}這樣。
Mixins
當不同的組件擁有相同的功能的時候,我們可以使用mixins,這個就是將組件的功能進行抽離的一個方法,還蠻有意思的。使用的時候加個mixins: [SetIntervalMixin]
就可以了。
一個很重要的有點在于,如果有多個Mixin在同一個生命周期的方法執行,那么他們都會被執行,并且會嚴格按照申明的順序執行。
ES6 Classes
我們也可以使用es6的class語法來聲明組件,唯一不同的是沒有getInitialState這個方法,我們只能在constructor里面手動初始化state。
而且方法如果想要在render里面以this來調用的話,必須在constructor里面bind一下this。
還有propTypes和defaultProps得在外面申明,而不是寫在里面。下面的例子
export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
this.tick = this.tick.bind(this);
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick}>
Clicks: {this.state.count}
</div>
);
}
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };
關于this的綁定,我們可以在調用的時候綁定,也可以使用箭頭,不過最好像上面一樣在constructor里面綁定,這樣只綁定了一次。
<div onClick={this.tick.bind(this)}>//bad
<div onClick={()=> this.tick()}>//bad
ES6的語法的話沒有Mixins的支持。
Stateless Functions
如果組件只是一個簡單的js function的話,我們可以使用這種語法
function HelloMessage(props) {
return <div>Hello {props.name}</div>;
}
//或者直接使用下面的箭頭語法
const HelloMessage = (props) => <div>Hello {props.name}</div>;
這種比較適合沒有lifestyle方法的,不存有內部狀態,我們仍然可以設置propTypes和defaultProps。就像ES6的設置一樣。我們的項目應該較多的是stateless的模塊。
Transferring Props
我們想要傳遞給子模塊的時候加上某個屬性的話,可以直接使用spread語法。<Component {...this.props} more="values" />
如果沒有用jsx的話,我們可以使用ES6的語法Object.assign和underscore的extends。
如果我們在某一層組件的時候截斷某個屬性,然后將其他屬性傳下去的話,可以使用other的語法。
function FancyCheckbox(props) {
var { checked, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` contains { onClick: console.log } but not the checked property
return (
<div {...other} className={fancyClass} />
);
}
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
這樣子的話,other就會只包含除了checked以外的屬性了,主要是因為checked
這個屬性在html的結構有特殊的意義,而在自定義的組件沒有這個效果。
我們也可以使用rest properties,var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
,不過在webpack里面得加上transform-object-rest-spread這個plugin。
不用jsx的話,我們可以使用underscore的omit來刪屬性和extends來擴展。
Forms
與HTML對應的,value是被input和textarea支持的。
checked是被input的checkbox和radio支持的。
selected是被options支持的。
注意HTML中,textarea的值是被設置在兩個標簽之間,REACT是在value屬性上。
onChange事件會在input和textarea的值發生改變的時候觸發,還有input的checked改變,以及option的selected改變的時候。
如果我們在render方法里面寫死了input的value,那么我們輸入會被忽視的,只有value改為state的可變的值才行。
我們可以使用defaultValue給input,textarea,select(這個支持multiple),然后radio和checkbox的值是defaultChecked,注意這個值只能夠在初始化的時候。
Working with the browser
react快的原因是因為他不直接與DOM交流,render方法返回的對DOM的描述,react會計算最快的更新頁面的方法。
事件系統完美處理。
當我們想要調用html本身的命令的時候或者接觸真實的DOM樹,我們可以使用refs來控制。
Component Lifecycle
模塊有3個主要的階段:
- Mounting:組件準備插入DOM中
- Updating:組件更新
- Unmounting:組件從DOM中刪除
Mounting提供了getInitialState()和componentWillMount()和componentDidMount()。
Updating提供了componentWillReceiveProps(這個方法的價值在于組件接收到新的props的時候,我們可以比較新老的props,然后setState。這個方法在初次mount的時候并不會被觸發,因為這個時候沒有老的props),shouldComponentUpdate,componentWillUpdate(組件即將執行更新之前,我們無法執行setState方法),componentDidUpdate(更新發生之后會立即觸發)。
Unmounting提供了componentWillUnmount()在組件被移除之前觸發。
Mounted好了的復合組件也提供了component.forceUpdate()來強行重新刷一次。
react支持IE9以及以上,但是我們可以引入es5-shim和es5-sham來讓老版的支持,這其實取決于我們自己。
Refs to Components
構建組件完了,你可能想要在render的component實例上調用方法。大多數情況下應該是用不到的,因為正常的數據流應該是父組件傳props給子組件的。
jsx并不返回一個component的實例,他只是返回一個ReactElement(這只是一個告訴React這個組件應該是什么樣子的輕量的)。
我們想要調用某個組價實例的方法,只能在最上層的component使用(就是ReactDom.render生成的東西)。在組件的內部,我們應該自己處理他們之間的狀態,或者使用另一種方法來得到ref(字符串屬性或者回調方法屬性)。
The ref Callback Attribute
ref屬性我們可以直接寫成一個回調方法。這個方法會在組件結束mount之后立即被觸發,參數是引用的組件。我們可以直接使用這個組件或著把他存了等到以后使用。
這個方法會在componentDidMount之前觸發。
render: function() {
return (
<TextInput
ref={function(input) {
if (input != null) {
input.focus();
}
}} />
);
},
render: function() {
return <TextInput ref={(c) => this._input = c} />;
},
componentDidMount: function() {
this._input.focus();
},
當我們將refs添加給div的時候,我們得到的是DOM元素,如果給自定義的組件綁定,我們得到的是react的實例。如果是我們自定義的組件,我們可以調用任何在他的class里面定義的方法。
當組件unmounted或者ref改變的時候,老的ref都會以null來被調用,所以說當ref update的時候,在被組件實例為參數之前,會立即調用一次null為參數的。(這點需要注意的)
The ref String Attribute
我們也可以簡單的加一個string的ref屬性,然后我們在其他的事件處理里面就可以this.refs.xx來調用了。
Tooling Integration
作者希望react成為環境無關的,推薦了一些工具來讓我們更好地使用各種種類的語言。
Language Tooling
我們寫成JSX的文件的話,我們要用babel先轉化為純粹的react的語法。Flow和TypeScrip也都支持JSX了。
Package Management
我們可以在commonjs系統browserify或者webpack里面直接npm的形式來引入react和react-dom。
Server-side Environments
react并不是真的依賴于DOM,所以可以后端來執行,將HTML吐在頁面上,如果是nodejs的話,是可以ReactDOMServer.rendertoString的。
如果是java的話,可以依賴于Nashorn這個JS的執行器來轉化JSX。
Add-ons
這是一些react提供的功能插件,這些相對于核心來說變化的會比較多一些。以下的是一些實驗性質的:
- TransitionGroup and CSSTransitionGroup:解決那些不容易實現的動畫,例如在組件移除的時候的動畫。
- LinkedStateMixin:這個是將form的屬性與state綁在一起的插件,如果form比較大的話,這個還是很關鍵的。
- ...還有很多下面會慢慢介紹的
Animation
react提供了ReactTransitionGroup這種比較級別比較低的api來讓我們使用,還提供了ReactCSSTransitionGroup來讓我們更好的使用css實現的動畫。包括進入和離開頁面的動畫。
當我們在list添加的時候,我們可以使用ReactCSSTransitionGroup的enter和leave來實現。他會根據key的區別來判斷是不是新添加的,然后就會像我們通常觸發動畫一樣來toggle css的class。
組件初始化渲染的話,我們可以使用transitionAppear這個來添加動畫。注意初始化渲染的時候,所有的children是appear,然后后來添加進的就是enter了。
使用ReactCSSTransitionGroup我們得不到動畫結束的通知,也無法為了動畫加上更復雜的邏輯,想定制化,就得使用ReactTransitionGroup了。
如果想禁掉某些動畫,我們可以設置為false。
ReactTransitionGroup
這個玩意功能強大的多,他提供了在動畫生命周期里能夠執行的方法。使用到的時候再思考吧。
Two-Way Binding Helpers
ReactLink是個方便的在react里面實現雙向綁定的工具。但是這個在新版本被廢棄了,還是推薦通過onchange來設置值。
雙向綁定實際上強制性的要求了DOM完全等于react的state,這點雖然其實有很廣的范圍,React提供了ReactLink來幫我們簡單的封裝了setState和onChange方法。他并沒有實際上改變react的單項數據流。最好別用~
Test Utilities
React提供了非常棒的測試語法。配合Jest這層依賴于Jsdom的,我們可以寫腳本測試整個的渲染以及事件邏輯。
我們可以直接寫腳本模擬點擊,模擬輸入,模擬鍵盤事件。
類似于ReactTestUtils.Simulate.click(node);
我們可以renderIntoDocument然后進行各種類型判斷以及事件觸發檢驗。
不依賴于Jest,不依賴于DOM,我們也可以render組件,使用如下的Shallow rendering。
Shallow rendering
使用這個組件我們可以脫離DOM來渲染組件,但是這只是一層渲染,子組件不會被渲染。我們只能夠檢查output的信息。功能其實還是很少的。refs也不支持,function也不支持。
Cloning ReactElements
cloneWithProps這個組件被廢棄了,現在只建議使用React.cloneElement。就是在想要復制一個element。并且在他的原props上進行一些修改。
Keyed Fragments
有時我們需要將兩塊元素換位置,按照我們一般的寫法,我們會單純的給他們換位置,于是這些元素就會經歷unmount和remount兩個步驟。這是因為我們沒有給他們每個模塊一個單獨的key。我們如果使用createFragment就可以讓元素不執行unmount了。
Immutability Helpers
(這個add-on的好處在于我們可以改變外面的殼子為一個新對象,然后對象里面的屬性會自動重用老的。等于就是一個淺復制,然后我們就可以在子組件里面使用shandowCompare來比較。)
我們有時想要改變對象的里面的某個屬性,然后其他的不想改變。例如下圖。
var old = {a:1,b:{c:1,d:{e:12}},r:{f:1}};
var newData = Update(old,{b:{c:{$set:2}}});
console.log(old === newData);//false
console.log(old.b === newData.b);//false
console.log(old.b.d === newData.b.d);//true
console.log(old.r === newData.r);//true
PureRenderMixin
就是如果你的組件是pure的,就是說給不變的props和state,render同樣的結果。可以直接mixins這個插件。其實就是shouldComponentUpdate里面返回了一個shandowCompare而已。
Performance Tools
(這個是個非常好的提供性能的工具,可以讓我們查看一定的操作之后,我們頁面組件重新渲染的次數,可以讓我們進行組件的優化,使用可以參照本項目DEMO里的那個debug-panel,這個是勐喆開發的一個查看工具,內部調用了start,stop,printWasted,getLastMeasurement等方法)
這里有個Perf.printWasted,這個是react內部做的深層次比對,發現沒有變化,于是DOM沒有觸及,這一塊的浪費我們可以在shouldComponentUpdate里面通過return false來進行阻止。
Shandow Compare
這個是個最淺層的比較,會對對象的每個屬性進行嚴格等于的比較,然后都相等就返回false,有改變的話就返回true。代表著需要更新。
Advanced Performance
人們使用react的原因在于他們希望網站是快速的,并且是響應的。每次state的改變導致重新render整個子樹讓人們想知道這樣是否影響了性能,React使用了一些聰明的技術來減少需要更新UI時的DOM操作。
首先線上環境要使用壓縮過的production build
Avoiding reconciling the DOM
React使用的是虛擬DOM。這種平行的關系阻止了React直接創建和接觸真實的DOM。每次React的props和state改變的話。React都會生成一個新的虛擬DOM來和老的比較,如果不相等的話,React才會盡可能小的改變虛擬DOM。
在這之上,React提供了一個組件的生命周期的方法,shouldComponentUpdate,這個方法來阻止虛擬DOM比較以及可能的最終的DOM的更改。讓開發者來縮短整個過程。這個值默認返回true,默認執行比較以及更新。
我們很多時候的比較其實只是引用地址的比較(shandow compare),這個基本上都是true的,因為我們是在同一個對象上修改的。
我們可以使用Immutable這個東西來創建不同的對象,或者使用Object.assign來做這件事情。
Context
React讓我們很容易的跟蹤數據流的走向,因為他都是沿著組件樹的結構一層層props傳遞下去的。但是有時我們不想要一層層的傳遞下去,我們可以使用Context這個東西。(這個是個實驗性質的屬性,將來可能會修改)
我們在父組件中(context的提供者)申明好childContextTypes和getChildContext。然后我們在子組件里面申明好contextTypes就可以拿到相關的數據了。如果不申明,那this.context就會是一個空對象。
還是建議不要使用這個東西,用了的話,生命周期函數基本都會變化,會新加一個參數nextContext。會讓組件無法被重用。
REFERENCE
這一塊的太多了,先不看好了....
FLUX
這個也先不管....
TIPS
這里主要就是一些細節的點了
Inline Styles
在React里面,我們想要使用行內式樣的話,必須以對象的形式申明,而且必須是駝峰式,行內樣式本來就不推薦,這里也就了解一下感覺就夠了。
If-Else in JSX
JSX里面我們沒法使用if else,因為JSX只是一個來處理函數調用以及對象構建語法糖,最多只能處理3目運算符。如果想要使用if else也可以,只要在JSX外面使用就好了。或者寫成一個自執行的匿名函數調用。
Self-Closing Tag
就是說react component都可以自封閉,包括div什么的,因為他們本身也就是react的component。
Maximum Number of JSX Root Nodes
目前,render里面只允許返回一個root nodes。如果我們想要返回多個的話,我們只能用一個將他們包裝起立。
Shorthand for Specifying Pixel Values in style props
就是說在行內的style屬性中,當我們寫一些長度屬性的時候,React會幫我們自動加上px這個單位,這里也介紹了一些不會加的,不過行內的用處不大,這里了解下就好了。
Type of the Children props
我們在componentDidMount中可以通過this.props.children來訪問到組件內部包裹的組件。如果包裹的數量大于1的話,這個值就是一個數組,如果是1的話,這個值就是一個單個的值,并沒有用數組包起來,所以提供了React.Children utilities來訪問。
Value of null for Controlled Input
我們正常給input設置了value之后我們是無法修改他的值的,但是我們把input的value設置為null或者undefined之后,input就變的可以編輯狀態了(但是我這種賦值并沒有價值,這只是一種錯誤的狀態)
componentWillReceiveProps Not Triggered After Mounting
這個方法并不會在初次mount的時候執行,因為他的作用在于比較老的props和新的props,如果老的沒有的話,就不會觸發。
Props in getInitialState Is an Anti-Pattern
我們在getInitialState里面使用props來設置state需要注意一下,因為getInitialState這個方法只會在初始化的時候被執行一次。
DOM Event Listeners in a Component
就是說我們最好在componentDidMount這個方法執行之后進行DOM上事件的綁定,因為這個時候渲染已經完成了。
Load Initial Data via AJAX
讓我們在componentDidMount里面拉取ajax數據,然后在UnMount方法里面abort掉這個request。
False in JSX
false在jsx里面的渲染結果會有些不同,比如false作為id或者value等等的值就會被解析為字符串“false”,如果在div中間使用{false},就會得到一個空白的div。
Communicate Between Components
想要父組件與子組件交流,很簡單的傳輸props就可以了,想要子組件與父組件交流,只需要func.bind(this,i,props)這樣綁定一下就好了。
如果是沒有父子關系的組件之間的交流,我們可以設置自己的時間系統,在componentDidMount里面訂閱,然后在willUnmount里面取消訂閱。
或者按照flux來解決。
Expose Component Functions
將方法暴露給父組件來調用,其實就是父組件創建的時候給個ref值,然后在父組件里面使用this.refs.item1.func()就可以調用子組件的方法了。
this.props.children undefined
children這個屬性并不指的是自己的render方法里面的子,而是調用這個組件里面傳入的子。注意調用我們自己包裝的組件時,在里面包的div并不會渲染,除非我們自己在組件里面的渲染中調用{this.props.children}來手動渲染。
Use React with Other Libraries
我們完全可以不整個的使用react,我們可以在shouldComponentUpdate里面手動return false。我們可以在DidMount里面進行一些事件的綁定。在DidUpdate進行一些處理。但是這是件tricky的事。
Dangerously Set innerHTML
一般React會幫我們編碼一下吐到頁面上,基本不會有XSS攻擊,但是有時我們想要自己生成html吐到頁面上,react提供了dangerouslySetInnerHTML這個function,傳入的數據是{__html:'haha'},注意這個就是有風險的,而且我們基本完全可以避免,除非一些非常特別的case。
接下來會細讀一下react的reference和flux。
文章可以直接訪問我的前端網站來查看
平時的日常整理也會記錄上去。