$ 前言
? 最近在考慮框架轉(zhuǎn)型,鑒于作為一名JSer钓辆,要時(shí)時(shí)刻刻保持對新技術(shù)和流行技術(shù)的敏感性泪幌,而 React呀非、Vue、Angular 已基本占領(lǐng)現(xiàn)前端市場禽作,React作為領(lǐng)頭大哥尸昧,建議年輕的JSer們都要學(xué)習(xí)使用或至少了解這門技術(shù)。
$ 版本聲明
? 本文使用版本 React v16.2.0
$ 什么是 React ?
? React是一個(gè)聲明式的旷偿,高效的烹俗,并且靈活的用于構(gòu)建用戶界面的 JavaScript 庫
? 一個(gè)最簡單的React例子
ReactDom.render(
<h1>Hello World</h1>,
document.getElementById('root')
)
? ReactDom.render
接受兩個(gè)參數(shù),第一個(gè)是要被插入的內(nèi)容萍程,第二個(gè)是插入到DOM或者說index.html
的位置
?
$ 一個(gè)與Html對比的簡單組件
? 如下是一個(gè) React 組件
class ShoppingList extends React.Componnet {
render() {
return (
<div className="shopping-list">
<h1>Shoping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
}
// Example usage: <ShoppingList name="Mark" />
? 在這里幢妄,ShoppingList是一個(gè) React組件類,或 React組件類型茫负。組件接受參數(shù)蕉鸳,稱為屬性 props
, 并通過 render
方法返回一個(gè)現(xiàn)實(shí)的視圖層次結(jié)構(gòu)。
? render
方法返回您要渲染的內(nèi)容描述,然后React接受該描述并將其渲染到屏幕上潮尝,特別是榕吼,render
返回一個(gè)React 元素,這是一個(gè)渲染內(nèi)容的輕量級的描述勉失。大多數(shù)
React 開發(fā)人員使用 JSX
語法羹蚣,也是上述案例寫到的語法。
? JSX
語法的轉(zhuǎn)換規(guī)則為: <div />
語法在構(gòu)建是被轉(zhuǎn)換為 React.createElement('div')
乱凿。因此顽素,上面的例子等價(jià)于:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* h1 children ... */),
React.createElement('ul', /* ul children ... */)
);
? 既然 JSX
在 React 開發(fā)者中這么流行,那 JSX
又是什么呢徒蟆?
?
$ JSX 語法
? JSX
它是 Javascript
的一種拓展語法胁出,能夠讓你的 Javascript
中和正常描述 HTML一樣編寫 HTML。
? 你可以用 花括號
將任意 Javascript
表達(dá)式嵌入到 JSX
中后专。例如:表達(dá)式 1 + 2
, 變量 user.firstName
, 和函數(shù) formatName(User)
等都可以嵌入使用
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'harper',
lastName: 'Perez'
}
const element = {
<h1> Hello, {formatName(user)}! </h1>
}
ReactDOM.render (
element,
document.getElementById('root')
)
? 請注意,為了方便閱讀開發(fā)者們常將 JSX
分割成多行包裹起來输莺,因?yàn)檫@可以避免分號自動插入的陷阱戚哎,如
{ 1
2 } 3
// is transformed to
{ 1
;2 ;} 3;
JSX 也是一個(gè)表達(dá)式
? 編譯之后, JSX
表達(dá)式也就成了一個(gè)常規(guī)的 javascript
對象
? 也正因?yàn)槿绱松┯茫覀兛梢栽?if
語句或這是 for
循環(huán)語句中使用 JSX
型凳,用它給變量賦值,當(dāng)做參數(shù)接受嘱函,或者作為函數(shù)的返回值
function getGreeting(user) {
if (user) {
return <h1>Hello. {formatName(User}</h1>;
}
return <h1>Hello, Stranger</h1>
}
用 JSX 指定屬性值
? 你可以用花括號嵌入一個(gè) JavaScript 表達(dá)式作為屬性值
// 用引號形式
const element = <div tableIndex="0"></div>;
// 用表達(dá)式甘畅,并且表達(dá)式用花括號包裹
const element = <img src={user.avatarUrl}></img>;
用 JSX 指定子元素
? 如果是空標(biāo)簽,可以直接用 />
閉合
const element = <img src={user.avatarUrl} />
? 如果包含子標(biāo)簽:
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
比起
HTML
往弓,JSX
更接近于Javascript
疏唾,所以React DOM
規(guī)范使用駝峰(camelCase)屬性命名約定,而不是HTML屬性名稱函似,當(dāng)然槐脏,html的部分屬性名稱也作為保留字,不可使用撇寞,例如class
顿天;
因此,class
在 JSX 中 變?yōu)?className
,tableindex
變?yōu)?tableIndex
蔑担。
用 JSX 防止注入攻擊
? 在JSX 中嵌入用戶輸入是安全的:
const title = response.potentiallyMaliciousInput;
// 這樣是安全的
const element = <h1>{title}</h1>
? 默認(rèn)情況下牌废, 在渲染之前, React DOM 會格式化(escapes) JSX中的所有值. 從而保證用戶無法注入任何應(yīng)用之外的代碼. 在被渲染之前,所有的數(shù)據(jù)都被轉(zhuǎn)義成為了字符串處理啤握。 以避免 XSS(跨站腳本) 攻擊鸟缕。
?
$ 元素渲染到DOM
? 正常情況下,你的 index.html
文件下會有這么一個(gè)div
<div id='root'></div>
? 這個(gè)root
DOM 節(jié)點(diǎn)掛在所有React DOM的位置排抬。正常情況下叁扫,對于一個(gè)React單頁面應(yīng)用構(gòu)建三妈,只需要一個(gè)單獨(dú)的根DOM節(jié)點(diǎn)即可。但如果要把React整合到現(xiàn)有的APP中莫绣,則可能會使用到多個(gè)DOM節(jié)點(diǎn)畴蒲。
? React利用render
方法將React元素渲染到DOM上,一旦元素被渲染到頁面了之后对室,就不能在修改器子元素或任何元素的屬性模燥,就像電影里的一幀,在某以特定的時(shí)間點(diǎn)的UI效果掩宜,那元素的更新呢蔫骂?沒錯,就是重新 render
function tick() {
cosnt element = {
<div>
<h1>Hello, world</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
}牺汤;
ReactDom.render (
element,
document.getElementById('root')
)
}
setInterval(tick, 1000);
實(shí)際上辽旋,大多數(shù) React 應(yīng)用只會調(diào)用一次ReactDom.render(),而實(shí)現(xiàn)組件更新的辦法就是將代碼封裝在有狀態(tài)的組件中檐迟。
React 只更新必須更新的部分
? 這正是 React 的強(qiáng)大之處补胚。React DOM 會將元素及其子元素與之前版本逐一對比,并只對有必要更新的 DOM 進(jìn)行更新, 以達(dá)到 DOM 所需的狀態(tài)追迟。
? 開發(fā)過程中溶其,更應(yīng)該每個(gè)時(shí)間點(diǎn)UI的表現(xiàn), 而不是關(guān)注隨著時(shí)間不斷更新UI的狀態(tài), 可以減少很多奇怪的 bug
?
$ 組件和屬性
? 組件 components
和屬性 props
,其中敦间,屬性是單詞 property
的代碼簡寫瓶逃。
定義組件的兩種辦法
? 定義組件有兩種方式
- 函數(shù)式組件定義
-
類組件定義
? 最簡單的定義組件的方法就是寫一個(gè)Javascript
函數(shù)
function Welcome(props) {
return <h1>Hello, props.name</h1>
}
? 這就是一個(gè)有效的組件,它接首了一個(gè) props
參數(shù)廓块,并返回了一個(gè)React元素厢绝,這是一個(gè)函數(shù)式組件,表面上看带猴,他就是一個(gè) Javascript
函數(shù)代芜。
? 類組件的定義則依賴ES6 的 class
來定義,下面這種定義方法和上方是等效的;
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染一個(gè)組件
// DOM標(biāo)簽作為組件
const element = <div />;
// React 元素作為組件
const element = <Welcome name="Sara" />;
? 當(dāng)React 遇到一個(gè)代表用戶定義組件的元素時(shí)浓利,它將 JSX
屬性以一個(gè)單獨(dú)對象即
props對象
的形式傳遞給相應(yīng)的組件挤庇,例如
function Welcome(props) {
return <h1>Hello, {props.mname] </h1>;
}
const element = <Wlecome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
)
【理解】
- 調(diào)用
ReactDOM.render()
方法并向其傳入了<Welcome name="Sara" />
元素 - Raect 調(diào)用
Welcome
組件,并向其傳入了{name: ‘Sara’}
作為props對象
-
Welcome
組件返回<h1>Hello, Sara</h1>
- React DOM 迅速更新 DOM贷掖,使其顯示為
<h1>Hello, Sara</h1>
組件名稱總是以大寫字母開始嫡秕, 如本例子中
<Welcome />
, 而不是<welcome />
構(gòu)成組件
? 既然組件是單獨(dú)的一個(gè)React元素,那他能單獨(dú)工作苹威,因此我們能在一個(gè)React 元素中多次引用到相同的組件, 舉個(gè)例子:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
function App() {
return (
<Welcome name="Sara" />
<Welcome name="Lucy" />
<Welcome name="Edite" />
)
}
ReactDOM.render(
<App />,
document.getElementBuId('root')
)
? 通常情況下昆咽, React apps 都有一個(gè)單獨(dú)的頂層的 App
組件。如果是在已有的應(yīng)用中整合React,也需要由下至上的從小的組件開始逐步整合到視圖頂層的組件中掷酗。
組件必須返回一個(gè)單獨(dú)的根元素调违,這就是為什么我們要添加一個(gè)
<div>
來包裹所有的<Welcome />
元素的原因
提取組件
? 對于一個(gè)React 元素,如果其中含有可復(fù)用或可能會重復(fù)使用的內(nèi)容泻轰,不要害怕把它單拿出來多個(gè)更小的組件技肩。
? 提取組件可能看起來是一個(gè)繁瑣的工作,但是在大型的 App
中可以回報(bào)給我們的是大量的可復(fù)用組件浮声。一個(gè)好的經(jīng)驗(yàn)準(zhǔn)則是如果你 UI 的一部分需要用多次 (Button
虚婿,Panel
,Avatar
)泳挥,或者本身足夠復(fù)雜(App
然痊,FeedStory
,Comment
)屉符,最好的做法是使其成為可復(fù)用組件剧浸。
Props 是只讀的
? 無論你用函數(shù)或類的方法來聲明組件,
? 雖然 React 很靈活,但是它有一條嚴(yán)格的規(guī)則:**所有 React 組件都必須是純函數(shù)矗钟,并禁止修改其自身 props **唆香。所謂的純函數(shù)就是:傳入函數(shù)參數(shù)不會在函數(shù)執(zhí)行過程中發(fā)生改變,比如自增操作 a++
真仲。
? 如果props
是只讀的袋马,那傳遞給子元素(子組件)的參數(shù)豈不是不能修改了初澎?那子元素如何與父元素做交互呢秸应?React還給我們提供了狀態(tài)屬性 state
供我們在子組件內(nèi)部修改值
?
狀態(tài)和生命周期
? 狀態(tài)state
, 生命周期 liftcircle
.
? 之前說過,一旦元素被渲染了之后就不可改變了碑宴,但我們可以通過重新渲染的方法使頁面得以刷新软啼,同樣我們提到過最常用的方法是編寫一個(gè)可復(fù)用的具有狀態(tài)的組件,這里的狀態(tài)延柠,就是我們將要說的 state
? 我們對上述提過的計(jì)時(shí)器tick
中的計(jì)時(shí)功能封裝成一個(gè)函數(shù)式組件如下:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
)
}
? 然后把他當(dāng)做一個(gè)元素放入 tick
中進(jìn)行渲染
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
)
}
setInterval(tick, 1000);
? 在這個(gè)例子中祸挪,我們將計(jì)時(shí)功能代碼封裝成了一個(gè)獨(dú)立的可復(fù)用的組件,并通過屬性date的方式將參數(shù)傳入贞间,但還不能到達(dá)我們想要的結(jié)果贿条,那就是不能再組件內(nèi)部修改參數(shù)值,組件中顯示的數(shù)據(jù)依舊受控于父組件中date
屬性傳遞過來的值增热,那如果我們把這個(gè)date
屬性也添加到Clock
內(nèi)部呢整以?來看下
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
? 這時(shí)父組件中只保留了對計(jì)時(shí)組件Clock
的一個(gè)單純的引用。剩下的事情全部依托以組件Clock
自己去實(shí)現(xiàn)峻仇。要怎么實(shí)現(xiàn)這個(gè)需求公黑?這里React提出了另一個(gè)數(shù)據(jù)對象,即state
,它用來保存組件內(nèi)部的數(shù)據(jù)凡蚜,與props
類似人断,不同的是state
是組件私有的,并且由組件本身完全控制朝蜘。它能實(shí)現(xiàn)數(shù)據(jù)在組件內(nèi)部的修改和更新恶迈。怎么使用這個(gè)state
?繼續(xù)往下講之前我們先拓展一個(gè)知識
? 我們知道組件有兩種定義方式芹务,即函數(shù)式組件和類組件蝉绷,雖然函數(shù)式組件更加簡潔更加接近原生 javascript
,但類組件卻擁有一些額外的屬性枣抱,這個(gè)類組件專有特性熔吗,就是狀態(tài)和生命周期鉤子,到這里也能清楚知道狀態(tài)的關(guān)鍵作用佳晶,然而函數(shù)式組件沒有這兩個(gè)特性桅狠,因此,在需要使用到狀態(tài)state
情況下轿秧,我們需要將函數(shù)式組件轉(zhuǎn)換成類組件
函數(shù)式組件轉(zhuǎn)化成類組件
? 嘗試把一個(gè)函數(shù)式組件轉(zhuǎn)化成類組件中跌,官網(wǎng)給出了以下步驟,以Clock
組件為例
- 創(chuàng)建一個(gè)繼承自
React.Component
類的ES6 class
同名類 - 添加一個(gè)名為
render()
的空方法 - 把原函數(shù)中的所有內(nèi)容移至
render()
中 - 在
render()
方法中使用this.props
替代props
- 刪除保留的空函數(shù)聲明
class Clock extents React.Component {
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
? 到此菇篡,Clock
組件已經(jīng)成功被我們修改成了一個(gè)類組件漩符,我們便可以在其中添加本地狀態(tài)state
和生命周期鉤子
class Clock extends React.Component {
// 用類構(gòu)造函數(shù)constructor初始化 this.state
constructor(props) {
// 使用super()將props傳遞給基礎(chǔ)構(gòu)造函數(shù)
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
? 這樣,我們的類組件Clock
就擁有了自己的屬性 this.state.date
驱还,也就不需要引用組件向其傳遞值了嗜暴,因此,我么可以把組件引用中的date
屬性刪掉议蟆,最終闷沥,我們將其渲染到DOM上,只使用組件引用咐容,其他都交給組件Clock
自己去實(shí)現(xiàn)
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
? 到這里就結(jié)束了舆逃?細(xì)心的你會發(fā)現(xiàn),組件Clock
只是實(shí)現(xiàn)了當(dāng)前時(shí)間的顯示戳粒,而我們要改裝的功能是一個(gè)計(jì)時(shí)器路狮,計(jì)時(shí)功能去哪里了?沒實(shí)現(xiàn)拔翟肌奄妨?我們需要在組件Clock
中找到一個(gè)合適的時(shí)機(jī)去實(shí)現(xiàn)這個(gè)功能,為此炊琉,React團(tuán)隊(duì)引入了 聲明周期方法展蒂,也叫生命周期鉤子
在類組件中添加生命周期方法
? 在一個(gè)具有許多組件的應(yīng)用程序中又活,在組件被銷毀時(shí)釋放所占用的資源是非常重要的。就像瀏覽器的垃圾回收機(jī)制锰悼,近期內(nèi)不需要再用的資源柳骄,應(yīng)該及時(shí)清除。
? 當(dāng) Clock
第一次渲染到DOM時(shí)箕般,我們要設(shè)置一個(gè)定時(shí)器 耐薯。 這在 React 中稱為 “掛載(mounting)” 。它有一個(gè)生命鉤子componentDidMount()
當(dāng) Clock
產(chǎn)生的 DOM 被銷毀時(shí)丝里,我們也想清除該計(jì)時(shí)器曲初。 這在 React 中稱為 “卸載(unmounting)” 。它的生命鉤子是componentWillUnmount()
? 我們的計(jì)時(shí)器是在頁面加載之后杯聚,頁面生成初始化狀態(tài)臼婆,然后由計(jì)時(shí)器去觸發(fā)狀態(tài)的刷新,因此幌绍,在掛載完成是去設(shè)置計(jì)時(shí)器是個(gè)非常不錯的選擇
componentDidMount() {
this.timerID = setInterval(
() => this.tick(), 1000
)
}
? 這樣我們就實(shí)現(xiàn)了組件計(jì)時(shí)功能颁褂,或許你注意到了,在該例中傀广,我們把timerID
存放在this
中而不是this.state
中颁独。
? 其實(shí),this.props
和this.state
也是數(shù)據(jù)對象與普通對象一樣用來存放數(shù)據(jù)伪冰,只是他們被React團(tuán)隊(duì)賦予了新的職能誓酒, this.props
由React本身設(shè)定,用來存放在組件引用時(shí)的屬性鍵值對對象集贮聂,不允許Coder們自己去修改靠柑;而this.state
也具有特殊的含義,即存放組件本身的寂汇、用于視覺輸出的數(shù)據(jù)病往,但也不是說在編寫React程序的時(shí)候就必須用用這兩個(gè)捣染,我們依然可以自己定義普通的數(shù)據(jù)結(jié)構(gòu)骄瓣。
? 既然state
是用于存放組件視覺輸出的數(shù)據(jù),那在render()
方法中沒有被引用的耍攘,就不應(yīng)該出現(xiàn)在state
中了榕栏。
? 養(yǎng)成良好的編碼習(xí)慣,編寫好計(jì)時(shí)器時(shí)蕾各,及時(shí)的編寫卸載事件扒磁。卸載時(shí)我們清除的數(shù)據(jù)也是從this
中拿的。
componentWillUnmount() {
clearInterval(this.timerID);
}
? 掛載時(shí)我們聲明了一個(gè)tick()
方法式曲,接下來我們就要實(shí)現(xiàn)這個(gè)方法妨托,是用來觸發(fā)UI更新缸榛。嗯哼?UI更新兰伤?我們的頁面狀態(tài)state
不是已經(jīng)更新了嗎内颗?為啥還要UI更新?
? 這里有一個(gè)非常重要的方法:setState()
敦腔。我們先把代碼補(bǔ)充完整再說明
componentDidMount() {
// ...
}
tick() {
this.setState({
date: new Date()
})
}
componentWillUnmount() {
// ...
}
? setState()
是React觸發(fā)頁面更新的第二個(gè)辦法均澳,第一個(gè)辦法開篇即說過,即render()
方法符衔。setState
作用就是通知React檢查帶狀態(tài)的組件中是否含有臟值找前。此時(shí)react會生成一個(gè)虛擬DOM與之前的版本進(jìn)行對比,只有有必要更新時(shí)才會更新判族。關(guān)于 state 與 setState過程 在我的另一篇文章中有詳細(xì)說明躺盛,有興趣的可以翻過去看看。
? 為什么不把tick()
方法寫到componentDidMount()
中形帮?因?yàn)?code>tick()只是一個(gè)普通方法颗品,他不需要在生命周期中觸發(fā),也不用自動觸發(fā)沃缘。只要誰調(diào)用了觸發(fā)即可躯枢。因此不需要也不能放在生命周期鉤子函數(shù)中。
? 現(xiàn)在這個(gè)時(shí)鐘每秒都會走了槐臀。整理一下脱吱,我們整個(gè)計(jì)時(shí)器代碼如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
整個(gè)流程的執(zhí)行過程是這樣的:
當(dāng)
<Clock />
被傳入ReactDOM.render()
時(shí), React 會調(diào)用Clock
組件的構(gòu)造函數(shù)迂求。 因?yàn)?Clock
要顯示的是當(dāng)前時(shí)間,所以它將使用包含當(dāng)前時(shí)間的對象來初始化this.state
。我們稍后會更新此狀態(tài)菩帝。然后 React 調(diào)用了
Clock
組件的render()
方法。 React 從該方法返回內(nèi)容中得到要顯示在屏幕上的內(nèi)容油航。然后糊闽,React 然后更新 DOM 以匹配Clock
的渲染輸出。當(dāng)
Clock
輸出被插入到 DOM 中時(shí)谍珊,React 調(diào)用componentDidMount()
生命周期鉤子治宣。在該方法中,Clock
組件請求瀏覽器設(shè)置一個(gè)定時(shí)器來一次調(diào)用tick()
砌滞。瀏覽器會每隔一秒調(diào)用一次
tick()
方法侮邀。在該方法中,Clock
組件通過setState()
方法并傳遞一個(gè)包含當(dāng)前時(shí)間的對象來安排一個(gè) UI 的更新贝润。通過setState()
, React 得知了組件state
(狀態(tài))的變化, 隨即再次調(diào)用render()
方法绊茧,獲取了當(dāng)前應(yīng)該顯示的內(nèi)容。 這次打掘,render()
方法中的this.state.date
的值已經(jīng)發(fā)生了改變华畏, 從而鹏秋,其輸出的內(nèi)容也隨之改變。React 于是據(jù)此對 DOM 進(jìn)行更新亡笑。如果通過其他操作將
Clock
組件從 DOM 中移除了, React 會調(diào)用componentWillUnmount()
生命周期鉤子, 所以計(jì)時(shí)器也會被停止拼岳。
?
正確的使用State(狀態(tài))
? 對于setState()
有三件事情是我們應(yīng)該要知道的
(1)不要直接修改state
? 真正觸發(fā)React對比不同版本的虛擬DOM是setState()
方法,直接修改state
頁面不會刷新况芒,這一點(diǎn)與原生javascript
區(qū)別較大惜纸,需要理解。
// 這么做不會觸發(fā)React更新頁面
this.state.comment = 'hello';
// 使用 setState() 代替
this.setState({ comment: 'hello' });
? 【注意】在組件中绝骚,唯一可以初始化分配this.state
的地方就是構(gòu)造函數(shù)constructor(){}
(2)state(狀態(tài))更新可能是異步的
? React為了優(yōu)化性能耐版,有可能會將多個(gè)setState()
調(diào)用合并為一次更新。這就導(dǎo)致 this.props
和this.state
可能是異步更新的压汪,你不能依賴他們的值計(jì)算下一個(gè)state(狀態(tài))
// counter 計(jì)數(shù)更新會失敗
this.setState({
counter: this.state.counter this.props.increment
})
? 如果我們有這種需求粪牲,可以使用以下setState()
辦法:
// ES6 箭頭函數(shù)法
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// 常規(guī)函數(shù)法
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
})
(3)state(狀態(tài))更新會被合并
當(dāng)你調(diào)用setState()
, React將合并你提供的對象到當(dāng)前狀態(tài)中。例如止剖,你的狀態(tài)可能包含幾個(gè)獨(dú)立的變量腺阳,然后你用幾個(gè)獨(dú)立的setState方法去調(diào)用更新,如下
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
? 合并是淺合并穿香,所以亭引,this.setState({comments})
在合并過程中不會改變this.state.posts
的值,但是會完全替換this.state.comments
的值
數(shù)據(jù)向下流動
? 無論是作為父組件還是子組件皮获,它都無法或者一個(gè)組件是否有狀體焙蚓,同時(shí)也不需要關(guān)心另一個(gè)組件是定義為函數(shù)組件還是類組件。這就是為什么state
經(jīng)常被稱為 本地狀態(tài) 或 封裝狀態(tài) 的原因洒宝, 他不能被擁有并設(shè)置它的組件以外的任何組件訪問购公。那如果需要訪問怎么處理?
(1)作為其子組件的props(屬性)
// 在組件中使用
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
// 傳遞給子組件作為props
<FormattedDate date={this.state.date} />
? 雖然FormattedDate
組件通過props
接收了date
的值雁歌,但它仍然不能獲知該值是來自于Clock
的state
, 還是 Clock
的props
, 或者一個(gè)手動創(chuàng)建的變量.
? 這種數(shù)據(jù)關(guān)系宏浩,一般稱為"從上到下"或"單向"的數(shù)據(jù)流。任何state(狀態(tài))
始終由某個(gè)特定組件所有靠瞎,并且從該state
導(dǎo)出的任何數(shù)據(jù) 或 UI 只能影響樹"下方"的組件
? 如果把組件樹想像為 props(屬性)
的瀑布比庄,所有組件的 state(狀態(tài))
就如同一個(gè)額外的水源匯入主流,且只能隨著主流的方向向下流動较坛。
各組件完全獨(dú)立
? 借用上文的Clock
組件印蔗,我們創(chuàng)建一個(gè)App
組件扒最,并在其中渲染三個(gè)Clock
:
function App() {
return (
// 之前說過組件只能返回一個(gè)根節(jié)點(diǎn)丑勤,所以用<div>包起來
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
? 每個(gè)Clock
都設(shè)立它自己的計(jì)時(shí)器并獨(dú)立更新,如果App
中有一個(gè)數(shù)據(jù)變量吧趣,也能被三個(gè)Clock
相互獨(dú)立修改法竞。
? 至于何時(shí)使用有狀態(tài)組件耙厚,何時(shí)使用無狀態(tài)組件,被認(rèn)為是組件的一個(gè)實(shí)現(xiàn)細(xì)節(jié)岔霸,取決于你當(dāng)時(shí)的需求薛躬,你可以在有狀態(tài)組件中使用無狀態(tài)組件,也可以在無狀態(tài)組件中使用有狀態(tài)組件
?
$ 事件處理
? 通過 React 元素處理事件跟在 DOM 元素上處理事件非常相似呆细。但是有一些語法上的區(qū)別:
- React 事件使用駝峰命名型宝,而不是全部小寫
- 通過 JSX , 傳遞一個(gè)函數(shù)作為事件處理程序,而不是一個(gè)字符串
// html usage
<button onclick="todo()">click me</button>
// React usage
<button onClick={todo}>click me></button>
- 在React中不能通過返回
false
來阻止默認(rèn)行為絮爷。必須明確的調(diào)用preventDefault
// html usage
<a href="#" onclick="console.log('clicked'); return false">
Click me
</a>
// React usage
class ActionLink extends React.Component {
function handleClick(e) {
e.preventDefault();
console.log('clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
)
}
? 在這里趴酣,React團(tuán)隊(duì)幫Coder們實(shí)現(xiàn)了e事件的跨瀏覽器兼容問題。當(dāng)使用React時(shí)坑夯,我們也不需要調(diào)用addEventListener
在DOM 元素被創(chuàng)建后添加事件監(jiān)聽器岖寞。相反,只要當(dāng)元素被初始渲染的時(shí)候提供一個(gè)監(jiān)聽器就可以了柜蜈。
? 當(dāng)使用ES6類定義一個(gè)組件時(shí)仗谆,通常的一個(gè)事件處理程序就是類上的一個(gè)方法,看個(gè)例子淑履,Toggle
組件渲染一個(gè)按鈕隶垮,讓用戶在 “ON” 和 “OFF” 狀態(tài)之間切換:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 這個(gè)綁定是必要的,使`this`在回調(diào)中起作用
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
綁定類方法
? 在JSX回調(diào)中你必須注意 this
的指向秘噪。 在 JavaScript 中岁疼,類方法默認(rèn)沒有 綁定 的。如果你忘記綁定 this.handleClick
并將其傳遞給onClick
缆娃,那么在直接調(diào)用該函數(shù)時(shí)捷绒,this
會是 undefined
。
?這不是 React 特有的行為贯要;這是 JavaScript
中的函數(shù)如何工作的一部分暖侨,可以使用屬性初始值設(shè)置來正確地 綁定(bind
) 回調(diào),但這是實(shí)驗(yàn)性做法崇渗,不建議使用字逗,以后有可能會廢棄,如果你沒有使用屬性初始化語法
(1)可以在回調(diào)中使用一個(gè) arrow functions
class LoginButton extends React.Component {
handleClick() {
console.log('this is: ', this)
}
render() {
// 這個(gè)語法確保 `this` 被綁定在 handleClick 中
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
(2)使用Function.prototype.bind
方法宅广,相對簡潔方便
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
傳遞參數(shù)給事件處理程序
? 在循環(huán)內(nèi)部葫掉,通常需要將一個(gè)額外的參數(shù)傳遞給事件處理程序,常用的有一下兩種方案;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this.id)}>Delete Row</button>
? 上面兩個(gè)例子中跟狱,參數(shù) e
作為 React 事件對象將會被作為第二個(gè)參數(shù)進(jìn)行傳遞俭厚。通過箭頭函數(shù)的方式,事件對象必須顯式的進(jìn)行傳遞驶臊,但是通過 bind
的方式挪挤,事件對象以及更多的參數(shù)將會被隱式的進(jìn)行傳遞叼丑。
?
$ 條件渲染
在 React 中,你可以創(chuàng)建不同的組件封裝你所需要的行為扛门。然后鸠信,只渲染它們之中的一些,取決于你的應(yīng)用的狀態(tài)论寨。
整個(gè)組件的條件渲染
? React 中的條件渲染就可在JS中的條件語句一樣星立,使用JS操作符如if
或者條件控制符來創(chuàng)建渲染當(dāng)前的元素,并且讓React更新匹配的UI葬凳。比如我們有一個(gè)需求贞铣,需要判斷用戶是否登錄,來顯示不同組件
function UserGreeting(props) {
return <h1>Welcome back!</h1>
}
function GustGrreeting(props) {
return <h1>Please sign up.</h1>
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />
}
return <GuestGreeting />
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
使用元素變量條件渲染部分內(nèi)容
? 你可以用變量來存儲元素沮明。這可以幫助您有條件地渲染組件的一部分辕坝,而輸出的其余部分不會更改。下方兩個(gè)組件用于顯示登出和登入按鈕
function LoginButton() {
return(
<button onClick={props.onClick}>Login</button>
)
}
function LogoutButton(props) {
return (
<button onClick={props.onclick}>Logout</button>
)
}
? 登入登出按鈕已做好荐健,接下來需要實(shí)現(xiàn)有切換功能的一個(gè)有狀態(tài)的組件酱畅,為了更系統(tǒng)化學(xué)習(xí),我們把前面的Greeting
組件一起加進(jìn)來
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoginedIn: false
}
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick.bind(this)} />
} else {
button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />{button}</div>
</div>
)
}
}
reactDOM.render(
<LoginControl />,
document.getElementById('root')
)
? 使用if
是很常見的一種做法江场,當(dāng)然也有一些更簡短的語纺酸。JSX
中有幾種內(nèi)聯(lián)條件的方法,
(1)使用邏輯與&&操作符的內(nèi)聯(lián)if用法
? 我們可以在 JSX
中嵌入任何表達(dá)式址否,方法是將其包裹在花括號中餐蔬,同樣適用于JS的邏輯與&&運(yùn)算符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{ unreadMeaasges.length > 0 &&
<h2> You have {unreadMessages.length} unread messages.
}
</div>
)
}
cosnt message = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
? 該案例是可以正常運(yùn)行的,因?yàn)樵?JavaScript
中佑附, true && expression
總是會評估為 expression
樊诺,而 false && expression
總是執(zhí)行為 false
。并且我們可以在表達(dá)式中嵌入表達(dá)式
(2)使用條件操作符的內(nèi)聯(lián)If-Else
? 條件操作符 即三目表達(dá)式:condition 音同? trueExpression : falseExpression
// 條件渲染字符串
<div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
// 條件渲染組件
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
? 總之词爬,遵循一個(gè)原則,哪種方式易于閱讀权均,就選擇哪種寫法顿膨。并且,但條件變得越來越復(fù)雜時(shí)叽赊,可能是提取組件的好時(shí)機(jī)恋沃。
阻止組件渲染
? 在極少數(shù)情況下,您可能希望組件隱藏自身必指,即使它是由另一個(gè)組件渲染的囊咏。為此,返回 null
而不是其渲染輸出。注意這里是不渲染匆笤,不是不顯示研侣。
? 在下面的例子中谱邪,根據(jù)名為warn
的 props
值炮捧,呈現(xiàn) <WarningBanner />
。如果 props
值為 false
惦银,則該組件不渲染:
function WarningBanner(props) {
if (props.warn) {
return null;
}
return (
<div className="warning">Warning</div>
)
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = { showWarning: true }
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
<Warningbanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick.bind(this)}>
{ this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
)
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
)
? 從組件的 render
方法返回 null
不會影響組件生命周期方法的觸發(fā)咆课。 例如, componentWillUpdate
和componentDidUpdate
仍將被調(diào)用扯俱。因此需要組件剛載入時(shí)就要判斷執(zhí)行返回null
$ 后語
? 本文為React系統(tǒng)性需學(xué)習(xí)上半文书蚪,下半文主要包括:
- 列表(
List
) 和 鍵(keys
) - 表單(
Forms
) - 狀態(tài)提升(
Lifting State Up
) - 組合 VS 繼承 (
Composition vs inheritance
)
? 如果有錯誤,歡迎大家指正迅栅,也歡迎大家到評論區(qū)交流共同進(jìn)步