本筆記基于React官方文檔,當(dāng)前React版本號為15.4.0。
1. 安裝
1.1 嘗試
開始之前可以先去codePen嘗試一下,也可以下載這份HTML文件并編輯它來嘗試React。
1.2 Creat React App工具
推薦使用React官方提供的Creat React App工具,來快速新建React單頁面應(yīng)用項目。
npm install -g create-react-app
create-react-app hello-world
cd hello-world
npm start
1.3 推薦工作流
雖然React可以在沒有任何構(gòu)建工具的情況下進(jìn)行使用,但在生產(chǎn)環(huán)境還是應(yīng)該使用成套的構(gòu)建工具來將React用于你的項目。一個現(xiàn)代化的(前端)工作流通常由以下三部分組成:
- 包管理器:比如Yarn或Npm,可以讓你更方便使用第三方庫而不用自己造輪子
- 編譯器:比如Babel,能翻譯使用了最新語法的代碼到瀏覽器兼容較好的版本
- 打包器 :比如Webpack或Browserify,讓你能夠編寫各種風(fēng)格的模塊化的代碼,由它們打包和壓縮
基于以上工作流,你可以通過Npm或者Yarn來將React安裝到項目,然后使用Babel來編譯JSX和ES6語法,最終用于生產(chǎn)環(huán)境的代碼還需要經(jīng)過Webpack或Browserify的打包和壓縮才能使用。
1.4 CDN服務(wù)
<!--開發(fā)環(huán)境-->
<script src="https://unpkg.com/react@15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>
<!--生產(chǎn)環(huán)境-->
<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
2. Hello World
一個最基本的React例子:
ReactDom.render(
<h1>Hello world!</h1>,
document.getElementById('root')
)
你可以在CodePen上嘗試修改這段代碼看看效果。
React推薦配合ES6語法使用,但僅需要了解() => {}、const、let、`template literals`和classes這幾個特性即可
3. 初識JSX
const element = <h1>hello world</h1>
上面這段既不是字符串又不是HTML的代碼(其實主要指的是<h1>hello world</h1>)就是JSX了。官方推薦搭配使用JSX,有別于模板語言,JSX是全功能的JavaScript。JSX 用于創(chuàng)建“React元素”。
3.1 JSX是表達(dá)式
跟其他JavaScript表達(dá)式一樣,JSX也是表達(dá)式,被React編譯后的JSX返回的是普通的JavaScript對象,這意味著你可以類似對待普通JavaScript表達(dá)式那樣對待一個JSX語句:將它賦值給變量、將他作為函數(shù)參數(shù)或返回值等等:
function getGreating (user) {
if (user) {
return <h1>hello {formatName(user)}!</h1>
}
return <h1>hello world!</h1>
}
稍微深入一點,Babel會將JSX轉(zhuǎn)換成對react.creatElement()的調(diào)用,所以下面兩種寫法完全等價:
// JSX
const mine = (
<h1 className="greeting">
這是我的標(biāo)題
</h1>
)
// javaScript
const yours = react.creatElement(
'h1',
{ className: 'greeting ' },
'這是你的標(biāo)題'
)
然而react.createElement()返回的結(jié)果是類似下面這樣的一個對象:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: '這是誰的標(biāo)題'
}
// ...
}
這就不難理解JSX的用法了——像一個javaScript表達(dá)式那樣去使用。
3.2 在JSX中嵌入JavaScript表達(dá)式
使用花括號{},可以在JSX中嵌入任意JavaScript表達(dá)式:
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
為了提升可讀性可以對JSX使用縮進(jìn)和換行,但是為了避免JavaScript自動添加分號的機制給我們帶來麻煩,應(yīng)該在換行的JSX外面添加一對小括號。
在JSX的元素中插入用戶輸入的內(nèi)容是安全的,React默認(rèn)會對元素內(nèi)的文本進(jìn)行轉(zhuǎn)義以防止XSS攻擊。
3.3 在JSX中聲明屬性
就像在HTML中聲明元素屬性,可以在“React元素”上直接聲明某個屬性。當(dāng)希望屬性值是變量或引用時,則就像在在JSX中嵌入JavaScript表達(dá)式,使用花括號{}來插入“React元素”的值。
// 簡單屬性值
const element = <div tabIndex="0"></div>;
// 屬性值為變量或引用
const element = <img src={user.avatarUrl}></img>;
需要注意的是,JSX中元素的屬性名統(tǒng)一使用駝峰寫法(camelCase),并且在React的內(nèi)置元素上,諸如
class、for等屬性還需要換成className和htmlFor來使用(自定義元素可以正常使用)。
3.4 在JSX中聲明子元素
如果“React元素”的標(biāo)簽內(nèi)沒有子元素,則可以像在XML中那樣使用單標(biāo)簽(包括React內(nèi)置的HTML元素)。
const element = <img src={user.avatarUrl} />;
如果存在子元素,則就像在HTML中那樣直接包裹在父元素中即可(注意換行的JSX要加小括號()):
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
)
4. 渲染元素
元素是React應(yīng)用的最小組成部分。元素描繪了界面。不同于瀏覽器的DOM元素,React元素是簡單對象,創(chuàng)建它們比創(chuàng)建真實的DOM元素要節(jié)省太多性能,同時React DOM負(fù)責(zé)將React元素和真實DOM元素對應(yīng)起來:
const ele = <h1>Hello World!</h1>
不能將React元素和React組件搞混,React元素是React組件的組成部分,一個React組件由一個或多個React元素組成。同時也要注意區(qū)別DOM元素和React元素,DOM元素指的是HTML標(biāo)準(zhǔn)中規(guī)定的具體的某個元素,而React元素實際上是用于告訴React如何渲染頁面、渲染時用到哪些DOM元素的一個配置對象,它與DOM元素不是一個概念。
4.1 將React元素渲染到DOM中
先創(chuàng)建一個React元素,然后用ReactDOM.render()將其渲染到DOM的某個元素中(就這么簡單):
const ele = <h1>Hello World!</h1>
ReactDOM.render(
ele,
document.getElementById('root') // 假設(shè)頁面上有一個id為root的元素
)
4.2 更新已經(jīng)渲染的元素
請記住,React元素是不可變的,一旦創(chuàng)建,你就不能再直接改變它的屬性或子元素。假如我們要更新上面已經(jīng)渲染到id為root的元素中的React元素,那么在沒有其他手段的前提下就只能是像電影膠片一樣一幀一幀進(jìn)行刷新:
function tick() {
const element = (
<div>
<h1>Hello World!</h1>
<p>{new Date().toLocaleTimeString()}</p>
</div>
)
ReactDOM.render(
ele,
document.getElementById('root') // 假設(shè)頁面上有一個id為root的元素
)
}
setInterval(tick, 1000) // 每秒刷新
當(dāng)然正常情況下我們不會這么做,但是這里很好的演示了另外一個問題——React在渲染頁面時都做了什么?答案是它只渲染了與上次渲染時DOM中不同的部分!React會比較當(dāng)前渲染與上次渲染時DOM中的不同之處,并只刷新這些地方!
[圖片上傳失敗...(image-89394-1535353259915)]
5. 組件和props(輸入屬性)
組件能讓你將UI分割成獨立的可復(fù)用的片段,這些片段都有各自隔離的作用域,不會互相干擾。你可以將組件理解成類似函數(shù)的概念,組件從它的props屬性接受參數(shù),然后返回React元素來描述UI。
5.1 用函數(shù)和類(class)定義組件
最簡單的定義組件的方式就是寫一個構(gòu)造函數(shù):
function Welcom (props) {
return <h1>hello, {props.name}</h1>
}
上面這個Welcom構(gòu)造函數(shù)就是一個合法的React組件,因為它接受一個對象作為參數(shù),然后返回React元素。我們稱這樣的組件為“函數(shù)式”的組件因為它就是一個JavaScript構(gòu)造函數(shù)。當(dāng)然也可以使用ES6的class特性來定義函數(shù):
class Welcom extends React.Component {
render () {
return <h1>hello, {this.props.name}</h1>
}
}
ES6的class特性其實是ES5的構(gòu)造函數(shù)和對象繼承特性的一個語法糖,上面的寫法也完全可以轉(zhuǎn)換為ES5的寫法。React推薦這種寫法存粹是因為寫起來方便,可讀性也更強。但這種寫法的重點是從React.Component繼承一些核心的屬性,后文還會細(xì)說。不過目前簡單起見,我們暫時還只是用簡單函數(shù)來創(chuàng)建組件。
5.2 渲染組件
React元素不僅僅可以用于指定需要使用的DOM元素,也可以用于指代自定義的組件:
// 指代需要使用的DOM元素
const ele1 = <div />
// 指代用戶自定義的組件
const ele2 = <Welcom name="Sara">
當(dāng)React遇到像<Welcom name="Sara">這種自定義組件時,它會將JSX屬性(也就是React元素屬性)都放在一個對象中(這個對象就是props)并將其傳遞給組件的構(gòu)造函數(shù),構(gòu)造函數(shù)再返回React元素用于渲染。
5.3 組件的組合
既然React元素可用于指代自定義組件,那么組件之間就可以相互嵌套使用:
function Welcom (props) {
return <h1>Hello, {props.name}</h1>
}
function WelcomList () {
return (
<div>
<Welcom name="Sara" />
<Welcom name="Lily" />
<Welcom name="Tom" />
</div>
)
}
function App () {
return <WelcomList />
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
5.4 組件的提取
既然組件可以嵌套組合使用,我們就可以將一個大的組件分割成很多小的組件。React官方鼓勵對UI進(jìn)行切割,分成不同的組件來實現(xiàn)?;旧弦唤MReact元素是否要提取成組件,可從以下兩點考慮:
- 這組元素在別的地方也要使用
- 這組元素內(nèi)部的功能相對復(fù)雜
這部分其實是組件化的思路,這里不再展開。
5.5 只讀的props
類似于“純函數(shù)”的概念(不會改變?nèi)魏瓮獠康闹?,包括輸入的參?shù),即與外部完全無耦合),不管是使用構(gòu)造函數(shù)還是類來定義組件,組件都不應(yīng)該修改它的props,因為這是輸入到組件中的參數(shù)。在這一點上,React做了嚴(yán)格限定:
所有的React組件必須像“純函數(shù)”那樣永遠(yuǎn)不修改自己的
props屬性
6. state(私有狀態(tài))和生命周期
我們以上文的時鐘的例子來理解組件的私有狀態(tài)和生命周期。
function tick() {
const element = (
<div>
<h1>Hello World!</h1>
<p>{new Date().toLocaleTimeString()}</p>
</div>
)
ReactDOM.render(
ele,
document.getElementById('root') // 假設(shè)頁面上有一個id為root的元素
)
}
setInterval(tick, 1000)
首先我們將時鐘作為組件提取出來:
// 時鐘組件
function Clock(props) {
return (
<div>
<h1>Hello World!</h1>
<p>{props.date.toLocaleTimeString()}</p>
</div>
)
}
// 重新渲染
function tick () {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root') // 假設(shè)頁面上有一個id為root的元素
)
}
// 每秒刷新
setInterval(tick, 1000)
我們發(fā)現(xiàn)對于Clock組件來說,刷新時間的功能其實完全與外部無關(guān),它不涉及到任何外部的變量,完全可以由Clock組件自己來實現(xiàn)而不是讓外部傳遞時間給它。此時Clock組件就需要“私有狀態(tài)”來實現(xiàn)這個功能了。
6.1 從React.Component上繼承
到目前為止,我們使用簡單的構(gòu)造函數(shù)來創(chuàng)建React組件,不管外部輸入屬性還是私有狀態(tài),都需要我們手動創(chuàng)建和管理,諸如修改私有狀態(tài)后刷新渲染,外部輸入屬性為只讀這類功能,如果我們沒有在構(gòu)造函數(shù)中手動實現(xiàn)則不會存在。
這時我們可以從React.Component這個React內(nèi)置的構(gòu)造函數(shù)上繼承一些有用的方法,這其中就包括對“私有狀態(tài)”和“生命周期”實現(xiàn)。我們可以使用ES6的class特性來實現(xiàn)這個繼承(當(dāng)然這不是必須的,完全可以使用ES5的構(gòu)造函數(shù)和原型的寫法,但那樣會繁瑣很多,可讀性也大大下降):
class Clock extends React.Component {
render () { // React提供的用于渲染和刷新組件的鉤子函數(shù)
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
6.2 定義組件私有狀態(tài)
React.Component提供了props和state來分別訪問外部輸入屬性和內(nèi)部私有狀態(tài)。我們可以在時鐘組件中通過state訪問私有狀態(tài),然后在其構(gòu)造函數(shù)中對該私有狀態(tài)進(jìn)行初始化,最后將它渲染到頁面上:
class Clock extends React.Component {
constructor (props) {
super(props) // ES6中類的constructor函數(shù)可以通過super訪問其父類的構(gòu)造函數(shù)
this.state = { date: new Date() }
} // 注意,ES6中類的方法之間不需要任何符號
render () {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
ReactDOM.render(
<Clock />, // 外部不再干涉Clock組件的刷新功能
document.getElementById('root)
)
注意
Clock類中的constructor構(gòu)造函數(shù)中,調(diào)用了父類的構(gòu)造函數(shù),這是為了實現(xiàn)完全的繼承。使用class特性創(chuàng)建React組件時應(yīng)當(dāng)總是執(zhí)行這一步。
6.3 添加生命周期函數(shù)
從組件被創(chuàng)建到組件被渲染到頁面到最終被銷毀,React提供了一系列的“生命周期鉤子”,用于在組件的不同階段調(diào)用回掉函數(shù)。為了讓Clock組件能夠自己刷新,我們希望在組件被創(chuàng)建后立即添加一個計時器進(jìn)行每秒刷新,同時在組件被銷毀時一并銷毀這個計時器,這樣我們就需要用到兩個生命周期鉤子函數(shù):
-
componentDidMount:組件被渲染到頁面后執(zhí)行 -
componentWillUnmount:組件被銷毀前執(zhí)行
class Clock extends React.Component {
constructor (props) {
super(props) // ES6中類的constructor函數(shù)可以通過super訪問其父類的構(gòu)造函數(shù)
this.state = { date: new Date() }
} // 注意,ES6中類的方法之間不需要任何符號
render () {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
componentDidMount () {
this.timerID = setInterval(this.tick.bind(this), 1000)
}
componentWillUnmount() {
clearInterval(this.timerID)
}
}
ReactDOM.render(
<Clock />, // 外部不再干涉Clock組件的刷新功能
document.getElementById('root)
)
注意我們將定時器存儲在了組件實例上,而不是
state中,請先記住一個原則:任何沒有在組件的render()函數(shù)中使用的變量,都不應(yīng)該存放在state中
然后再添加tick方法。在這個方法中我們需要改變組件state中的date的值,這時需要用到方法setState(),該方法會通知React現(xiàn)在state已經(jīng)改變了,而后React會去重新調(diào)用組件的Render()方法刷新DOM。這也是為什么會有**任何沒有在組件的render()函數(shù)中使用的變量,都不應(yīng)該存放在state中 **一說:
class Clock extends React.Component {
constructor (props) {
super(props) // ES6中類的constructor函數(shù)可以通過super訪問其父類的構(gòu)造函數(shù)
this.state = { date: new Date() }
} // 注意,ES6中類的方法之間不需要任何符號
render () {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
componentDidMount () {
this.timerID = setInterval(this.tick, 1000)
}
componentWillUnmount() {
clearInterval(this.timerID)
}
tick () {
this.setState({ date: new Date() }) // 該方法會觸發(fā)React調(diào)用實例的render方法進(jìn)行重繪
}
}
ReactDOM.render(
<Clock />, // 外部不再干涉Clock組件的刷新功能
document.getElementById('root)
)
6.4 組件生命周期小結(jié)
- 當(dāng)把組件傳遞給
ReactDOM.render()函數(shù)后,React會調(diào)用組件的構(gòu)造函數(shù)constructor,進(jìn)行一些初始化
- 當(dāng)把組件傳遞給
- 然后React會去調(diào)用
Clock組件的render()方法將組件渲染出來
- 然后React會去調(diào)用
- 當(dāng)組件渲染完畢后,React會調(diào)用
componentDidMount()生命周期鉤子函數(shù)
- 當(dāng)組件渲染完畢后,React會調(diào)用
- 當(dāng)
setState()函數(shù)被調(diào)用時,React會重新調(diào)用組件的render()方法進(jìn)行重繪
- 當(dāng)
- 當(dāng)組件被從DOM中移除時,React會調(diào)用
componentWillUnmount()生命周期鉤子函數(shù)
- 當(dāng)組件被從DOM中移除時,React會調(diào)用
6.5 setState注意事項
-
不要直接改變
state
直接對組件state中的屬性賦值將不會觸發(fā)DOM更新,因為React并不知道state被改變了 -
state的更新可能是異步的
React會一次處理多個對setState的調(diào)用以提高性能,所以調(diào)用setState()時不應(yīng)當(dāng)直接基于另外一些來自state或props中的屬性進(jìn)行計算,很有可能當(dāng)前計算的值并不是最終的值,當(dāng)用于計算的另一些值再次變化后,React并不會刷新DOM(因為沒有再次調(diào)用setState())。為了修正這點,React提供另一種調(diào)用setState()函數(shù)的方式:傳入一個函數(shù),而不是對象
// 錯誤的用法
this.setState({
counter: this.state.counter + this.props.increment
})
// 正確的用法
this.setState((prevState, props) => ({ // 接受一個表示前次state的參數(shù)和一個當(dāng)前props的參數(shù)
counter: prevState.counter + props.increment // 這里實際上是返回了一個對象,是ES6箭頭函數(shù)的簡寫
}))
-
setState是對象的合并而不是替換
setState方法是將傳入的參數(shù)對象或函數(shù)返回的對象與現(xiàn)有的state對象進(jìn)行合并,非常類似于使用Object.assign(prevState, newState)的效果
6.6 單項數(shù)據(jù)流
在React組件的嵌套中,父組件通過props向子組件傳遞數(shù)據(jù),不管傳遞進(jìn)來的數(shù)據(jù)是來自于父組件的props還是state還是別的地方,子組件不知道也不用關(guān)心,因為它不能修改通過props傳遞進(jìn)來的數(shù)據(jù)而只能讀取它。這樣,數(shù)據(jù)就可以從最外層的父組件一路向內(nèi)傳遞下去,但反過來卻不行。
這就是傳說中的“單項數(shù)據(jù)流”("top-down" or "unidirectional" data flow)了:每個組件只能修改本身和其子組件的數(shù)據(jù),而不能修改父組件的數(shù)據(jù)。這樣的好處不言而喻,數(shù)據(jù)和狀態(tài)的管理會更加方便,但有時候在應(yīng)用越來越復(fù)雜的時候,可能需要多個組件共享某些數(shù)據(jù)或狀態(tài),因此誕生了很多用于管理數(shù)據(jù)和狀態(tài)的庫,redux就是其中最有名的一個。
7. 事件
7.1 基本用法
在React中綁定事件跟直接在HTML中綁定事件非常相似,定義一個事件處理函數(shù),并在JSX中綁定它:
function Greeting () {
function sayHi(e) {
e.preventDefault()
console.log('Hi!')
}
return (
<a onClick={sayHi}>Click me to say hi!</a>
)
}
所有事件綁定屬性比如onClick均使用駝峰寫法(camelCase),事件綁定屬性的值不是字符串而是事件處理函數(shù)名稱,可以帶上()并傳參,無參數(shù)時可省略();
7.2 使用類定義組件時事件處理函數(shù)this的指向問題
使用ES6的class特性定義組件時,通常的做法是將事件處理函數(shù)當(dāng)作該類的方法寫在類中。但需要注意的是方法的this指向。
定義在類中的方法的默認(rèn)的this指向的是當(dāng)前的類的實例,但事件處理函數(shù)因為是綁定到了具體的元素上,就會丟失定義時this的指向。如果你的處理函數(shù)中使用了this關(guān)鍵字來指向當(dāng)前組件實例,那么你需要手動將該方法的this綁定到當(dāng)前組件實例,有三種方法可以進(jìn)行綁定:
1)在類的constructor中調(diào)用或在JSX中調(diào)用Function.prototype.bind()手動綁定
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this); // 手動綁定
}
handleClick() {
// console.log(this)
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
// <button onClick={this.handleClick.bind(this)}> // 在這里綁定也可以
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('content')
);
2)在JSX的事件綁定屬性中的事件處理函數(shù)外層再套一個箭頭函數(shù),在其中返回處理函數(shù)調(diào)用結(jié)果
render() {
return (
<button onClick={(e) => this.handleClick(e)}> // 這么綁定也行
Click me
</button>
);
}
3)Babel提供的一個ES8+的實驗性質(zhì)的寫法
class LoggingButton extends React.Component {
handleClick = () => { // 純粹的實驗性質(zhì)的寫法,需要babel的支持
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
7.3 事件對象
React的事件對象是一個完全由React給出的事件對象,該對象對各個瀏覽器做了兼容,同時保留了標(biāo)準(zhǔn)事件對象的接口,詳細(xì)信息可以查看React官網(wǎng)的參考。使用時需要關(guān)心的是如何在事件處理函數(shù)中使用事件對象。
在事件綁定的JSX中,處理函數(shù)接受一個名為event的參數(shù)來表示事件對象,可以認(rèn)為event在事件綁定插值中屬于React的保留字,如果需要往事件處理函數(shù)中傳遞更多參數(shù),請使用其他標(biāo)識符。
另外,7.2小節(jié)中不同的事件綁定寫法也對事件對象的處置略有不同,主要體現(xiàn)在事件綁定JSX中:
// 無括號
<button onClick={this.handleClick}>
Click me
</button>
// 帶括號
<button onClick={this.handleClick(event)}>
Click me
</button>
// 調(diào)用了bind()
<button onClick={this.handleClick.bind(this, event)}>
Click me
</button>
- 當(dāng)事件綁定插值中的處理函數(shù)省略了
()時,處理函數(shù)默認(rèn)接受一個表示事件對象的參數(shù), - 當(dāng)事件綁定插值中的處理函數(shù)未省略
()時,則需要顯示地使用保留字event來傳入事件對象,未傳入則為undefined;注意,不管有沒有在constructor中綁定this,直接在處理函數(shù)名后加()會導(dǎo)致頁面初始化時該函數(shù)被立即執(zhí)行一次,可能會有意想不到的錯誤,比如不能調(diào)用setState()方法等,所以強烈不建議用這種寫法 - 當(dāng)事件綁定插值中的處理函數(shù)調(diào)用了
bind()時,可以顯示地使用保留字event來傳入事件對象,否則React會在bind()函數(shù)參數(shù)序列的末尾默認(rèn)增加一個表示事件對象的參數(shù)
最后,在React中不能通過return false來阻止默認(rèn)事件,而是需要在事件處理函數(shù)中顯式調(diào)用event.preventDefault()。
8. 條件渲染
所有的JavaScript條件語句都可以用于React條件渲染,因為本質(zhì)上JSX就是JavaScript的擴展語言?;诖擞腥N常用的條件渲染:
if...else...
function UserGreeting () {
return <h1>Welcom back!</h1>
}
function GuestGreeting () {
return <h1>Please Sign up.</h1>
}
function App (props) {
if (!props.isLoggedIn) {
return <GuestGreeting />
}
return <UserGreeting />
}
ReactDOM.render(
<App isLoggedIn={false} />,
document.getElementById('root')
)
- 三元運算符
function App (props) {
return props.isLoggedIn ? <UserGreeting /> : <GuestGreeting />
}
- 短路
function App (props) {
return props.isLoggedIn && <UserGreeting /> // props.isLoggedIn為true則顯示UserGreeting,否則不顯式
}
如果判斷邏輯比較復(fù)雜,不能用三元或者短路表達(dá)式編寫,且判斷后的結(jié)果需要直接用在JSX中(JSX中只能通過{}插入表達(dá)式,而不能使用語句),則可使用if...else...語句判斷并將結(jié)果保存到變量,然后再返回變量或通過{}插值到JSX中:
function UserGreeting () {
return <h1>Welcom back!</h1>
}
function GuestGreeting () {
return <h1>Please Sign up.</h1>
}
function Button (props) {
return <button onClick={ props.handleToggle }>toggle me</button>
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
prevState: false
}
}
handleClick () {
this.setState(prevState => ({ isLoggedIn: !prevState.isLoggedIn }))
}
render () {
let greeting = this.state.isLoggedIn ? <UserGreeting /> : <GuestGreeting />
return (
<div>
<div><Button handleToggle={this.handleClick.bind(this)} /></div> // 注意this的重定向
{ greeting }
</div>
)
}
}
ReactDOM.render(
<App isLoggedIn={false} />,
document.getElementById('root')
)
另外,在組件的render函數(shù)中返回假值,會阻止組件渲染,結(jié)合條件判斷,能夠達(dá)到隱藏或顯示組件的目的。
9. 列表和key(索引)
9.1 渲染列表
可以像下面這樣渲染一個列表:
class List extends React.Component {
constructor (props) {
super(props)
}
render () {
let list = this.props.number.map(number => ( // 拼裝li
<li>{number}</li>
))
return (
<ul>{list}</ul>
)
}
}
ReactDOM.render(
<List number={[1, 2, 3, 4, 5]} />,
document.getElementById('root')
)
也可以將map()調(diào)用通過{}內(nèi)聯(lián)到JSX中:
class List extends React.Component {
constructor (props) {
super(props)
}
render () {
return (
<ul>{
this.props.number.map(number => ( // 內(nèi)聯(lián)map()方法
<li key={number}>{number}</li>
))
}</ul>
)
}
}
通常會使用數(shù)組的map()方法來從數(shù)組拼裝列表,這與使用JavaScript拼裝HTML類似。但上面的代碼運行時會出現(xiàn)警告:

9.2 key
在渲染列表時,React的差異比較算法需要一個在列表范圍內(nèi)的唯一key來提高性能(通常用于獲知哪個列表項改變了)。這個唯一的key需要我們手動提供。React官方建議使用列表數(shù)據(jù)中可用于唯一性標(biāo)識的字段來作為列表項渲染時的key。如果實在沒有,則可使用數(shù)組的index勉為其難,性能上可能會打折扣。
let list = this.props.number.map(number => ( // 拼裝li
<li key={number.toString()}>{number}</li>
))
key的使用需要注意一下幾點:
-
只能在數(shù)組內(nèi)指定
key:準(zhǔn)確地說,只能在map()的回調(diào)函數(shù)中使用key -
key需要在列表范圍內(nèi)保證唯一性:同一個數(shù)組中的key需要保證唯一性,但不同數(shù)組中的key無所謂 -
key不會作為props傳入組件:可以認(rèn)為key是React在JSX中的保留字,你不能用它來向組件傳遞數(shù)據(jù)而應(yīng)該改用其他詞
10. 表單
在React中存在一個“受控組件(Controlled Component)”的概念,專門指代被React控制了的表單元素。通過onChange事件的處理函數(shù)將表單元素值的變化映射到組件的state中,然后再將組件中的這個映射好的值通過{}在JSX中插值給表單元素的value,(二者缺一不可)這就是一個被React控制了的組件也即“受控組件”了。
class Form extends React.Component {
constructor (props) {
super(props)
this.state ={
inputTextValue: ''
}
this.handleInputTextChange = this.handleInputTextChange.bind(this)
}
render () {
return (
<form>
<input
value={this.state.inputTextValue} // 從state中將值綁定到表單元素
onChange={this.handleInputTextChange}/>
</form>
)
}
handleInputTextChange (e) {
this.setState({
inputTextValue: e.target.value // 將表單元素的值的變化映射到state中
})
}
}
ReactDOM.render(
<Form />,
document.getElementById('root')
)
基本上所有表單元素的使用都跟上例一樣,通過value來“控制”元素,讓state成為組件唯一的狀態(tài)保存地。但是有時候在非React項目中使用React或者一些其他原因,我們不希望使用受控組件時,可以選擇“非受控組件”技術(shù),這里不再展開。
11. 共享狀態(tài)提升
考慮下面的需求,頁面上有兩個輸入框,用來輸入貨幣數(shù)量,一個輸入美元,一個輸入人民幣,還有一行提示文字例如:“我們有1美元,也就是6.9元”;要求兩個輸入框隨意輸入一個,另一個輸入框會根據(jù)匯率自動顯示轉(zhuǎn)換后的貨幣數(shù)量,并且下方提示文字也跟隨變化。
通常情況下,我們會編寫一個用于輸入貨幣數(shù)量的組件,然后在頁面上放兩個這樣的組件:
const exchangeRate = 6.9339
const currency = {
'$': '美元',
'¥': '人民幣'
}
class CurrencyInput extends React.Component {
constructor (props) {
super(props)
this.state = {
value: ''
}
this.changeHandler = this.changeHandler.bind(this)
}
render () {
return(
<div>
<label>
{currency[this.props.currency]}:
<input value={this.state.value} onChange={this.changeHandler}/>
</label>
</div>
)
}
changeHandler (e) {
this.setState({
value: e.target.value
})
}
}
class App extends React.Component {
constructor (props) {
super(props)
}
render () {
return(
<div>
<CurrencyInput currency={'$'}/>
<CurrencyInput currency={'¥'} />
<p>我們有{}美元,也就是{}元</p>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
在上面的代碼中我們將貨幣種類通過props傳遞給輸入框組件,分別顯示了美元和人名幣的輸入框。然后在輸入框組件內(nèi)部,我們使用了上一節(jié)的“受控組件”技術(shù),將輸入框的值交由組件的state控制。但并沒有完成需求——兩個輸入框并不同步,同時組件外部也不知道組件中到底填了什么值所以下面的提示語句也沒有更新。
很多時候,若干組件需要隱射同一個變化的狀態(tài)。我們推薦將共享的狀態(tài)提升至它們最近的共同的祖先上。
就像官方推薦的那樣,這時我們就需要用到共享狀態(tài)提升技術(shù):我們要將兩個貨幣輸入框組件共享的“數(shù)量”狀態(tài),提升到它們最近的祖先組件上,也就是App組件上。
// ...省略的代碼
class CurrencyInput extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
render () {
return(
<div>
<label>
{CURRENCY[this.props.currency]}:
<input value={this.props.value} onChange={this.handleChange}/> // 需要傳遞額外參數(shù)的情況下只能再包一層
</label>
</div>
)
}
handleChange (e) {
this.props.onValueChange(e.target.value, this.props.currency) // 父級傳遞進(jìn)來的回調(diào)函數(shù)
}
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = { // 將共享狀態(tài)存放在祖先元素上
dollar: '',
yuan: ''
}
this.valueChangeHandler = this.valueChangeHandler.bind(this)
}
render () {
return( // 通過props向下傳遞共享狀態(tài)和回調(diào)函數(shù),很多情況下子組件共享的狀態(tài)父級也需要用到
<div>
<CurrencyInput value={this.state.dollar} currency={'$'} onValueChange={this.valueChangeHandler}/>
<CurrencyInput value={this.state.yuan} currency={'¥'} onValueChange={this.valueChangeHandler}/>
<p>我們有{this.state.dollar}美元,也就是{this.state.yuan}元</p>
</div>
)
}
valueChangeHandler (value, type) {
this.setState({
dollar: type === '$' ? value : this.exchange(value, type),
yuan: type === '¥' ? value : this.exchange(value, type)
})
}
exchange (value, type) {
return value * (type === '$' ? EXCHANGERATE : 1 / EXCHANGERATE)
}
}
// ... 省略的代碼
其實不管是美元還是人民幣,其實背后都只有一個數(shù)量,這個數(shù)量同時代表了一定數(shù)量的美元和一定數(shù)量的人民幣,所以更好地,我們可以也應(yīng)該只存放一個狀態(tài)在父組件上,然后在渲染子組件時計算子組件的狀態(tài)并傳遞給他們:
// ... 省略的代碼
function exchange (value, type) { // 將轉(zhuǎn)換函數(shù)放到全局以便子組件可以訪問
return value * (type === '$' ? EXCHANGERATE : 1 / EXCHANGERATE)
}
class CurrencyInput extends React.Component {
// ... 省略的代碼
render () {
// 子組件在渲染時自己計算自己的狀態(tài)
let currentCurrency = this.props.currentCurrency
let currency = this.props.currency
let value = ''
if (currentCurrency.value !== '' && !/^\s+$/g.test(currentCurrency.value)) {
value = currentCurrency.type === currency ?
currentCurrency.value :
exchange(currentCurrency.value, currentCurrency.type)
}
return(
<div>
<label>
{CURRENCY[currency]}:
<input value={value} onChange={this.handleChange}/>
</label>
</div>
)
}
// ... 省略的代碼
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
currentCurrency: { // 存儲一個值,這里具體做法時存儲當(dāng)前改變的值
value: '',
type: ''
}
}
this.valueChangeHandler = this.valueChangeHandler.bind(this)
}
render () {
// 將共享的狀態(tài)傳遞給組件,同時父組件需要的狀態(tài)也自己計算出來
return(
<div>
<CurrencyInput
currentCurrency={this.state.currentCurrency}
currency={'$'}
onValueChange={this.valueChangeHandler}/>
<CurrencyInput
currentCurrency={this.state.currentCurrency}
currency={'¥'}
onValueChange={this.valueChangeHandler}/>
<p>我們有{exchange(this.state.currentCurrency.value, '$')}美元,也就是{exchange(this.state.currentCurrency.value, '¥')}元</p>
</div>
)
}
valueChangeHandler (value, type) { // 這里只需要簡單映射關(guān)系即可,不再需要計算各個組件的具體狀態(tài)值
this.setState({
currentCurrency: { value, type }
})
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
上面的例子很好地貫徹了React官方反復(fù)強調(diào)推薦的“單項數(shù)據(jù)流”模式。雖然多寫了一些代碼,但是好處是可以減少因為子組件可以自行修改共享狀態(tài)而引起的一些bug,畢竟我們將共享狀態(tài)提升到父級組件上以后,所有對共享狀態(tài)的修改就都集中在父級組件上了。
另外,再次強調(diào)一個原則:任何可以由state或props計算出來的狀態(tài),都不應(yīng)該放在state中。就像上例那樣,應(yīng)該直接在render()函數(shù)中直接計算后使用。
12. 聚合而不是繼承
React官方推薦使用聚合而不是繼承來在組件之間復(fù)用代碼。通常有兩種服用的情況,一種是組件的部分結(jié)構(gòu)或內(nèi)容不確定,需要由外部傳入,這時組件就相當(dāng)于一個容器;另一種是從更為抽象的組件創(chuàng)建一個較為具體的組件,比如“彈層”和“登陸彈層”。
12.1 容器
當(dāng)組件內(nèi)有部分內(nèi)容不確定需要外部傳入時,可以使用一個特殊的props屬性children來傳入。在組件內(nèi)部訪問props.children可以獲取使用組件時寫在組件開始和結(jié)束標(biāo)簽內(nèi)的內(nèi)容:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
當(dāng)組件有多個部分內(nèi)容不確定都需要外部傳入時,單靠props.children就不能滿足需求了。但時不要忘記React組件的props可以接受任意類型的參數(shù),所以其實組件的內(nèi)容也完全可以直接使用props來傳遞到組件內(nèi)部:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() { // JSX中使用{}插入另一個JSX,因為JSX也是表達(dá)式
return <SplitPane left={ <Contacts /> } right={ <Chat /> } />
}
12.2 具象化
有時我們希望一個組件是另一個較為抽象的組件的特例(更為具象),官方推薦的做法是將抽象組件包裹在具象組件中,并使用props來配置它:
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
至于繼承...忘掉它吧。
13. Think in React (這部分直接翻譯的原文)
在React官方看來,React是構(gòu)建大型、高性能web應(yīng)用的不二之選。它在Fb和Ins表現(xiàn)得非常好。React最棒呆的地方在于它讓你在構(gòu)建應(yīng)用時如何看待你的應(yīng)用。下面給我們通過編寫一個搜索列表,來帶你體驗這個思維過程。
13.1 從效果圖開始
假設(shè)我們已經(jīng)有了一個JSON接口,并有了設(shè)計師給我們的效果圖:

JSON接口返回的數(shù)據(jù)格式如下:
[
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
]
13.2 第一步:將UI按照組件層級進(jìn)行分解
要做的第一件事就是在效果圖上的組件(和子組件)周圍畫框框,并命名組件。如果這是你的設(shè)計師同事給你的,那么這部分工作他可能已經(jīng)做完了,去和他嘮嘮。他的PSD圖層名很有可能可以作為你的組件名。
但具體怎么劃分組件呢?答案是跟你創(chuàng)建一個函數(shù)或?qū)ο笠粯?。這其中的一個原則是“單一職責(zé)原則”,具體來說就是一個組件應(yīng)該只做一件事,否則,它應(yīng)該被拆分成更多的子組件。
如果你經(jīng)常將JSON數(shù)據(jù)展現(xiàn)給你的用戶,那你應(yīng)該知道如果你創(chuàng)建了正確的數(shù)據(jù)模型,你的UI(和組件)將會規(guī)劃組織的非常好。因為你的UI和數(shù)據(jù)模型是同一個信息結(jié)構(gòu),這也意味著劃分組件是一件比較繁瑣的事情。就將你的組件按照J(rèn)SON返回的數(shù)據(jù)結(jié)構(gòu)拆分為就好了。

(未完待續(xù),這是React官方基礎(chǔ)教程的最后一章,有空我再繼續(xù)翻譯吧)
(2018年7月27日更新......天道好輪回,蒼天饒過誰......最終還是要寫react,時隔快2年了,我自己也得回來看文章復(fù)習(xí)一波...索性把坑填上吧?。?/p>
你會看到這個簡單的應(yīng)用中有5個組件。我們將每個組件所代表的數(shù)據(jù)用斜體表示。
- FilterableProductTable(橙色):示例的全部內(nèi)容
- SearchBar(藍(lán)色):接受 用戶輸入
- ProductTable(綠色):基于用戶輸入顯示過濾好的 產(chǎn)品列表
- ProductCategoryRow(青色):為每個 類目 顯示一個標(biāo)題
- ProductRow(紅色):為每個 產(chǎn)品 顯示一行
ProductTable的表頭(包含Name和Price標(biāo)簽)不是一個單獨的組件。這是個偏好問題,不管哪種方式都還存在爭議。這個例子中,我們將其留在了ProductTable中,因為ProductTable的任務(wù)是渲染 產(chǎn)品列表 ,而表頭也算列表的一部分。然而,當(dāng)這個表頭變得復(fù)雜了(比如當(dāng)我們添加了排序功能),就可以順理成章地將其抽出來成為ProductTableHeader組件。
既然現(xiàn)在我們已經(jīng)將組件都識別出來了,那就把他們結(jié)構(gòu)化一下吧。這很easy。效果圖中出現(xiàn)在另一個組件內(nèi)部的組件,在結(jié)構(gòu)上應(yīng)該作為前者的子組件:
-
FilterableProductTableSearchBar-
ProductTableProductCategoryRowProductRow
13.3 第二步:用React構(gòu)建一個靜態(tài)版本
現(xiàn)在你有了組件結(jié)構(gòu),是時候開始實現(xiàn)你的應(yīng)用了。最容易的方式是構(gòu)建一個帶著數(shù)據(jù)并渲染了UI,但是沒有交互的版本。最好是將這兩步分開做,因為構(gòu)建靜態(tài)版本只需要無腦編寫,而添加交互則需要大量思考和少量編寫。后面我們會看到這么做的原因。
為了構(gòu)建你的應(yīng)用的靜態(tài)版本,你會想要復(fù)用其它組件來構(gòu)建新的組件,并利用props來傳遞數(shù)據(jù)。props是從父組件傳遞數(shù)據(jù)到子組件的一種方式。如果你熟悉state,那么構(gòu)建這個靜態(tài)版本就完全不要使用state。state是專為交互性預(yù)留的,數(shù)據(jù)會隨時間改變。目前這還是個靜態(tài)版本,你還不需要它。
你可以自上而下或者自下而上進(jìn)行構(gòu)建。也就是說,你可以從較高層的組件開始構(gòu)建(比如從FilterableProductTable開始),也可以從較底層的組件開始(ProductRow)。在簡單的示例中,通常自上而下更容易,而在更大型的項目中,自下而上地構(gòu)建組件并同時為其編寫測試會來的更容易。
在這一步驟的結(jié)尾,你講會有一個可復(fù)用的組件庫來渲染你的數(shù)據(jù)。由于目前你的應(yīng)用還是靜態(tài)版本,組件們將只有render()方法。組件結(jié)構(gòu)最頂層的組件(FilterableProductTable)會將你的數(shù)據(jù)作為一個"prop"進(jìn)行接收。如果你修改了基礎(chǔ)數(shù)據(jù)并再次調(diào)用ReactDOM.render(),UI會跟著更新。很容易就能觀察到你UI的更新和變化的地方,因為到目前為止還沒有什么復(fù)雜的事情發(fā)生。React 的單向數(shù)據(jù)流(又被稱為單向綁定)使得一切有序而迅捷。
執(zhí)行這一步時如果需要可以鏈接到React文檔尋找?guī)椭?/p>
小插曲:Props與State
在React中有兩種數(shù)據(jù)模型:props和state。理解兩種模型的差別非常重要,如果你不了解其中差異,可以跳轉(zhuǎn)到React官方文檔進(jìn)行查看。
13.4 第三步:標(biāo)識出最?。ǖ暾模︰I狀態(tài)
為了讓你的UI可交互,你需要能夠觸發(fā)變化到你的基礎(chǔ)數(shù)據(jù)上。React用state將這件事變得很簡單。
為了正確構(gòu)建出你的應(yīng)用,首先你需要思考你的應(yīng)用需要的最小可變state集合。這里的關(guān)鍵是DRY: Don’t Repeat Yourself。搞清楚你的應(yīng)用需要的最小的state表示,然后按需要計算出其它的一切。比如,你要構(gòu)建一個TODO LIST,只需要維護(hù)一個TODO項數(shù)組即可;不需要單獨為數(shù)量保存一個state變量。相反,當(dāng)你想要渲染TODO的數(shù)量時,只需要簡單地讀取TODO項數(shù)組的長度即可。
考慮一下我們示例應(yīng)用中的所有數(shù)據(jù):
- 原始產(chǎn)品列表
- 用戶輸入的搜索文字
- 復(fù)選框的值
- 過濾后的產(chǎn)品列表
我們一個一個過一遍來搞清楚哪個才是state。只要簡單地在每個數(shù)據(jù)上問三個問題:
- 它是從父組件通過
props傳遞進(jìn)來的嗎?如果是,那它不是state - 它一直不會變化嗎?如果是,那它不是
state - 你能基于組建的其它
state或props計算出它來嗎?如果是,那它不是state
原始產(chǎn)品列表是從props傳遞進(jìn)來的,所以它不是state。用戶輸入的搜索文字和復(fù)選框的值好像是state,因為他們會變化,并且不能基于其它數(shù)據(jù)計算出來。最后,過濾后的產(chǎn)品列表不是state,因為他能夠基于原始產(chǎn)品列表、用戶輸入的搜索文字和復(fù)選框的值計算出來。
所以最終,我們的state是:
- 用戶輸入的搜索文字
- 復(fù)選框的值
13.5 第四步:確定你的state的位置
所以我們現(xiàn)在已經(jīng)get到了應(yīng)用需要的最小state集合,接下來我們需要搞清楚這些狀態(tài)應(yīng)該放到哪些組件里。
記?。篟eact的一切都是關(guān)于沿著組件層次結(jié)構(gòu)鄉(xiāng)下的單項數(shù)據(jù)流??赡芤婚_始你沒法馬上搞清楚哪個組件應(yīng)該擁有哪些狀態(tài)。這通常對于新手來說是最難的部分了,但你可以按下面的步驟來搞定:
對于你應(yīng)用里的每一個state來說:
- 確定基于該
state渲染的所有組件 - 找到這些組件的一個共同的所有者組件(在層級結(jié)構(gòu)中找到一個包含所有需要該
state的組件的單一組件) - 要么是共同的所有者組件,要么是更高層的祖先組件應(yīng)該擁有該
state - 如果你沒辦法找到一個合適的組件來控制這個
state,那就在共同的所有者組件之上再為其創(chuàng)建一個父組件來控制這個state
讓我們按照這個策略來看一下我們的應(yīng)用:
-
ProductTable需要基于state來過濾產(chǎn)品列表,SearchBar需要展示搜索文字和復(fù)選框狀態(tài) - 他們共同的所有者組件是
FilterableProductTable - 那么理論上把過濾文字和復(fù)選框的值放到
FilterableProductTable上是比較合適的
好,我們已經(jīng)決定將我們的state放在FilterableProductTable上了。首先,在FilterableProductTable的constructor中添加一個實例屬性this.state = { filterText: '', inStockOnly: false }來初始化你的應(yīng)用的state。然后將filterText和inStockOnly作為props傳遞給ProductTable和SearchBar。最后,用這倆props來過濾ProductTable的行,以及設(shè)置SearchBar的值。
你已經(jīng)可以看到你的應(yīng)用的一些表現(xiàn)了:將filterText設(shè)置為ball并刷新你的應(yīng)用,你將看到數(shù)據(jù)表格正確地更新了。
13.6 第五步:添加反向數(shù)據(jù)流
目前我們已經(jīng)構(gòu)建了一個應(yīng)用,props和state沿著層級結(jié)構(gòu)自上而下流轉(zhuǎn)并正確渲染出來?,F(xiàn)在是時候支持另一種方式的數(shù)據(jù)流了:層級結(jié)構(gòu)深處的的表單組件需要更新FilterableProductTable上的state。
React將這種數(shù)據(jù)流做的非常直白,讓你能更好理解地你的程序是如何運轉(zhuǎn)的。但相較于傳統(tǒng)的雙向數(shù)據(jù)綁定,它的確會需要多寫一點代碼。
如果你嘗試在當(dāng)前這個版本的示例中輸入或點擊復(fù)選框,React將會忽略你的輸入。這是故意的,因為我們已經(jīng)將input的value設(shè)置為總是等于從FilterableProductTable傳遞進(jìn)來的state。
讓我們思考一下我們想做什么。我們希望無論何時用戶修改了表單,都要更新state來反映用戶輸入。因為組件應(yīng)該只更新它們自己的state,FilterableProductTable會給SearchBar傳遞回調(diào)函數(shù),當(dāng)state需要更新時執(zhí)行該回調(diào)函數(shù)即可。我們可以使用input的onChange事件來通知其更新。FilterableProductTable傳遞的回調(diào)函數(shù)中會調(diào)用setState(),應(yīng)用就會更新了。
雖然這聽起來很復(fù)雜,但實際上卻只有幾行代碼而已。并且你的數(shù)據(jù)如何在應(yīng)用中流轉(zhuǎn)已經(jīng)非常明確了。
13.7 終于搞定了
希望這篇文章能給你一些如何用 React思考構(gòu)建組件和應(yīng)用的啟發(fā)。雖然這可能比你原來的代碼多一些,但請記住,讀代碼的次數(shù)要遠(yuǎn)遠(yuǎn)多于寫代碼的次數(shù),而且讀這種模塊化的、直白的代碼非常容易。當(dāng)你開始構(gòu)建大型組件庫時,你會感激這種明晰性和模塊化,并且隨著代碼的重用,你的代碼會越寫越少。:)