GUIDS
第一章 為什么使用React?
React
- 一個提供了用戶接口的JavaScript庫。
- 誕生于Facebook和Instagram項目中。
- 許多人把它看做是MVC編程模式。
我們編寫React只為解決一件事:數據需要實時刷新的大型應用程序
簡單
當相關數據發生改變時,React會自動更新所有的UI組件。你可以很簡單地掌控不同狀態下的app
聲明式
當數據改變時,React就好像被點擊了更新按鈕一樣,知道如何更新需要改變的部分
建立復用組件
React都是為了建立可以重復利用的組件。實際上,你用React可以做的唯一一件事就是建立組件。因為代碼都是封裝起來的,組件讓代碼重復利用,方便測試,并且便于單獨考慮每個組件機制。
給它5分鐘
React挑戰了許多傳統的想法,你第一次看到這種想法可能覺得它很瘋狂Give it five minutes。 當閱讀這篇指南時,這些瘋狂的想法已經在FB和Instagram還有其他網站上建立了上千個組件了。
學習更多
link.
第二章 展示數據
你能用UI做的最基本的事情就是顯示數據。React讓你很簡單地展示數據,并且讓用戶界面自動地更新這些數據。
開始吧~
先讓我們看個例子: Hello-react.html
<script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-core@5.8.38/browser.min.js"></script>
<script type="text/babel"> //
**寫在這里 **
</script>
var HelloWorld = React.createClass({
render: function() {
return (
<p>
Hello, <input type="text" placeholder="Your name here" />!
It is {this.props.date.toTimeString()}
</p>
);
}
});
setInterval(function() {
ReactDOM.render(
<HelloWorld date={new Date()} />,
document.getElementById('example')
);
}, 500);
Reactive 更新
hello-react.html
這個頁面,當你在input輸入字符時,react自動更改為時間,即使你現在沒寫任何代碼,React自動給你完成了。它是怎么做到的呢?除非特殊需要,React不會直接操縱Dom本身,它先在內部模擬的DOM身上執行這些變化,為你計算出最有效的改變方式,再應用到實際DOM中。
傳入組件的參數叫做props—— properties的簡稱。他們是通過JSX語法傳入的。你可以把他們當做組件的常量(不可變的量),也就是說,不要更改this.props
Comonets組件就像Functions
React組件非常簡單。你可以把他們看作是簡單的functions,接受參數props和state,再渲染出HTML。這樣想更能幫助你。
一個限制:React組件只能渲染一個單獨的節點(譯者注:就是說所有的東西要包在一個div里,或者別的tag里面)。如果你想返回多個節點,他們必須報在一個單獨的根節點中。
JSX語法
我們強烈認為,組件是分離模塊正確的方式,而不是模版或者邏輯地展示(display logic)。我們認為標記和代碼要密切結合在一起。另外,邏輯地展示經常很復雜,并且用模版語言會讓代碼變得笨重。
我們發現最好的解決方法就是用Javascript直接生成HTML和組件樹,這樣你就用真正的編程語言來建立UI。
為了更簡單的說明,我們增加了一個更簡單的像HTML的語法來創建這些React樹節點。JSX可以讓你通過HTML語法創建JavaScript對象。在React中生成一個鏈接的JavaScript語法是
React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')
使用JSX的話,你就可以直接這樣寫
<a >Hello!</a>
我們發現這讓開發React app開發更簡單,設計師更喜歡這種語法,但是每個人有自己的工作流,所以JSX不是React開發所必須的
JSX很小,學習更多請看 JSX in depth(https://facebook.github.io/react/docs/jsx-in-depth.html)?;蛘卟榭?the Babel REPL.
JSX和HTML很像,但是不完全一樣。查看他們有什么不同 [JSX gotchas] (https://facebook.github.io/react/docs/jsx-gotchas.html)。[Babel揭示了許多如何使用JSX的方法] (http://babeljs.io/docs/setup/), 包括Ruby on Rails的命令行工具。
沒有JSX的React
JSX完全是可選的,你可以不適用它。完全用JavaScript創建React元素你要使用React.createElement, 它接收標簽名或者組件作為參數,還有很多可選的子變量。
var child1 = React.createElement('li', null, 'First Text Content');
var child2 = React.createElement('li', null, 'Second Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
ReactDOM.render(root, document.getElementById('example'));
為了方便,你可以創建簡寫的工廠函數
var Factory = React.createFactory(ComponentClass);
...
var root = Factory({ custom: 'prop' });
ReactDOM.render(root, document.getElementById('example'));
對于常用的HTML標簽,React已經有內置的factories
var root = React.DOM.ul({ className: 'my-list' }, React.DOM.li(null, 'Text Content') );
2.1 JSX in Depth
JSX is 是一個JavaScript語義延伸,看起來像XML。你可以用簡單的JSX語義書寫React
[此處省略原因和比較](https://facebook.github.io/react/docs/jsx-in-depth.html)
組件命名空間
如果你正在建立一個有很多子組件的組件,例如一個表單,你可以會有很多很多的變量聲明:
// Awkward block of variable declarationsvar
Form = MyFormComponent;
var FormRow = Form.Row;
var FormLabel = Form.Label;
var FormInput = Form.Input;
var App = (
<Form>
<FormRow>
<FormLabel />
<FormInput />
</FormRow>
</Form>
);
為了讓它更簡便,命名空間*應運而生,他可以讓你使用組件的時候,可以用其他組件作為屬性。
你還需要聲明:
var MyFormComponent = React.createClass({ ... });
MyFormComponent.Row = React.createClass({ ... });
MyFormComponent.Label = React.createClass({ ... });
MyFormComponent.Input = React.createClass({ ... });
JSX 會在編譯的時候自動handle這些。
JavaScript 表達式
Attribute 表達式
如果要使用JavaScript 表達式作為屬性值,要把他們包在{ } 花括號中,而不是“ ”引號中
// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// Output (JS):
var person = React.createElement(
Person, {name: window.isLoggedIn ? window.name : ''}
);
Boolean 屬性
在JSX中沒有設置屬性的值則默認為true, 設置了屬性后才會視為false。這個問題時常出現在使用HTML中的disabled, required, checked, readOnly屬性的時候。
// 二者相同
<input type="button" disabled />;
<input type="button" disabled={true} />;
// 二者相同
<input type="button" />;
<input type="button" disabled={false} />;
Child Expressions 子表達式
JavaScript表達式也可以用在children上
// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
注釋
給你JSX添加注釋很簡單,他們只是JS的語法。你要包在{ }里面
var content = (
<Nav>
{/* child comment, put {} around */}
<Person /* multi line comment */
name={window.isLoggedIn ? window.name : ''} // end of line comment />
</Nav>
);
2.2 JSX 延展屬性
如果你提前知道所有你想放在組件的所有屬性,就可以方便的使用JSX
var component = <Component foo={x} bar={y} />;
多個Props是不好的
如果你提前不知道你想設置的屬性,你可能想要在之后把他們添加到一個對象
var component = <Component />;
component.props.foo = x; // bad
component.props.bar = y; // also bad
這是一種反模式,因為它意味著我們不能幫你檢查propTypes的正確性,這導致你的propTypes之后會報錯,模糊的堆疊追蹤???(a cryptic stack trace)。Props應該是不變的,在某處改變props對象會造成無法預期的結果。所以此刻把它看成一個凍結的(不可更改的)對象。
延展屬性
現在你可以使用一個新的JSX特性,叫spread attributes
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
你可以傳遞并且拷貝組件的props的屬性,你可以多次使用,結合其他屬性。聲明的順序很重要,后申明的屬性會覆蓋之前的。
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'
奇怪的...是什么?
...是ES6的語法,我們支持這些語法來提供更簡潔的JSX
2. 3 JSX Gotchas 性能和可伸縮性
JSX看起來像HTML但是有些重要的區別你需要知道
DOM的區別
為了實現跨平臺的統一,React完成了獨立于瀏覽器的事件和DOM系統。我們借機清掃了一些DOM未完善的地方。所有的DOM屬性(包括事件處理 event handler)都是駝峰命名,與JavaScript style一致。這里我們故意地打破了規則,因為它是前后矛盾的。然而,data-和aria-屬性 conform to the specs只是用小寫。Style屬性接受一個駝峰法明明的JavaScript對象,而不是一個CSS字符串,這和JavaScript DOM style是一致的,并且能防止XSS安全漏洞。因為class和for都是JavaScript的保留字, JSX元素內置了DOM nodes DOM節點,應該使用className和htmlFor。自定義的元素可以使用class和for(eg.<my-tag class="foo" />。所有的事件對象遵照W3C規則,所有的events(包括submit)bubble correctly per the W3C spec. See Event System for more details.
The onChange事件就像你期待的那樣,無論是form field更改了,event被觸發而不是on blur. 我們有意地更改了這個瀏覽器的默認表現因為onChange表現不當,React依賴這個事件來即使地響應用戶輸入。更多查看 FormsForm input屬性,例如value和checked,還有textarea。 More here.
HTML Entities 實體
你可以在JSX中插入HTML實體
<div>First · Second</div>
如果你想要動態地顯示HTML內容,你陷入double escaping問題,因為為了防止XSS漏洞攻擊, React默認跳過要顯示的字符串(譯者注:翻譯的好爛,這里說的是轉義字符的問題,直接用就顯示成字符串了,看例子)
// Bad: It displays "First · Second"
<div>{'First · Second'}</div>
有很多方法可以解決這個問題。最簡單的就是直接用JavaScript寫Unicode字符,你需要確保文件是用UTF-8保存的,并且瀏覽器設置了UTF-8編碼。
<div>{'First · Second'}</div>
一個更安全的方法是找到 unicode對應的數字 在JavaScript字符串里使用它
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
你可以使用字符串和JSX元素的混合數組,每個數組的JSX元素需要一個唯一的key鍵值
<div>{['First ', <span key="middot">·</span>, ' Second']}</div>
最后一種方法,你總有辦法插入原生的HTML代碼 insert raw HTML.
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
自定義HTML屬性
如果你要給HTML元素傳遞自定義的屬性,React默認不會渲染它,你要加個前綴data-
<div data-custom-attribute="foo" />
但是呢,自定義的組件中,用連字符-連接的自定義屬性都支持
<x-my-component custom-attribute="foo" />
網絡無障礙 屬性aria-* 會被好好的渲染
<div aria-hidden={true} />
第三章 交互性和動態的UIs
你已經學會了如何用React展示數據 learned how to display data 現在看看如何交互UI組件
簡單例子
class likeButton extends React.Component{
constructor() {
super();
this.state = { liked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({liked: !this.state.liked});
}
render() {
const text = this.state.liked ? 'liked' : 'haven\'t liked';
return (
<div onClick={this.handleClick}> You {text} this. Click to toggle. </div>
);
}
}
事件驅動和合成事件 Event Handling and Synthetic Events
React,可以把event handler作為prop傳遞進來,就像HTML那樣。React保證所有的事件在不同瀏覽器中有同樣的效果。React知道如何根據規范bubble和捕獲event事件,events被傳遞到你的event handler中,保證了不同瀏覽器中的一致性 the W3C spec
系統內部:自動綁定和事件委托
系統內部,React讓你的代碼易讀懂并且高性能
自動綁定Autobinding當創建JavaScript的callback函數時,你常常需要bind方法來綁定this,好讓正確的this變量傳入。React中每個方法都會自動綁定當前的組件變量(除非你使用ES6語法)。React會緩存綁定方法,可以提高CPU和內存的效力。還能讓你少打字。
事件委托Event delegationReact并不是真的把event handlers綁定到了節點node本身。當React啟動時,它先用一個event listener在最頂層top level監聽所有的events,當一個組件被掛載或者卸載,event handlers就相應地增加或刪除掉一個內部的映射mapping。當event發生,React知道如何利用這個mapping去派送它。當映射庫mapping中沒有event handlers時,React就執行空操作no-ops。(譯者注,就像老舊的電話接線機似的,接線員在最頂層,看到有個組件打電話進來了,它就根據線路圖傳送過去,組件掛掉電話時,它就把線路掐斷)如果你想了解為什么它如此高效:see David Walsh's excellent blog post.
組件和公平的(?Just)狀態機
React認為UIs都是狀態機。UI有多鐘不同的狀態,只需渲染這些不同的狀態就能很好地呈現你的UI。
React中,你只需要更新一個組件的狀態,然后根據這個新狀態render一個新的UI。React會高效率地自動為你更新DOM
State 狀態機是如何工作的
一個常見的方法就是用setState(data, callback)通知React數據已經改變了,這個方法會把新數據合并到當前狀態this.state,再重新渲染組件,當組件渲染完成,callback方法被調用。大部分時候你根本不用寫callback方法,因為React會好好為你更新UI的。
什么組件需要用到State呢?
你的大多數組件只是從props讀取數據再進行渲染。然而,有時你需要獲取input的值,一個server的請求或者一段時間。這種情況下你要用state
盡量讓你的組件避免使用state這樣做你可以保證state獨立的邏輯性,并且減少信息冗余。
一個常見的模式是,創建幾個不用state的組件來旋繞數據,然后在這基礎上創建一個state組件,將state這個參數通過props傳遞給他們。State組件封裝了所有需要交互的邏輯,非state組件負責渲染數據。
在state什么該做?
State應該包含數據,組件的event handlers可以改變這些數據,并更新UI。在真實的apps中,數據可能是很小的JSON串。當創建state組件時,考慮如何最小化地展示這個state,只在this.state里儲存必要的數據,基于這個數據計算出其他需要的信息。你會發現這樣思考后書寫出來的程序可以創造出最正確,因為給state增加的冗余的計算值會導致在同步時存儲他們,而不是依賴React組件去計算。
在state什么不該做?
this.state應該只包含需要呈現在UI上的極少的數據,它不應該包含:
計算出的數據。不要擔心根據state計算得到的值——如果你的計算都在render()中完成,更能保證你的UI是一致的。例如:如果你保存了一個list在state中,你想要render它的長度的字符串形式,只要render()中使用this.state.listItems.length+'list items' 方法,而不是把這個結果儲存在state中去調用
React組件根據props和state在render()中創建
props中的重復數據如果可能的話,嘗試用props作為數據來源。一個有效的使用就是在state中儲存props,這樣你就能知道它之前的值,因為props可能會根據父組件的渲染結果而改變。
多個組件
目前我們已經知道了如何書寫一個單獨的組件來戰士數據,并且處理用戶輸入。接下來讓我們看看React最出色的特性:可組合性。
Motivation: Separation of Concerns
動機:分離關注點
通過創建模塊化的組件,可以復用有完善接口的組件,就像使用fuctions和classes一樣,你能從模塊化組件受益多多。特別是,通過創建新組建可以分離app的關注點。通過給你的程序創建個性化組化庫,你能找到更適合更新UI的方式。
復合組件案例
讓我們創建一個簡單的Avatar組件,用來展示Facebook頁面的圖片和名字。這個案例調用了Facebook Graph API
所有者
上面這個例子,Avatar的own示例是PagePic和PageLink。React中,一個所有者就是給其他組件設置props的組件。更正式的說,如果組件Y的render中,創建了組件X,我們就說X被Y擁有。就像之前討論的,一個組件不能改變它的props,他們會一直和owner所有者設置的值一致。這個基礎不變量會保證UI的一致性。
區分owner-ownee主人-奴隸的關系、父-子的關系是很重要的,React中主奴的關系很明確,父子的關系就像DOM一樣。上述的例子中,Avatar奴役div,pagePic和PageLink,div是PagePic和PageLink的爸爸。
子
當你創建了一個React組件示例時,你可以在{ }包含額外的React組件或者JavaScript表達式
<Parent><Child /></Parent>
父Parent可以通過this.porps.children讀取children子的內容。this.props.children是一個特殊的數據結構,調用了 React.Children utilities 來操縱他們。
Child Reconciliation
Reconciliation是React用每個render更新DOM的過程。通常來說,子組件根據他們render的順序reconcil。例如,假設兩個render傳遞了以下的markup
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p>Paragraph 2</p>
</Card>
可以直觀的看出,<p>Paragraph 1</p>被去掉了。React改變第一個child的內容來更新DOM,并且destroy的最后一個child。React更具children的順序reconciles
擁有state的Children
對于大多數組件來說,這不是什么大問題。然而,對于有state的組件來說,攜帶著this.state.保存的data,進行render,就很有問題。
大多數情況下,這些可以通過隱藏元素而不是destroy元素來規避。
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p style={{display: 'none'}}>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
動態的Children
這種情況就更復雜了。當children被攪亂了(因為搜索結果??)或者,如果新的組件被添加到list的前面(在stream中)。這些情況下,通過render的每個child的identity和state都必須保持,你可以給每個孩子分配個獨特的key
render: function() {
var results = this.props.results;
return (
<ol> { results.map(function(result) {
return <li key={result.id}>{result.text}</li>; })
}
</ol>
);
}
當React reconciles有key的children時,它會確保所有的key都儲存起來。key必須直接在組件的數組中提供,而不是在包含的HTML children組件的容器上。(譯者注:不是綁在li上,是綁在包含li的組件上)
數據流
React中,主人的數據流通過props從主人向奴隸組件傳遞。這是高效的單方向數據捆綁:主人在props上捆綁奴隸需要的數據,主人根據props或者state進行計算。因為這個過程遞歸地進行,數據會自動更新。
性能上需要注意的是
你可能認為如果一個主任好多個奴隸節點,要更新一次數據很奢侈。好消息就是JavaScript很高效,render()方法又很簡單,素有大多數程序這個過程會非??臁A硗?,瓶頸總是發生在DOM的改變,而不是JS的遞歸。React會最優化批處理和改變檢測。然而,有時你真的想對性能更精細地掌控。這時你需要重寫override shouldComponentUpdate()。要 return false當你想要React跳過處理subtree。See the React reference docs for more information.
注意:
如果 shouldComponentUpdate() returns false 當數據發生改變時,React不能同步更新UI。請確保你知道你在干什么,只用在當你發現性能問題的時候,不要低估JavaScript更新DOM的速度。
重復利用的組件
當設計接口時,分解基本的設計元素(按鈕,表單,布局等)把他們變成可復用的組件。下次你建立UI的時候,你可以寫更少的代碼。這意味著縮短開發時間,減少bug,減少數據傳輸。
Prop 校檢
隨著你的app的成長,確保你的組件正確的使用也很重要。我們通過申明propTypes. React.PropTypes來做到這點。輸出一系列的驗證器可以確保你接收到的數據是有效的。當prop提供了一個無效的值時,警告會在JavaScript中拋出。注意,為了性能,propTypes只在開發模式下檢查。下面是不同的檢查器案例。
React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
optionalSymbol: React.PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: React.PropTypes.node,
// A React element.
optionalElement: React.PropTypes.element,
// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
optionalMessage: React.PropTypes.instanceOf(Message),
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// An object that could be one of many types
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// An array of a certain type
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// An object with property values of a certain type
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// An object taking on a particular shape
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn't provided.
requiredFunc: React.PropTypes.func.isRequired,
// A value of any data type
requiredAny: React.PropTypes.any.isRequired,
// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// You can also supply a custom validator to `arrayOf` and `objectOf`.
// It should return an Error object if the validation fails. The validator
// will be called for each key in the array or object. The first two
// arguments of the validator are the array or object itself, and the
// current item's key.
customArrayProp: React.PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
},
/* ... */
});
});
獨生子 Single Child
通過React.PropTypes.element 你可以申明,自己只有一個孩子可以被傳入組件
var MyComponent = React.createClass({
propTypes: {
children: React.PropTypes.element.isRequired
},
render: function() {
return (
<div>
{this.props.children} // This must be exactly one element or it will warn.
</div>
);
}
});
默認的Prop值
React允許你定義初始值(保存在props中), 通過 getDefaultProps()設置的初始值會保證this.props.value有值(如果父層沒有申明),這可以讓你安全的使用props而不用寫重復的代碼來處理。
轉移Props:快捷鍵
一種常見的React組件類型就是繼承基本的HTML元素。你可能常常想復制HTML屬性,傳遞給你的組件里的HTML元素,為了少打點字,你可以使用JSX語法來實現。(譯者注:這段就說你要用JSX可以簡單點)
class CheckLink extends React.Component {
render() {
// This takes any props passed to CheckLink and copies them to <a>
return (
<a {...this.props}>{'√ '}{this.props.children}</a>
);
}
}
ReactDOM.render(
<CheckLink href="/checked.html">
Click here!
</CheckLink>,
document.getElementById('example')
);
無狀態Functions
如果一個組件不使用local state(怎么翻譯==就是你那個function范圍內的state)或者 lifecycle hooks,你可以把它定義成一個function而非class
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
ReactDOM.render(
<Greeting name="Sebastian" />,
document.getElementById('example')
);
或者使用ES6語法:
const Greeting = (props) => (
<h1>Hello, {props.name}</h1>
);
ReactDOM.render(
<Greeting name="Sebastian" />,
document.getElementById('example')
);
簡化后的組件API是為了成為基于props的擁有更純粹的functions的組件。這些組件不可以使用內部的state,不可以有引用的變量(backing instances),不可以有組件內生命周期的方法(component lifecycle methods)。他們只是使用輸入參數的單純方法,并且沒有使用任何引用變量。
但是,你還是可以聲明propTypes和defaultProps,設置他們為function的屬性,ES6就這么寫
function Greeting(props) {
return (
<h1>Hello, {props.name}</h1>
);
}
Greeting.propTypes = {
name: React.PropTypes.string
};
Greeting.defaultProps = {
name: 'John Doe'
};
ReactDOM.render(
<Greeting name="M?d?lina"/>,
document.getElementById('example')
);
注意
因為沒有state的functions沒有引用變量,你不能給它附屬一個ref。通常情況下這不是個問題,因為沒有state的functions不需要提供必要的API。沒有必要的API,你拿著變量就啥也不能做。然而,如果一個用戶在無state的function中想要找到DOM節點,他們必須把function包裹在一個state組件中(譯者注:要有wrapper class),并且把ref附屬給那個包裹的組件上(wrapper class)。
理想情況下,你的許多組件都會是不需要state的functions。將來我們計劃優化這些組件,為了避免不必要的檢查和內存分配。
當你的組件中不需要local state或者lifecycle hooks,我們建議你把它申明為function。并且,我們推薦ES6 class語法來實現。
ES6 Classes 和 React.createClass()
通常你需要用普通的JavaScript class定義React組件
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
如果你不用ES6
var Greeting = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
聲明 Prop Types 和 默認的 Props
用functions和ES6 classes,propTypes和defaultProps被定義為組件屬性
class Greeting extends React.Component {
// ...
}
Greeting.propTypes = {
name: React.PropTypes.string
};
Greeting.defaultProps = {
name: 'Mary'
};
用React.createClass(),你需要將propTypes定義為傳入對象的屬性,getDefaultProps()是一個方法
var Greeting = React.createClass({
propTypes: {
name: React.PropTypes.string
},
getDefaultProps: function() {
return {
name: 'Mary'
};
},
// ...
});
設置初始state
In ES6 classes, you can define the initial state by assigning this.state
in the constructor:
ES6 classes中,你可以在constructor中通過給this.state賦值定義初始state
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
// ...
}
React.createClass()中,你需要單獨提供getInitialState方法,返回初始state
var Counter = React.createClass({
getInitialState: function() {
return {count: this.props.initialCount};
},
// ...
});
自動綁定
如果React組件是用ES6 classes聲明的,方法會遵從相同的ES6 classes語法。這意味著你不用給instance自動bind,你要在構造器中調用.bind(this)
class SayHello extends React.Component {
constructor(props) {
super(props);
// This line is important!
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert('Hello!');
}
render() {
// Because `this.handleClick` is bound, we can use it as an event handler.
return (
<button onClick={this.handleClick}>
Say hello
</button>
);
}
}
React.createClass() 就不需要bind 方法了
var SayHello = React.createClass({
handleClick: function() {
alert('Hello!');
},
render: function() {
return (
<button onClick={this.handleClick}>
Say hello
</button>
);
}
});
也就是說用ES6語法會在event handlers中多點代碼量,但是它的優勢是,會在大型程序中稍稍有更好的性能。如果你樂意多寫這幾個字,你可以啟用實驗性的屬性experimental Class Properties syntax proposal with Babel:
class SayHello extends React.Component {
// WARNING: this syntax is experimental!
// Using an arrow here binds the method:
handleClick = () => {
alert('Hello!');
}
render() {
return (
<button onClick={this.handleClick}>
Say hello
</button>
);
}
}
請注意,上面的屬于實驗性語法,將來可能會改變,可能不會采用,保險起見,你可以使用箭頭函數,e.g. onClick={(e) => this.handleClick(e)}),或者React.createClass()
Mixins 混合類
注意
ES6不支持mixin。因此,如果你使用ES6語法,就不可以使用mixins。我們在使用mixins的代碼庫中發現大量的問題。所以不推薦使用。
有時候非常復雜的組件可能會分享共用的功能。也就是所謂的 cross-cutting concerns. React.createClass
允許你使用合法的mixins系統。一個常見的例子就是,一個組件想要在一段時間間隔后自我更新。使用setInterval()函數很簡單,但是很重要的一點事,當你不需要的時候要取消你的interval來節省空間。React提供 lifecycle methods可以讓你知道什么時候一個組件即將創建或銷毀。讓我們來用這些方法創建一個簡單的mixin,來提供簡單的setInterval()方法,可以在你的組件銷毀時候自動地清理。
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.forEach(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // Use the mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // Call a method on the mixin
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
ReactDOM.render(
<TickTock />,
document.getElementById('example')
);
如果一個組件使用多個mixins,多個mixins定義了相同的lifecycle method(例如:多個mixins想要在銷毀時清理你的組件),所有的lifecycle methods都會被call。被call后,mixins按照了順序運行在里面定義的方法。