GUIDS
第一章 為什么使用React?
React
- 一個(gè)提供了用戶接口的JavaScript庫(kù)。
- 誕生于Facebook和Instagram項(xiàng)目中。
- 許多人把它看做是MVC編程模式。
我們編寫(xiě)React只為解決一件事:數(shù)據(jù)需要實(shí)時(shí)刷新的大型應(yīng)用程序
簡(jiǎn)單
當(dāng)相關(guān)數(shù)據(jù)發(fā)生改變時(shí),React會(huì)自動(dòng)更新所有的UI組件。你可以很簡(jiǎn)單地掌控不同狀態(tài)下的app
聲明式
當(dāng)數(shù)據(jù)改變時(shí),React就好像被點(diǎn)擊了更新按鈕一樣,知道如何更新需要改變的部分
建立復(fù)用組件
React都是為了建立可以重復(fù)利用的組件。實(shí)際上,你用React可以做的唯一一件事就是建立組件。因?yàn)榇a都是封裝起來(lái)的,組件讓代碼重復(fù)利用,方便測(cè)試,并且便于單獨(dú)考慮每個(gè)組件機(jī)制。
給它5分鐘
React挑戰(zhàn)了許多傳統(tǒng)的想法,你第一次看到這種想法可能覺(jué)得它很瘋狂Give it five minutes。 當(dāng)閱讀這篇指南時(shí),這些瘋狂的想法已經(jīng)在FB和Instagram還有其他網(wǎng)站上建立了上千個(gè)組件了。
學(xué)習(xí)更多
link.
第二章 展示數(shù)據(jù)
你能用UI做的最基本的事情就是顯示數(shù)據(jù)。React讓你很簡(jiǎn)單地展示數(shù)據(jù),并且讓用戶界面自動(dòng)地更新這些數(shù)據(jù)。
開(kāi)始吧~
先讓我們看個(gè)例子: 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"> //
**寫(xiě)在這里 **
</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
這個(gè)頁(yè)面,當(dāng)你在input輸入字符時(shí),react自動(dòng)更改為時(shí)間,即使你現(xiàn)在沒(méi)寫(xiě)任何代碼,React自動(dòng)給你完成了。它是怎么做到的呢?除非特殊需要,React不會(huì)直接操縱Dom本身,它先在內(nèi)部模擬的DOM身上執(zhí)行這些變化,為你計(jì)算出最有效的改變方式,再應(yīng)用到實(shí)際DOM中。
傳入組件的參數(shù)叫做props—— properties的簡(jiǎn)稱(chēng)。他們是通過(guò)JSX語(yǔ)法傳入的。你可以把他們當(dāng)做組件的常量(不可變的量),也就是說(shuō),不要更改this.props
Comonets組件就像Functions
React組件非常簡(jiǎn)單。你可以把他們看作是簡(jiǎn)單的functions,接受參數(shù)props和state,再渲染出HTML。這樣想更能幫助你。
一個(gè)限制:React組件只能渲染一個(gè)單獨(dú)的節(jié)點(diǎn)(譯者注:就是說(shuō)所有的東西要包在一個(gè)div里,或者別的tag里面)。如果你想返回多個(gè)節(jié)點(diǎn),他們必須報(bào)在一個(gè)單獨(dú)的根節(jié)點(diǎn)中。
JSX語(yǔ)法
我們強(qiáng)烈認(rèn)為,組件是分離模塊正確的方式,而不是模版或者邏輯地展示(display logic)。我們認(rèn)為標(biāo)記和代碼要密切結(jié)合在一起。另外,邏輯地展示經(jīng)常很復(fù)雜,并且用模版語(yǔ)言會(huì)讓代碼變得笨重。
我們發(fā)現(xiàn)最好的解決方法就是用Javascript直接生成HTML和組件樹(shù),這樣你就用真正的編程語(yǔ)言來(lái)建立UI。
為了更簡(jiǎn)單的說(shuō)明,我們?cè)黾恿艘粋€(gè)更簡(jiǎn)單的像HTML的語(yǔ)法來(lái)創(chuàng)建這些React樹(shù)節(jié)點(diǎn)。JSX可以讓你通過(guò)HTML語(yǔ)法創(chuàng)建JavaScript對(duì)象。在React中生成一個(gè)鏈接的JavaScript語(yǔ)法是
React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!')
使用JSX的話,你就可以直接這樣寫(xiě)
<a >Hello!</a>
我們發(fā)現(xiàn)這讓開(kāi)發(fā)React app開(kāi)發(fā)更簡(jiǎn)單,設(shè)計(jì)師更喜歡這種語(yǔ)法,但是每個(gè)人有自己的工作流,所以JSX不是React開(kāi)發(fā)所必須的
JSX很小,學(xué)習(xí)更多請(qǐng)看 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的命令行工具。
沒(méi)有JSX的React
JSX完全是可選的,你可以不適用它。完全用JavaScript創(chuàng)建React元素你要使用React.createElement, 它接收標(biāo)簽名或者組件作為參數(shù),還有很多可選的子變量。
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'));
為了方便,你可以創(chuàng)建簡(jiǎn)寫(xiě)的工廠函數(shù)
var Factory = React.createFactory(ComponentClass);
...
var root = Factory({ custom: 'prop' });
ReactDOM.render(root, document.getElementById('example'));
對(duì)于常用的HTML標(biāo)簽,React已經(jīng)有內(nèi)置的factories
var root = React.DOM.ul({ className: 'my-list' }, React.DOM.li(null, 'Text Content') );
2.1 JSX in Depth
JSX is 是一個(gè)JavaScript語(yǔ)義延伸,看起來(lái)像XML。你可以用簡(jiǎn)單的JSX語(yǔ)義書(shū)寫(xiě)React
[此處省略原因和比較](https://facebook.github.io/react/docs/jsx-in-depth.html)
組件命名空間
如果你正在建立一個(gè)有很多子組件的組件,例如一個(gè)表單,你可以會(huì)有很多很多的變量聲明:
// 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>
);
為了讓它更簡(jiǎn)便,命名空間*應(yīng)運(yùn)而生,他可以讓你使用組件的時(shí)候,可以用其他組件作為屬性。
你還需要聲明:
var MyFormComponent = React.createClass({ ... });
MyFormComponent.Row = React.createClass({ ... });
MyFormComponent.Label = React.createClass({ ... });
MyFormComponent.Input = React.createClass({ ... });
JSX 會(huì)在編譯的時(shí)候自動(dòng)handle這些。
JavaScript 表達(dá)式
Attribute 表達(dá)式
如果要使用JavaScript 表達(dá)式作為屬性值,要把他們包在{ } 花括號(hào)中,而不是“ ”引號(hào)中
// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ''} />;
// Output (JS):
var person = React.createElement(
Person, {name: window.isLoggedIn ? window.name : ''}
);
Boolean 屬性
在JSX中沒(méi)有設(shè)置屬性的值則默認(rèn)為true, 設(shè)置了屬性后才會(huì)視為false。這個(gè)問(wèn)題時(shí)常出現(xiàn)在使用HTML中的disabled, required, checked, readOnly屬性的時(shí)候。
// 二者相同
<input type="button" disabled />;
<input type="button" disabled={true} />;
// 二者相同
<input type="button" />;
<input type="button" disabled={false} />;
Child Expressions 子表達(dá)式
JavaScript表達(dá)式也可以用在children上
// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
注釋
給你JSX添加注釋很簡(jiǎn)單,他們只是JS的語(yǔ)法。你要包在{ }里面
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} />;
多個(gè)Props是不好的
如果你提前不知道你想設(shè)置的屬性,你可能想要在之后把他們添加到一個(gè)對(duì)象
var component = <Component />;
component.props.foo = x; // bad
component.props.bar = y; // also bad
這是一種反模式,因?yàn)樗馕吨覀儾荒軒湍銠z查propTypes的正確性,這導(dǎo)致你的propTypes之后會(huì)報(bào)錯(cuò),模糊的堆疊追蹤???(a cryptic stack trace)。Props應(yīng)該是不變的,在某處改變props對(duì)象會(huì)造成無(wú)法預(yù)期的結(jié)果。所以此刻把它看成一個(gè)凍結(jié)的(不可更改的)對(duì)象。
延展屬性
現(xiàn)在你可以使用一個(gè)新的JSX特性,叫spread attributes
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
你可以傳遞并且拷貝組件的props的屬性,你可以多次使用,結(jié)合其他屬性。聲明的順序很重要,后申明的屬性會(huì)覆蓋之前的。
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'
奇怪的...是什么?
...是ES6的語(yǔ)法,我們支持這些語(yǔ)法來(lái)提供更簡(jiǎn)潔的JSX
2. 3 JSX Gotchas 性能和可伸縮性
JSX看起來(lái)像HTML但是有些重要的區(qū)別你需要知道
DOM的區(qū)別
為了實(shí)現(xiàn)跨平臺(tái)的統(tǒng)一,React完成了獨(dú)立于瀏覽器的事件和DOM系統(tǒng)。我們借機(jī)清掃了一些DOM未完善的地方。所有的DOM屬性(包括事件處理 event handler)都是駝峰命名,與JavaScript style一致。這里我們故意地打破了規(guī)則,因?yàn)樗乔昂竺艿摹?strong>然而,data-和aria-屬性 conform to the specs只是用小寫(xiě)。Style屬性接受一個(gè)駝峰法明明的JavaScript對(duì)象,而不是一個(gè)CSS字符串,這和JavaScript DOM style是一致的,并且能防止XSS安全漏洞。因?yàn)閏lass和for都是JavaScript的保留字, JSX元素內(nèi)置了DOM nodes DOM節(jié)點(diǎn),應(yīng)該使用className和htmlFor。自定義的元素可以使用class和for(eg.<my-tag class="foo" />。所有的事件對(duì)象遵照W3C規(guī)則,所有的events(包括submit)bubble correctly per the W3C spec. See Event System for more details.
The onChange事件就像你期待的那樣,無(wú)論是form field更改了,event被觸發(fā)而不是on blur. 我們有意地更改了這個(gè)瀏覽器的默認(rèn)表現(xiàn)因?yàn)閛nChange表現(xiàn)不當(dāng),React依賴這個(gè)事件來(lái)即使地響應(yīng)用戶輸入。更多查看 FormsForm input屬性,例如value和checked,還有textarea。 More here.
HTML Entities 實(shí)體
你可以在JSX中插入HTML實(shí)體
<div>First · Second</div>
如果你想要?jiǎng)討B(tài)地顯示HTML內(nèi)容,你陷入double escaping問(wèn)題,因?yàn)闉榱朔乐筙SS漏洞攻擊, React默認(rèn)跳過(guò)要顯示的字符串(譯者注:翻譯的好爛,這里說(shuō)的是轉(zhuǎn)義字符的問(wèn)題,直接用就顯示成字符串了,看例子)
// Bad: It displays "First · Second"
<div>{'First · Second'}</div>
有很多方法可以解決這個(gè)問(wèn)題。最簡(jiǎn)單的就是直接用JavaScript寫(xiě)Unicode字符,你需要確保文件是用UTF-8保存的,并且瀏覽器設(shè)置了UTF-8編碼。
<div>{'First · Second'}</div>
一個(gè)更安全的方法是找到 unicode對(duì)應(yīng)的數(shù)字 在JavaScript字符串里使用它
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
你可以使用字符串和JSX元素的混合數(shù)組,每個(gè)數(shù)組的JSX元素需要一個(gè)唯一的key鍵值
<div>{['First ', <span key="middot">·</span>, ' Second']}</div>
最后一種方法,你總有辦法插入原生的HTML代碼 insert raw HTML.
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
自定義HTML屬性
如果你要給HTML元素傳遞自定義的屬性,React默認(rèn)不會(huì)渲染它,你要加個(gè)前綴data-
<div data-custom-attribute="foo" />
但是呢,自定義的組件中,用連字符-連接的自定義屬性都支持
<x-my-component custom-attribute="foo" />
網(wǎng)絡(luò)無(wú)障礙 屬性aria-* 會(huì)被好好的渲染
<div aria-hidden={true} />
第三章 交互性和動(dòng)態(tài)的UIs
你已經(jīng)學(xué)會(huì)了如何用React展示數(shù)據(jù) learned how to display data 現(xiàn)在看看如何交互UI組件
簡(jiǎn)單例子
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>
);
}
}
事件驅(qū)動(dòng)和合成事件 Event Handling and Synthetic Events
React,可以把event handler作為prop傳遞進(jìn)來(lái),就像HTML那樣。React保證所有的事件在不同瀏覽器中有同樣的效果。React知道如何根據(jù)規(guī)范bubble和捕獲event事件,events被傳遞到你的event handler中,保證了不同瀏覽器中的一致性 the W3C spec
系統(tǒng)內(nèi)部:自動(dòng)綁定和事件委托
系統(tǒng)內(nèi)部,React讓你的代碼易讀懂并且高性能
自動(dòng)綁定Autobinding當(dāng)創(chuàng)建JavaScript的callback函數(shù)時(shí),你常常需要bind方法來(lái)綁定this,好讓正確的this變量傳入。React中每個(gè)方法都會(huì)自動(dòng)綁定當(dāng)前的組件變量(除非你使用ES6語(yǔ)法)。React會(huì)緩存綁定方法,可以提高CPU和內(nèi)存的效力。還能讓你少打字。
事件委托Event delegationReact并不是真的把event handlers綁定到了節(jié)點(diǎn)node本身。當(dāng)React啟動(dòng)時(shí),它先用一個(gè)event listener在最頂層top level監(jiān)聽(tīng)所有的events,當(dāng)一個(gè)組件被掛載或者卸載,event handlers就相應(yīng)地增加或刪除掉一個(gè)內(nèi)部的映射mapping。當(dāng)event發(fā)生,React知道如何利用這個(gè)mapping去派送它。當(dāng)映射庫(kù)mapping中沒(méi)有event handlers時(shí),React就執(zhí)行空操作no-ops。(譯者注,就像老舊的電話接線機(jī)似的,接線員在最頂層,看到有個(gè)組件打電話進(jìn)來(lái)了,它就根據(jù)線路圖傳送過(guò)去,組件掛掉電話時(shí),它就把線路掐斷)如果你想了解為什么它如此高效:see David Walsh's excellent blog post.
組件和公平的(?Just)狀態(tài)機(jī)
React認(rèn)為UIs都是狀態(tài)機(jī)。UI有多鐘不同的狀態(tài),只需渲染這些不同的狀態(tài)就能很好地呈現(xiàn)你的UI。
React中,你只需要更新一個(gè)組件的狀態(tài),然后根據(jù)這個(gè)新?tīng)顟B(tài)render一個(gè)新的UI。React會(huì)高效率地自動(dòng)為你更新DOM
State 狀態(tài)機(jī)是如何工作的
一個(gè)常見(jiàn)的方法就是用setState(data, callback)通知React數(shù)據(jù)已經(jīng)改變了,這個(gè)方法會(huì)把新數(shù)據(jù)合并到當(dāng)前狀態(tài)this.state,再重新渲染組件,當(dāng)組件渲染完成,callback方法被調(diào)用。大部分時(shí)候你根本不用寫(xiě)callback方法,因?yàn)镽eact會(huì)好好為你更新UI的。
什么組件需要用到State呢?
你的大多數(shù)組件只是從props讀取數(shù)據(jù)再進(jìn)行渲染。然而,有時(shí)你需要獲取input的值,一個(gè)server的請(qǐng)求或者一段時(shí)間。這種情況下你要用state
盡量讓你的組件避免使用state這樣做你可以保證state獨(dú)立的邏輯性,并且減少信息冗余。
一個(gè)常見(jiàn)的模式是,創(chuàng)建幾個(gè)不用state的組件來(lái)旋繞數(shù)據(jù),然后在這基礎(chǔ)上創(chuàng)建一個(gè)state組件,將state這個(gè)參數(shù)通過(guò)props傳遞給他們。State組件封裝了所有需要交互的邏輯,非state組件負(fù)責(zé)渲染數(shù)據(jù)。
在state什么該做?
State應(yīng)該包含數(shù)據(jù),組件的event handlers可以改變這些數(shù)據(jù),并更新UI。在真實(shí)的apps中,數(shù)據(jù)可能是很小的JSON串。當(dāng)創(chuàng)建state組件時(shí),考慮如何最小化地展示這個(gè)state,只在this.state里儲(chǔ)存必要的數(shù)據(jù),基于這個(gè)數(shù)據(jù)計(jì)算出其他需要的信息。你會(huì)發(fā)現(xiàn)這樣思考后書(shū)寫(xiě)出來(lái)的程序可以創(chuàng)造出最正確,因?yàn)榻ostate增加的冗余的計(jì)算值會(huì)導(dǎo)致在同步時(shí)存儲(chǔ)他們,而不是依賴React組件去計(jì)算。
在state什么不該做?
this.state應(yīng)該只包含需要呈現(xiàn)在UI上的極少的數(shù)據(jù),它不應(yīng)該包含:
計(jì)算出的數(shù)據(jù)。不要擔(dān)心根據(jù)state計(jì)算得到的值——如果你的計(jì)算都在render()中完成,更能保證你的UI是一致的。例如:如果你保存了一個(gè)list在state中,你想要render它的長(zhǎng)度的字符串形式,只要render()中使用this.state.listItems.length+'list items' 方法,而不是把這個(gè)結(jié)果儲(chǔ)存在state中去調(diào)用
React組件根據(jù)props和state在render()中創(chuàng)建
props中的重復(fù)數(shù)據(jù)如果可能的話,嘗試用props作為數(shù)據(jù)來(lái)源。一個(gè)有效的使用就是在state中儲(chǔ)存props,這樣你就能知道它之前的值,因?yàn)閜rops可能會(huì)根據(jù)父組件的渲染結(jié)果而改變。
多個(gè)組件
目前我們已經(jīng)知道了如何書(shū)寫(xiě)一個(gè)單獨(dú)的組件來(lái)戰(zhàn)士數(shù)據(jù),并且處理用戶輸入。接下來(lái)讓我們看看React最出色的特性:可組合性。
Motivation: Separation of Concerns
動(dòng)機(jī):分離關(guān)注點(diǎn)
通過(guò)創(chuàng)建模塊化的組件,可以復(fù)用有完善接口的組件,就像使用fuctions和classes一樣,你能從模塊化組件受益多多。特別是,通過(guò)創(chuàng)建新組建可以分離app的關(guān)注點(diǎn)。通過(guò)給你的程序創(chuàng)建個(gè)性化組化庫(kù),你能找到更適合更新UI的方式。
復(fù)合組件案例
讓我們創(chuàng)建一個(gè)簡(jiǎn)單的Avatar組件,用來(lái)展示Facebook頁(yè)面的圖片和名字。這個(gè)案例調(diào)用了Facebook Graph API
所有者
上面這個(gè)例子,Avatar的own示例是PagePic和PageLink。React中,一個(gè)所有者就是給其他組件設(shè)置props的組件。更正式的說(shuō),如果組件Y的render中,創(chuàng)建了組件X,我們就說(shuō)X被Y擁有。就像之前討論的,一個(gè)組件不能改變它的props,他們會(huì)一直和owner所有者設(shè)置的值一致。這個(gè)基礎(chǔ)不變量會(huì)保證UI的一致性。
區(qū)分owner-ownee主人-奴隸的關(guān)系、父-子的關(guān)系是很重要的,React中主奴的關(guān)系很明確,父子的關(guān)系就像DOM一樣。上述的例子中,Avatar奴役div,pagePic和PageLink,div是PagePic和PageLink的爸爸。
子
當(dāng)你創(chuàng)建了一個(gè)React組件示例時(shí),你可以在{ }包含額外的React組件或者JavaScript表達(dá)式
<Parent><Child /></Parent>
父Parent可以通過(guò)this.porps.children讀取children子的內(nèi)容。this.props.children是一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu),調(diào)用了 React.Children utilities 來(lái)操縱他們。
Child Reconciliation
Reconciliation是React用每個(gè)render更新DOM的過(guò)程。通常來(lái)說(shuō),子組件根據(jù)他們r(jià)ender的順序reconcil。例如,假設(shè)兩個(gè)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改變第一個(gè)child的內(nèi)容來(lái)更新DOM,并且destroy的最后一個(gè)child。React更具children的順序reconciles
擁有state的Children
對(duì)于大多數(shù)組件來(lái)說(shuō),這不是什么大問(wèn)題。然而,對(duì)于有state的組件來(lái)說(shuō),攜帶著this.state.保存的data,進(jìn)行render,就很有問(wèn)題。
大多數(shù)情況下,這些可以通過(guò)隱藏元素而不是destroy元素來(lái)規(guī)避。
// 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>
動(dòng)態(tài)的Children
這種情況就更復(fù)雜了。當(dāng)children被攪亂了(因?yàn)樗阉鹘Y(jié)果??)或者,如果新的組件被添加到list的前面(在stream中)。這些情況下,通過(guò)render的每個(gè)child的identity和state都必須保持,你可以給每個(gè)孩子分配個(gè)獨(dú)特的key
render: function() {
var results = this.props.results;
return (
<ol> { results.map(function(result) {
return <li key={result.id}>{result.text}</li>; })
}
</ol>
);
}
當(dāng)React reconciles有key的children時(shí),它會(huì)確保所有的key都儲(chǔ)存起來(lái)。key必須直接在組件的數(shù)組中提供,而不是在包含的HTML children組件的容器上。(譯者注:不是綁在li上,是綁在包含li的組件上)
數(shù)據(jù)流
React中,主人的數(shù)據(jù)流通過(guò)props從主人向奴隸組件傳遞。這是高效的單方向數(shù)據(jù)捆綁:主人在props上捆綁奴隸需要的數(shù)據(jù),主人根據(jù)props或者state進(jìn)行計(jì)算。因?yàn)檫@個(gè)過(guò)程遞歸地進(jìn)行,數(shù)據(jù)會(huì)自動(dòng)更新。
性能上需要注意的是
你可能認(rèn)為如果一個(gè)主任好多個(gè)奴隸節(jié)點(diǎn),要更新一次數(shù)據(jù)很奢侈。好消息就是JavaScript很高效,render()方法又很簡(jiǎn)單,素有大多數(shù)程序這個(gè)過(guò)程會(huì)非??臁A硗?,瓶頸總是發(fā)生在DOM的改變,而不是JS的遞歸。React會(huì)最優(yōu)化批處理和改變檢測(cè)。然而,有時(shí)你真的想對(duì)性能更精細(xì)地掌控。這時(shí)你需要重寫(xiě)override shouldComponentUpdate()。要 return false當(dāng)你想要React跳過(guò)處理subtree。See the React reference docs for more information.
注意:
如果 shouldComponentUpdate() returns false 當(dāng)數(shù)據(jù)發(fā)生改變時(shí),React不能同步更新UI。請(qǐng)確保你知道你在干什么,只用在當(dāng)你發(fā)現(xiàn)性能問(wèn)題的時(shí)候,不要低估JavaScript更新DOM的速度。
重復(fù)利用的組件
當(dāng)設(shè)計(jì)接口時(shí),分解基本的設(shè)計(jì)元素(按鈕,表單,布局等)把他們變成可復(fù)用的組件。下次你建立UI的時(shí)候,你可以寫(xiě)更少的代碼。這意味著縮短開(kāi)發(fā)時(shí)間,減少bug,減少數(shù)據(jù)傳輸。
Prop 校檢
隨著你的app的成長(zhǎng),確保你的組件正確的使用也很重要。我們通過(guò)申明propTypes. React.PropTypes來(lái)做到這點(diǎn)。輸出一系列的驗(yàn)證器可以確保你接收到的數(shù)據(jù)是有效的。當(dāng)prop提供了一個(gè)無(wú)效的值時(shí),警告會(huì)在JavaScript中拋出。注意,為了性能,propTypes只在開(kāi)發(fā)模式下檢查。下面是不同的檢查器案例。
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.'
);
}
})
},
/* ... */
});
});
獨(dú)生子 Single Child
通過(guò)React.PropTypes.element 你可以申明,自己只有一個(gè)孩子可以被傳入組件
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>
);
}
});
默認(rèn)的Prop值
React允許你定義初始值(保存在props中), 通過(guò) getDefaultProps()設(shè)置的初始值會(huì)保證this.props.value有值(如果父層沒(méi)有申明),這可以讓你安全的使用props而不用寫(xiě)重復(fù)的代碼來(lái)處理。
轉(zhuǎn)移Props:快捷鍵
一種常見(jiàn)的React組件類(lèi)型就是繼承基本的HTML元素。你可能常常想復(fù)制HTML屬性,傳遞給你的組件里的HTML元素,為了少打點(diǎn)字,你可以使用JSX語(yǔ)法來(lái)實(shí)現(xiàn)。(譯者注:這段就說(shuō)你要用JSX可以簡(jiǎn)單點(diǎn))
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')
);
無(wú)狀態(tài)Functions
如果一個(gè)組件不使用local state(怎么翻譯==就是你那個(gè)function范圍內(nèi)的state)或者 lifecycle hooks,你可以把它定義成一個(gè)function而非class
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
ReactDOM.render(
<Greeting name="Sebastian" />,
document.getElementById('example')
);
或者使用ES6語(yǔ)法:
const Greeting = (props) => (
<h1>Hello, {props.name}</h1>
);
ReactDOM.render(
<Greeting name="Sebastian" />,
document.getElementById('example')
);
簡(jiǎn)化后的組件API是為了成為基于props的擁有更純粹的functions的組件。這些組件不可以使用內(nèi)部的state,不可以有引用的變量(backing instances),不可以有組件內(nèi)生命周期的方法(component lifecycle methods)。他們只是使用輸入?yún)?shù)的單純方法,并且沒(méi)有使用任何引用變量。
但是,你還是可以聲明propTypes和defaultProps,設(shè)置他們?yōu)閒unction的屬性,ES6就這么寫(xiě)
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')
);
注意
因?yàn)闆](méi)有state的functions沒(méi)有引用變量,你不能給它附屬一個(gè)ref。通常情況下這不是個(gè)問(wèn)題,因?yàn)闆](méi)有state的functions不需要提供必要的API。沒(méi)有必要的API,你拿著變量就啥也不能做。然而,如果一個(gè)用戶在無(wú)state的function中想要找到DOM節(jié)點(diǎn),他們必須把function包裹在一個(gè)state組件中(譯者注:要有wrapper class),并且把ref附屬給那個(gè)包裹的組件上(wrapper class)。
理想情況下,你的許多組件都會(huì)是不需要state的functions。將來(lái)我們計(jì)劃優(yōu)化這些組件,為了避免不必要的檢查和內(nèi)存分配。
當(dāng)你的組件中不需要local state或者lifecycle hooks,我們建議你把它申明為function。并且,我們推薦ES6 class語(yǔ)法來(lái)實(shí)現(xiàn)。
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 和 默認(rèn)的 Props
用functions和ES6 classes,propTypes和defaultProps被定義為組件屬性
class Greeting extends React.Component {
// ...
}
Greeting.propTypes = {
name: React.PropTypes.string
};
Greeting.defaultProps = {
name: 'Mary'
};
用React.createClass(),你需要將propTypes定義為傳入對(duì)象的屬性,getDefaultProps()是一個(gè)方法
var Greeting = React.createClass({
propTypes: {
name: React.PropTypes.string
},
getDefaultProps: function() {
return {
name: 'Mary'
};
},
// ...
});
設(shè)置初始state
In ES6 classes, you can define the initial state by assigning this.state
in the constructor:
ES6 classes中,你可以在constructor中通過(guò)給this.state賦值定義初始state
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
// ...
}
React.createClass()中,你需要單獨(dú)提供getInitialState方法,返回初始state
var Counter = React.createClass({
getInitialState: function() {
return {count: this.props.initialCount};
},
// ...
});
自動(dòng)綁定
如果React組件是用ES6 classes聲明的,方法會(huì)遵從相同的ES6 classes語(yǔ)法。這意味著你不用給instance自動(dòng)bind,你要在構(gòu)造器中調(diào)用.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>
);
}
});
也就是說(shuō)用ES6語(yǔ)法會(huì)在event handlers中多點(diǎn)代碼量,但是它的優(yōu)勢(shì)是,會(huì)在大型程序中稍稍有更好的性能。如果你樂(lè)意多寫(xiě)這幾個(gè)字,你可以啟用實(shí)驗(yàn)性的屬性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>
);
}
}
請(qǐng)注意,上面的屬于實(shí)驗(yàn)性語(yǔ)法,將來(lái)可能會(huì)改變,可能不會(huì)采用,保險(xiǎn)起見(jiàn),你可以使用箭頭函數(shù),e.g. onClick={(e) => this.handleClick(e)}),或者React.createClass()
Mixins 混合類(lèi)
注意
ES6不支持mixin。因此,如果你使用ES6語(yǔ)法,就不可以使用mixins。我們?cè)谑褂胢ixins的代碼庫(kù)中發(fā)現(xiàn)大量的問(wèn)題。所以不推薦使用。
有時(shí)候非常復(fù)雜的組件可能會(huì)分享共用的功能。也就是所謂的 cross-cutting concerns. React.createClass
允許你使用合法的mixins系統(tǒng)。一個(gè)常見(jiàn)的例子就是,一個(gè)組件想要在一段時(shí)間間隔后自我更新。使用setInterval()函數(shù)很簡(jiǎn)單,但是很重要的一點(diǎn)事,當(dāng)你不需要的時(shí)候要取消你的interval來(lái)節(jié)省空間。React提供 lifecycle methods可以讓你知道什么時(shí)候一個(gè)組件即將創(chuàng)建或銷(xiāo)毀。讓我們來(lái)用這些方法創(chuàng)建一個(gè)簡(jiǎn)單的mixin,來(lái)提供簡(jiǎn)單的setInterval()方法,可以在你的組件銷(xiāo)毀時(shí)候自動(dòng)地清理。
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')
);
如果一個(gè)組件使用多個(gè)mixins,多個(gè)mixins定義了相同的lifecycle method(例如:多個(gè)mixins想要在銷(xiāo)毀時(shí)清理你的組件),所有的lifecycle methods都會(huì)被call。被call后,mixins按照了順序運(yùn)行在里面定義的方法。