
說在前面
關(guān)于 react 的總結(jié)過去半年就一直碎碎念著要搞起來,各(wo)種(tai)原(lan)因(le)。心心念的東西終于要重新拿了起來了。希望這個總結(jié)歸納能對你的日常開發(fā)或者跳槽面試有幫助哪怕只有那么一點點,反正對我?guī)椭峭Υ螅瑴毓识侣铮?/p>
廢話說了一堆, 這個總結(jié)可能大概也許會以問答的形式總結(jié)
希望你能各個擊破,像闖關(guān)卡一樣一個一個過!開始吧 !Let's go!
【1】react component有幾種寫法?分別是什么?
① 函數(shù)式定義的無狀態(tài)組件(Stateless Functional)
- 性能更高效、代碼更簡潔
- 沒有 state,也就是無狀態(tài)
- 不需要管理/維護 組件的生命周期
- 純函數(shù),相同的 props 會得到同樣的UI渲染結(jié)果
function List (props) {
return <div>我是一個函數(shù)式定義的react組件</div>
}
② ES5方式 React.createClass 定義的組件(該方式已經(jīng)被廢棄,推薦使用①和③)
③ ES6 方式定義的組件(Class Components)
class List extends React.Component {
render() {
return <div>我是一個es6方式定義的react組件</div>
}
}
官方文檔寫的還是頗具神秘感的,先告訴我們①和③方式在 UI 渲染效果是一毛一樣的,但是'Classes have some additional features...' Class 這種方式比 Functional 這種方式多了些不一樣的地方。那么問題來了。多了哪些不一樣的呢? 不一樣的地方你可能也發(fā)現(xiàn)了,有無 state,有無生命周期...等
【2】那什么時候該用 Stateless Functional 什么時候用 Class 呢?
推薦使用 Functional,能用 Functional 就用 Functional,就醬。
多說一句。
Class 是用來創(chuàng)建包含狀態(tài)生命周期和用戶交互的復(fù)雜組件,而當(dāng)組件只是用來純展示或者 props 傳遞渲染時(展示性),二話不說請用 Stateless Functional 來快速創(chuàng)建組件。
【3】無狀態(tài)組件(Stateless Functional)有哪些優(yōu)缺點
- 優(yōu)點
- 語法/代碼更加簡潔
- 占用內(nèi)存?。]有 props 等屬性), 首次 render 性能更好
- 不需要管理/維護組件的生命周期
- 純函數(shù),相同的 props 會得到同樣的 UI 渲染結(jié)果
- 單元測試更容易進行。因為邏輯都被移出了 view 層,所以單元測試時不需要渲染任何東西,可以專注于單個邏輯。
- 缺點
- 無生命周期函數(shù)。對于一個函數(shù)而言應(yīng)該是談不上生命周期。當(dāng)然了,我們其實可以使用高階組件去實現(xiàn)生命周期。
- 沒有 this。在Stateless中 this 是 undefined 。
【4】React.Component 綁定方法的幾種方法?
//第一種方法:構(gòu)造函數(shù)中綁定
class List extends React.Component {
constructor(props) {
super(props)
this.onClickList = this.onClickList.bind(this)
}
onClickList() {
console.log('我被點了')
}
render() {
return <div onClick={this.onClickList}>點我點我點我</div>
}
}
//第二種方法: 在render()行內(nèi)事件使用bind來綁定
class List extends React.Component {
onClickList() {
console.log('我被點了')
}
render() {
return <div onClick={this.onClickList.bind(this)}>點我點我點我</div>
}
}
//第三種方法: 使用箭頭函數(shù) =>
class List extends React.Component {
onClickList = () => {
console.log('我被點了')
}
render() {
return <div onClick={this.onClickList}>點我點我點我</div>
}
}
//第四種,當(dāng)然,你要在render()行內(nèi)使用箭頭函數(shù)也行
class List extends React.Component {
onClickList() {
console.log('我被點了')
}
render() {
return <div onClick={() => this.onClickList()}>點我點我點我</div>
}
}
我日常開發(fā)都比較喜歡用箭頭函數(shù)的方法,代碼量比第一種少??。當(dāng)然,官方說在 render 中創(chuàng)建函數(shù)(第二,和第四種)可能會有性能問題。但往往需要傳遞參數(shù)或者回調(diào)時,都得用到。例如:
<button onClick={this.handleClick.bind(this, id)} />
<button onClick={() => this.handleClick(id)} />
【5】智能組件 vs 木偶組件 ?(容器組件 vs 展示組件)
Smart 組件 和 Dumb 組件對于開發(fā)過 react 項目的朋友來說應(yīng)該不陌生了。
Dumb 組件,聽名字你就知道這種組件很傻很木,因為木偶組件只關(guān)心一件事情就是 —— 根據(jù) props 進行渲染。
而 Smart 組件就很聰明,它專門做數(shù)據(jù)相關(guān)的邏輯,和各路數(shù)據(jù)打交道,ajax獲取數(shù)據(jù),定義好數(shù)據(jù)操作的相關(guān)函數(shù),然后將這些數(shù)據(jù)、函數(shù)直接傳遞給具體實現(xiàn)的組件(Dumb 組件)即可。所以根據(jù)是否需要高度的復(fù)用性,把組件劃分為 Dumb 和 Smart 組件。
小提示1:Smart 組件復(fù)用性不強甚至不需要復(fù)用,Dumb 組件往往是復(fù)用性強的,但是Dumb 組件對于 Smart 組件的帶入性不要太強,因為帶入太多邏輯會導(dǎo)致復(fù)用性降低。二者選擇,設(shè)計組件時需要斟酌一下。
小提示2:Dumb 組件 的子組件也應(yīng)該是 Dumb 組件。
小提示3:redux store 相關(guān)的應(yīng)該和 Smart 組件連接起來 。
關(guān)于React生命周期
關(guān)于生命周期,面試的時候總喜歡問點兒react生命周期相關(guān)的,而且想要了解別人寫的 react 代碼,深刻理解 react 生命周期也是很重要的。先不要往下看,閉上眼睛想想看你所了解的 react 生命周期有哪些?
...
...
...
...
...
...

ok 你應(yīng)該想完了哈。是不是大概有下面這么一個流程?(忽略圖片的渣像素??圖片是經(jīng)典的組件掛載圖來源于網(wǎng)絡(luò))
react 組件的生命周期方法都可以被分割成四個階段:初始化、掛載階段(Mounting)、更新階段(Updating)、卸載階段(Unmounting)。
接下來就讓我們?nèi)タ纯瓷芷诙加心男┬≈R點。
【6】Mounting -- 下面這些方法將會在 component 實例被創(chuàng)建和插入到DOM后調(diào)用。
- constructor()
- componentWillMount()
- render()
- componentDidMount()
【7】Updating -- props 或者 state 的變化都會導(dǎo)致更新。下面這些方法會在 component 重新渲染時調(diào)用。
- componentWillReceiveProps()
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
【8】Unmounting -- 該方法將會在 component 從DOM中移除時調(diào)用。
- componentWillUnmount()
接下來簡單的介紹一下幾個生命周期。
【9】1. componentWillMount
componentWillMount() 是在組件掛載(mount)之前被調(diào)用.
componentWillMount()是唯一一個在服務(wù)器端渲染(ssr)調(diào)用的生命周期鉤子
關(guān)于 setState 在 componentWillMount 使用:可以使用。因為它是 render 方法之前被調(diào)用,因此 setState 也不會導(dǎo)致重繪(re-render)
【10】2. componentDidMount
componentDidMount() 在組件掛載之后立即執(zhí)行
在這個鉤子里合適:
- ajax 請求
- 初始化DOM節(jié)點的操作
- 設(shè)置計時器 setTimeout 或者 setInterval (溫馨提示,別忘了在 componentWillUnmount 關(guān)閉這些計時器)
關(guān)于 setState 在 componentDidMount 使用: 可以使用。但是經(jīng)常導(dǎo)致性能問題。當(dāng)然非要在 render 前拿到 DOM 節(jié)點的大小和位置,是可以用的。
插曲。面試題:ajax 請求應(yīng)該在哪個生命周期?為什么?
【11】3. componentWillReceiveProps(nextProps)
componentWillReceiveProps 將會在已掛載組件(mounted component)接收到新的 props 之前調(diào)用。所以初始化 props 的mount是不會觸發(fā)這個函數(shù)。直接 setState 也不會觸發(fā)這個函數(shù)。
在這個鉤子里合適:
- 更新 state 的值(比如重置)
- 比較 this.props 和 nextProps
特別特別特別要注意的是,當(dāng)父組件導(dǎo)致該組件 re-render 時,即便 props 沒有發(fā)生任何的改變,react 也有可能執(zhí)行該鉤子函數(shù)。所以呢,所以就是如果你想要真正處理 props 的變化,要記得比較當(dāng)前 props 和 nextProps.
關(guān)于setState在componentWillReceiveProps使用: 可以使用。
【12】4. shouldComponentUpdate(nextProps, nextState)
當(dāng)改變state 或者 props 并且是在render之前會調(diào)用shouldComponentUpdate,說白了就是該鉤子函數(shù)用于告訴 React 組件是否需要重新渲染。
shouldComponentUpdate 默認(rèn)return true,如果return false componentWillUpdate、render、componentDidUpdate都將不會被調(diào)用。千萬記住一點, 當(dāng)return false時,當(dāng)他們的 state 發(fā)生改變時,并不會阻止子組件(child component)進行重新渲染。
shouldComponentUpdate在兩種情況下不會被調(diào)用:
- 組件初始化
- 使用forceUpdate的情況
大家應(yīng)該都是 shouldComponentUpdate 還有一個知識點就是和 react 組件性能優(yōu)化相關(guān)的。是的。你可以this.state 和 nextState、this.props 和 nextProps 做比較來決定出 return false 并告訴 react 可以不更新該組件。如果做的只是一些淺層的數(shù)據(jù)比較完全可以用 PureComponent 來代替(深層的嵌套數(shù)據(jù)PureComponent也無能為力)
react 不建議在 shouldComponentUpdate 做深層的對比或者用 JSON.stringify(),因為這樣反而損害到性能。
【13】5. componentWillUpdate(nextProps, nextState)
state 或者 props 更新后 re-render 之前調(diào)用。
注意:不要在componentWillUpdate 使用 this.setState, 或者 redux 分發(fā)一個action(dispatch a Redux action),因為在 componentWillUpdate 之前會觸發(fā)組件的更新。 如果非要在做以上操作的話,可以在componentWillReceiveProps 哦
【14】6. componentDidUpdate(prevProps, prevState)
在組件更新之后馬上調(diào)用 componentDidUpdate。
在這個鉤子函數(shù)中你可以:
- 操作 DOM
- 發(fā)起網(wǎng)絡(luò)請求
【15】7. componentWillUnmount
在組件卸載(unmounted)和銷毀(destroyed)前調(diào)用。
在componentWillUnmount你可以執(zhí)行任何需要清除的方法。比如:
- 清除計時器
- 斷開網(wǎng)絡(luò)請求
- 解綁dom事件
- 等等
【16】生命周期table
| 生命周期 | 是否可以調(diào)用this.setState | 初始化是否執(zhí)行 |
|---|---|---|
| componentWillMount | 可以 | 是 |
| componentDidMount | 可以 | 是 |
| componentWillReceiveProps | 可以 | 否 |
| shouldComponentUpdate | 不可以 | 否 |
| componentWillUpdate | 不可以 | 否 |
| componentDidUpdate | 可以 | 否 |
| componentWillUnmount | 不可以 | 否 |
特別特別特別注意:
①componentWillMount 和 componentWillReceiveProps 調(diào)用 setState 不會重復(fù)渲染(re-render)
②componentDidUpdate,不能直接 this.setState, 不然會溢出棧。需要對 prevProps 與 this.props 和 prevState 和 this.state 做一個判斷再執(zhí)行 this.setState。就類似while循環(huán)不能陷入死循環(huán)。
好吧。長篇大論了一番 react 的生命周期。不為什么,就因為它非常的重要。不管是對你的面試或者日常開發(fā)或者閱讀理解別人的代碼都是非常重要。哪個階段會觸發(fā)哪個生命周期,哪個能干什么哪個不能干什么,哪個更合適哪個不合適。來!干了它,咱們再繼續(xù)往下看!
......
.....
....
...
..
.
感謝你能看到這里,咱們繼續(xù)往下鑿....
.
..
...
....
.....
......
【17】props 和 state 的區(qū)別
- "props"是別人的, props實現(xiàn)組件間的狀態(tài)傳遞,props從父組件到子組建的數(shù)據(jù)傳遞;"state"是自己的,state只能定義在組件內(nèi)部,定義組件的自己的狀態(tài)。
- props 是不可變的; state 可以通過this.setState改變
【18】props vs state
| ? | props | state |
|---|---|---|
| 可以從父組件獲得初始值嗎? | Yes | Yes |
| 可以被父組件改變嗎? | Yes | No |
| 內(nèi)部(當(dāng)前)組件可以設(shè)置默認(rèn)值嗎? | Yes | Yes |
| 可以改變內(nèi)部(當(dāng)前)組件嗎? | No | Yes |
| 可以為子組件設(shè)置初始值嗎? | Yes | Yes |
| 可以改變子組件嗎? | Yes | No |
【19】jsx是什么?
剛接觸 react 的童鞋,看到 jsx,第一反應(yīng)就是“丑”。說實在的一開始寫 jsx 我也是拒絕的,但是沒想到 jsx 其語法和背后的邏輯讓構(gòu)建react組件變得極其簡單。
那 jsx 到底是什么呢?jsx 是一個看起來很像 XML 的 JavaScript 語法擴展。說白了 jsx 并不是什么高深的技術(shù),可以說只是一個比較高級但很直觀的語法糖。它非常有用,卻不是一個必需品,沒有 jsx 的 React 也可以正常工作:只要你樂意用 JavaScript 代碼去創(chuàng)建這些虛擬 DOM 元素(但是真的超級麻煩)。
jsx優(yōu)點:
- 執(zhí)行更快,因為它在編譯為 JavaScript 代碼后進行了優(yōu)化
- 它是類型安全的,在編譯過程中就能發(fā)現(xiàn)錯誤
- 編寫模板更加簡單快速
- 更加直觀,可讀性高
來看看以下代碼:
1.當(dāng)我們用HTML描述一個按鈕的時候,你會發(fā)現(xiàn)一個 DOM 元素包含的信息其實只有三個:標(biāo)簽名,屬性,子元素。
<div id="btn-wrap">
<button class="btn">click</button>
</div>
2.我們?nèi)绻胘s描述,可以通過JSON對象,且依然包括元素的標(biāo)簽名、屬性,子元素
{
type: 'div',
props: { id: 'btn-wrap' },
children: {
type: 'button',
props: { className: 'btn' },
children: 'click'
}
}
仔細(xì)觀察,你會發(fā)現(xiàn)HTML和js描述一個按鈕他們所對應(yīng)的結(jié)構(gòu)簡直是一毛一樣的,就是說一個html構(gòu)建的UI界面我們完全可以用js來描述。你會發(fā)現(xiàn)HTML書寫一個按鈕遠(yuǎn)比js書寫方式來得蘇胡,而且結(jié)構(gòu)更加清晰。但是如果 你堅決要用js來寫我也不會反對的。來!先寫個div十層嵌套試試?
react提供jsx語法糖,將html語法直接加入到JavaScript代碼中去,再通過編譯器(babel)轉(zhuǎn)化為JavaScript后由瀏覽器執(zhí)行。
我們修改src/index.js的代碼如下
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class HappyReact extends Component {
render() {
return (
<div>
<h1 id="title">happy react !</h1>
</div>
)
}
}
ReactDOM.render(<HappyReact />, document.getElementById('root'));
這時候你會看到頁面瀏覽器自動刷新了且頁面顯示了'happy react !'字樣。
如果以上代碼經(jīng)過編譯會變成:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class HappyReact extends Component {
render() {
return (
React.createElement(
'div',
null,
React.createElement(
'h1',
{ id: 'title' },
'happy react !'
)
)
)
}
}
ReactDOM.render(
React.createElement(
HappyReact,
null
),
document.getElementById('root')
);
編譯前后兩段代碼渲染結(jié)果是一樣的,你一定發(fā)現(xiàn)了jsx的代碼更加直觀便于維護了吧!
雖然你看到的html寫在了js代碼中,但是你永遠(yuǎn)要記住"jsx最終其實就是JavaScript對象"。react通這個對象來創(chuàng)建或者更新虛擬元素最終來管理virtual DOM(虛擬DOM)
jsx對象元素可以理解為和真實元素一一對應(yīng)的,它的創(chuàng)建、更新、刪除都是在內(nèi)存中完成的。并不會直接渲染到真實DOM中去,整個react應(yīng)用程序唯一操作到DOM就是:
ReactDOM.render(<HappyReact />, document.getElementById('root'));
【20】大概知道jsx是什么了。我們是得花點兒時間學(xué)習(xí)/了解/回憶一下jsx的寫法。
- render函數(shù)只能return一個根節(jié)點,只允許被一個標(biāo)簽包裹
- Component 命名首字大寫,HTML 標(biāo)簽用小寫
- 如果不存在子節(jié)點,可以使用自閉合 <div />
- jsx的注釋 {/* */}
- JavaScript 屬性表達式,屬性值用 {}
- 三元表達式
- 數(shù)組遞歸(渲染列表) map
- 兩個特殊屬性 class, for. 因為class, for在JavaScript中這個兩個單詞都是關(guān)鍵詞。因此需要做一手轉(zhuǎn)換。其他屬性可以像寫html一樣添加上去。
- jsx書寫樣式
- 事件處理,使用inline方式的駝峰式寫法,例如onClick、onChange
- HTML轉(zhuǎn)義 --> dangerouslySetInnerHTML={{__html: '<div>hhh</div>'}}
- 利用es6 中 ... 展開運算符。例如
const helloProps = {
value: 'hello',
show: true,
}
<HelloWorld ...helloProps />
- 如果屬性值是true 這里直接寫屬性名。例如
<Button disabled={true} />
可以寫成
<Button disabled />
- false, null, undefined, true 是有效的子內(nèi)容,但是不會被渲染出來。以下表達式渲染結(jié)果是一樣的:
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>
15 ...
...好吧我可能暫時想到這么多了。
【21】refs 是什么?(refs功能,如何實現(xiàn)?)
react 提供了一種特殊屬性, 允許您直接訪問DOM元素或組件實例。
ref 可以返回一個字符串(string) 或者 一個回調(diào)函數(shù)(cb),這個回調(diào)函數(shù)會在組件實例化或者銷毀之后立即執(zhí)行。
字符串refs在未來的版本中可能被移除,所以推薦回調(diào)的方式來代替。
class TextInput extends Component {
componentDidMount() {
this.textInput.focus()
}
render() {
return (
<input ref={(input) => this.textInpuf = input} />
)
}
}
官方推薦幾種很好的方式使用refs:
- 管理焦點,文本選擇或者媒體播放
- 觸發(fā)重要的動畫
- 整合第三方DOM庫
當(dāng)然能不用refs就盡量不要用refs,不要太過度依賴refs來解決問題。
【22】什么是受控組件和什么是非受控組件
在react表單組件可被分為兩類:受控組件 和 非受控組件。
-
受控組件
我們簡單的理解,設(shè)置了 value 的 <input>(表單標(biāo)簽) 是一個受控組件。
當(dāng)我們設(shè)置了value為"hi"(某個值)時,且在頁面上渲染出改值時,我們在渲染出來的元素里輸入任何值都不起作用。因為react 已經(jīng)把value賦值為"hi"。 要想改變value值,還必須配合這onChange 和 setState 來實現(xiàn)。
當(dāng)然你也可以看看官網(wǎng)文檔來如何定義受控組件的。
在 HTML 中,表單元素如 <input>,<textarea> 和 <select> 表單元素通常保持自己的狀態(tài),并根據(jù)用戶輸入進行更新。而在 React 中,可變狀態(tài)一般保存在組件的 state(狀態(tài)) 屬性中,并且只能通過 setState() 更新。
我們可以通過使 React 的 state 成為 “單一數(shù)據(jù)源原則” 來結(jié)合這兩個形式。然后渲染表單的 React 組件也可以控制在用戶輸入之后的行為。這種形式,其值由 React 控制的輸入表單元素稱為“受控組件”。
話不多說,來呀,上代碼:
class App extends Component {
constructor(props) {
super(props)
this.state = { value: 'hi' }
}
onInputChange = (e) => {
this.setState({ value: e.target.value })
}
render() {
const { value } = this.state
return (
<input value={value} onChange={this.onInputChange} />
)
}
}
React官方推薦使用受控表單組件。總結(jié)一下上面受控組件代碼更新是state的流程:
- 初始化state設(shè)置表單的默認(rèn)值,例如 this.state = { value: 'hi' }
- 每當(dāng)表單值發(fā)生變化時,調(diào)用onChange事件
- 通過對象e拿到改變的狀態(tài),例如e.target.value
- 通過setState更新應(yīng)用value 并 觸發(fā)視圖重新渲染,最終完成表單組件的更新
// 第四步 setState 我們還可以對表單值進行直接修改或者驗證
// 受控組件支持即時字段驗證,允許您有條件地禁用/啟用按鈕,強制輸入格式
onInputChange = (e) => {
this.setState({ value: e.target.value.substring(0, 140).toUpperCase() })
}
特別特的注意?。。?! 如果 value 為 undefined,則變成了非受控組件。
-
非受控組件
理解了受控組件,那你一定知道非受控組件就是沒有value(單選/復(fù)選按鈕為 checked)屬性的表單組件??梢酝ㄟ^設(shè)置 defalutValue / defalutChecked 來設(shè)置組件初始值。
多啰嗦一句,defalutValue / defalutChecked,僅僅會被渲染一次,在后續(xù)渲染并不起作用。
因為不受state / props控制,我們需要為其添加 ref 來訪問渲染后的DOM元素,才能最終拿到改變后的value/checked。還記得refs那句話怎么說來著:“能不用refs就盡量不要用refs”。so react官方還是比較推薦使用受控組件。
......
.....
....
...
..
.
看累了,我們留一點兒明天再來嘛。。。
.
..
...
....
.....
......
關(guān)于setState
setState 對于每一個使用過react的盆友來說應(yīng)該并不陌生。與之還能立刻聯(lián)想出來幾個詞 “更改state” “異步” “重新渲染”...
來一個道題練練手?雖然平時不會寫這么*的代碼,但誰知道面試會不會出現(xiàn)呢? 歡迎寫下你的答案!
...
this.state = { count : 0 }
...
componentDidMount() {
this.setState({ count: this.state.count + 1 }, () => {
console.log(`apple...${this.state.count}`)
})
console.log(`orange...${this.state.count}`)
setTimeout(() => {
console.log(`lemen...${this.state.count}`)
this.setState({ count: this.state.count + 1 }, () => {
console.log(`banana...${this.state.count}`)
})
setTimeout(() => {
console.log(`grape...${this.state.count}`)
}, 0)
this.setState({ count: this.state.count + 1 }, () => {
console.log(`strawberry...${this.state.count}`)
})
console.log(`pear...${this.state.count}`)
}, 0)
}
【23】官方是這么定義setState
setState() 排隊更改組件的 state ,并通過更新 state 來告訴 React ,該組件及其子組件需要重新渲染。這是用于 響應(yīng)事件處理程序 和 服務(wù)器響應(yīng) 更新用戶界面的主要方法。
我記得我剛學(xué)習(xí)react的時候,文檔上還沒有明確說調(diào)用setState是異步的,只是說了“不保證是同步的”。但最近去看了官方文檔,文檔說調(diào)用setState是異步的了。
【24】調(diào)用setState()實際上發(fā)生了什么?
簡單的說,就是 更改state、更新UI
復(fù)雜的說,就是 怎么合并新的state,怎么根據(jù)新state來更新UI
【25】setState()第二個參數(shù)是什么?它有什么作用?
setState的第二個參數(shù)是一個可選的回調(diào)函數(shù)。這個回調(diào)函數(shù)將在 setState 完成后執(zhí)行,并且重新渲染組件。在這個回調(diào)函數(shù)中你可以拿到剛更新的state的值。但是這樣的邏輯 官方推薦 使用 componentDidUpdate。
【26】如何在 setState 后直接獲取修改后的值
- setState 第二個參數(shù),回調(diào)函數(shù)中獲取
- 使用setTimeout
setTimeout(() => {
this.setState({ value: 'hhh' })
console.log(this.state.value) // hhh
}, 0)
// 看到這里最開始的那道練手題,是不是已經(jīng)可以迎刃而解了。哈哈哈哈哈
【27】setState 第一個參數(shù)有兩種傳遞方式 1.一個對象 2. 一個函數(shù) 這兩種寫法有什么區(qū)別呢?
舉個例子
...
this.state = { text : '這是一個栗子' }
...
// 使用傳遞對象的寫法
handleClick = () => {
this.setState({ text: this.state.text + '111' })
this.setState({ text: this.state.text + '222' })
}
// 使用傳遞函數(shù)的寫法
handleClick = () => {
this.setState((prevState) => {
return { text: prevState.text + '111' }
})
this.setState((prevState) => {
return { text: prevState.text + '222' }
})
}
render() {
return <div onClick={this.handleClick}>{this.state.text}</div>
}
兩種傳遞方式,得到的結(jié)果是不一樣的。
- 傳遞對象 => this.state.text => '這是一個栗子222'
- 傳遞函數(shù) => this.state.text => '這是一個栗子111222'
setState為了提升性能,在批量執(zhí)行 state 改變在做統(tǒng)一的DOM渲染。而在這個批量執(zhí)行的過程中,如果你多次傳遞的是一堆對象,它就會做一些對象合并或者組合的操作,例如Object.assign({}, { a: '111' }, { a: '222' })。如果key值一樣的話,后面的值會覆蓋掉前面的值。
但多次傳遞函數(shù)方式,每次 React 從 setState 執(zhí)行函數(shù),并通過傳遞已更新的狀態(tài)來更新你的狀態(tài)。這使得功能 setState 可以基于先前狀態(tài)設(shè)置狀態(tài)。
使用setState要注意?。?!
- setState可能會引發(fā)不必要的渲染 (shouldComponentUpdate/PureComponent)
- setState無法完全掌控應(yīng)用中所有組件的狀態(tài)(Redux/Mbox)
【28】 什么是高階組件,它是如何使用?
高階組件它是一個函數(shù)。高階組件它是一個函數(shù)。高階組件它是一個函數(shù)。并不是一個組件。通俗的講就是它接收一個React組件作為輸入,輸出一個新的增強版的React組件。
舉一個可能不太恰當(dāng)?shù)睦?,大家可能都玩王者農(nóng)藥,打藍(lán)爸爸或者紅爸爸就是對英雄自身的一個增強版。吃了藍(lán)爸爸并不會影響你吃紅爸爸,也不會影響你買了什么裝備等等。
好了,那么我們定義一個最最最簡單的高階組件
const MyContainer = (WrappedComponent) => {
return class NewComponent extend Component {
render() {
return <WrappedComponent />
}
}
}
將你的組件類作為參數(shù)傳入高階組件這個函數(shù)即可
class Welcome extends Component {
...
}
export default MyContainer(Welcome)
或者使用ES7的裝飾器
@MyContainer
class Welcome extends Component {
...
}
export default Welcome
關(guān)于裝飾器在create-react-app中的配置:
- npm run eject
- npm install --save-dev plugin-transform-decorators-legacy
- 在package.json中找到"babel"項,添加 "plugins": ["transform-decorators-legacy"]
在代碼優(yōu)化(抽離公共邏輯)或者組件解耦的時候我們可以考慮一下使用高階組件,這樣有助于提高我們代碼的靈活性,邏輯的復(fù)用性。
【29】什么是PureComponent? 介紹一下PureComponent和shouldComponentUpdate有什么區(qū)別?
PureComponent 和 Component是相同,只要把繼承類從 Component 換成 PureComponent 即可。PureComponent改變了shouldComponentUpdate,它會自動檢查組件是否重新渲染。也就是說,只有當(dāng)PureComponent檢查到props或者state變化時,才會調(diào)用render函數(shù),因此不用寫額外的檢查。還可以減少 Virtual DOM 的生成和比對過程,達到提升性能的目的。
注意:PureComponent 的 shouldComponentUpdate 只是進行了淺比較(state,props對象結(jié)構(gòu)簡單,可以理解為對象只有一層),對于復(fù)雜且嵌套更深層數(shù)據(jù)的比較會出現(xiàn)偏差。對于深比較,你可以選擇在 shouldComponentUpdate 進行深比較檢查來確定組件是否渲染,但是你要知道 深比較 是非常昂貴的。 當(dāng)然,你可能知道 使用 Immutable 來幫助嵌套數(shù)據(jù)的快速比較。
【30】shouldComponentUpdate 的作用以及它的重要性?
shouldComponentUpdate 允許我們手動地判斷是否要進行組件更新,根據(jù)組件的應(yīng)用場景設(shè)置函數(shù)的合理返回值能夠幫我們避免不必要的更新。
【31】為什么我們利用循環(huán)產(chǎn)生的組件中要用上key這個特殊的prop?
// list = [{ id: 0, name: 'xiaoming', age: 18 }, { id: 1, name: 'xiaohong', age: 16 }]
render() {
return (
<ul>
list.map((item, index) => {
return <li key={item.id}>{item.name} - {item.age}</li>
})
</ul>
)
}
如果你沒添加上 key 屬性的話,會報一個警告: Warning: Each child in an array or iterator should have a unique "key" prop...
keys 是 React 用于追蹤哪些列表中元素被修改、被添加或者被移除的輔助標(biāo)識
之所以需要key,因為react 是非常高效的,它會借助元素的 key 值來判斷該元素是新創(chuàng)建的,或者移動(交換位置)而來的,從而減少不必要的元素重渲染。更直觀一點兒就是 react 很懶,能復(fù)用的元素就復(fù)用,他不想重新創(chuàng)建新的元素。
那么,如果上面代碼 key={index} 呢?你會發(fā)現(xiàn)也不會有warning,但是這樣做的效率是非常非常非常低的。
看看以下例子:
// list = [a, b, c, d]
<div>
list.map((item, index) => <div key={index}>{item}</div>)
</div>
渲染完成后我們abcd 分別對應(yīng)的是 0123。
a -> 0
b -> 1
c -> 2
d -> 3
假設(shè)我們只是將d的位置換到了首位 list = [d, a, b, c]
a -> 1
b -> 2
c -> 3
d -> 0
變換前和變換后,你應(yīng)該發(fā)現(xiàn)了abcd所對應(yīng)的key都改變了,這樣react Virtual DOM就不論有沒有相同的項,更新都會重新渲染了。所以我們要保證某個元素的 key 在其同級元素中具有唯一性,這個key 的值可以直接后臺數(shù)據(jù)返回的 id,因為后臺的 id 都是唯一的。
記住實際開發(fā)中,就別再直接用循環(huán)計數(shù)器 index 了,那就有點兒騙自己了哈。剛用react我也老用index...
react組件間的通信
組件之間的通信也是老生常談了。不僅在實際開發(fā)中,面試時估計也經(jīng)常被提及。
組件之間的通信大概可分為這么幾種:
- 父組件向子組件通信
- 子組件向父組件通信
- 兄弟組件之間通信
【32】父組件向子組件通信
在 react 中數(shù)據(jù)是單向傳遞的,父組件可以向子組件通過傳 props 的方式,子組件拿到 props 之后做相應(yīng)的處理,這就是父組件向子組件進行通信方式。
class Parent extends Component {
constructor(props) {
super(props)
this.state = { wishes: '2018新年快樂!' }
}
render() {
return (
<Child title={this.state.wishes} />
)
}
}
class Child extends Component {
render() {
return (
<h3>{this.props.title}</h3>
)
}
}
【33】子組件向父組件通信
子組件向父組件傳遞數(shù)據(jù)(通信) 也是要通過 props 傳遞一個函數(shù),子組件調(diào)用這個函數(shù),并將子組件需要傳遞的數(shù)據(jù)作為參數(shù),傳遞給父組件。
class Parent extends Component {
constructor(props) {
super(props)
this.state = { wishes: '2018新年快樂!' }
}
onSend = (msg) => {
this.setState({ wishes: msg })
}
render() {
return (
<Child onSend={this.onSend} title={this.state.wishes} />
)
}
}
class Child extends Component {
onChildSend = () => {
this.props.onSend('謝謝你的祝福!')
}
render() {
return (
<h3 onClick={this.onChildSend}>{this.props.title}</h3>
)
}
}
【34】兄弟組件之間通信
兩個兄弟組件之間的數(shù)據(jù)傳遞,我們可以通過他們的共同父組件來實現(xiàn)。Child1 將要傳遞的信息傳遞給 Parent 然后 Parent 再將從 Child1 拿到的信息傳遞給 Child2 當(dāng)然,我們同樣是利用 props。
我們來寫一段點擊 Child1,然后將 Child1 想傳遞給 Child2 的信息發(fā)送到 Child2 中。
class Parent extends Component {
constructor(props) {
super(props)
this.state = { wishes: '' }
}
onSend = (msg) => {
this.setState({ wishes: msg })
}
render() {
return (
<div>
<Child1 onSend={this.onSend} />
<Child2 fromChild1Wishes={this.state.wishes} />
</div>
)
}
}
class Child1 extends Component {
onChild1Send = () => {
this.props.onSend('嗨,老二新年快樂!')
}
render() {
return (
<h3 onClick={this.onChild1Send}>我是老大Child1</h3>
)
}
}
class Child2 extends Component {
onChild1Send = () => {
this.props.onSend('嗨,老二新年快樂!')
}
render() {
return (
<div>
<h3>我是老二Child2</h3>
{
this.props.fromChild1Wishes ?
<p>來自老大的祝福 - this.props.fromChild1Wishes</p>
: null
}
</div>
)
}
}
【35】組件通信小總結(jié)
以上三種方式是最常見到的。但是實際項目中往往比這種通信更復(fù)雜得多。因為復(fù)雜項目的組件嵌套往往就像一顆枝繁葉茂的樹一樣。
比如:
1、跨n級組件之間通信,就是 Parent 組件和它子組件的子組件通信,或者子組件的子組件的子組件通信....
2、非嵌套組件的通信,剛剛說的兄弟組件是最簡單非嵌套,還有更多不是同一父組件的非兄弟組件的嵌套。說得繞一點兒,你和你爺爺?shù)牡艿艿膶O子/兒子通信就是屬于這種情況。
以上的解決方案肯定是有的。
- 你不嫌麻煩一層一層傳遞 props (三層以上就不推薦)
- 利用 react 提供的 context , 它類似一個全局大容器,我們把想傳遞的信息放在里面,需要的往里面取便是。
- 自定義事件的方式。自定義事件是典型的發(fā)布/訂閱模式,通過向事件對象上添加監(jiān)聽器和觸發(fā)事件來實現(xiàn)組件間通信。
- 狀態(tài)管理工具 mobx redux 等
多嘮叨一句,所有通信方式肯定都可以用在任何項目下。但,就像女朋友一樣,最適合的才是最好的。
【36】ajax 應(yīng)該在哪個生命周期調(diào)用呢?why
既然有人問了這個問題,看來這個問題還有有很多討論的空間。
對于 ajax 應(yīng)該是在哪個生命周期調(diào)用呢? 備受爭議應(yīng)該就是在 componentDidmount 和 componentWillmount 這兩個生命周期之間了。網(wǎng)路上也眾說紛紜。看過官網(wǎng)文檔的小伙伴們應(yīng)該也是知道 官網(wǎng)說的是 應(yīng)該在 componentDidmount 。 然鵝。官網(wǎng)并沒有告訴我們 why ?
不少開發(fā)過 react 項目的同學(xué)應(yīng)該也分別嘗試過在 componentDidmount 和 componentWillmount 都做過 ajax 的請求,好像沒啥問題吧?好像都可以成功吧? 但是到底哪一個更合適呢?
咱們先來看點兒代碼熱熱場子......
代碼一:
componentWillMount() {
console.log(1)
this.setState({ isLoading: true })
}
render() {
console.log(2)
return <div>test</div>
}
代碼二:
componentWillMount() {
console.log(1)
setTimeout(() => {
this.setState({ isLoading: true })
}, 0)
}
render() {
console.log(2)
return <div>test</div>
}
代碼三:
componentDidMount() {
console.log(1)
this.setState({ isLoading: true })
}
render() {
console.log(2)
return <div>test</div>
}
代碼四:
componentDidMount() {
console.log(1)
setTimeout(() => {
this.setState({ isLoading: true })
}, 0)
}
render() {
console.log(2)
return <div>test</div>
}
現(xiàn)在你可以告訴我代碼1, 2, 3, 4分別輸出的是什么?
代碼一: 1, 2
代碼二: 1, 2, 2
代碼三: 2, 1, 2
代碼四: 2, 1, 2
很多盆友都知道 this.setState 在 componentWillMount 中并不會觸發(fā) re-render。 但是如果在 setState 在一個異步方法下結(jié)果可能就不一樣了。 你知道的,我們實際上獲取數(shù)據(jù)都是異步的,所以并不會阻礙組件渲染。而且我們往往都會在 ajax 請求成功后再 setState 來更新狀態(tài)。此時的 setState 會放入隊列中,等待組件掛載完成后,再更新組件。例如 將 setState 放入 setTimeout 或者 請求成功后的 fetch 或者 axios 中都是這種情況。
所以代碼二實際上就模擬了一次 在 componentWillMount 發(fā)送 ajax 請求。它的執(zhí)行效果或者說效率從上面代碼看上來和代碼四是一樣的(在 componentDidMount 發(fā)送 ajax)。所以 componentWillMount 和 componentDidMount 請求其實都是可以的!
但是?。?!為什么官網(wǎng)沒這么說呢?文檔只推薦了 componentDidMount 。
React 下一代調(diào)和算法 Fiber 會通過開始或停止渲染的方式優(yōu)化應(yīng)用性能,其會影響到 componentWillMount 的觸發(fā)次數(shù)。對于 componentWillMount 調(diào)用次數(shù)變得不可確定。 react 可能會多次頻繁調(diào)用 componentWillMount 。ajax 放入這個生命周期顯然不是最好的選擇。
所以呢。我還是比較推薦在 componentDidMount 中調(diào)用ajax 。
更多...
當(dāng)然面試中可能還會有更深層次更開發(fā)性的問題。
- 如果你能夠改進React的一樣功能,那會是哪一個功能?(react 的缺點)
- immutable.js 原理是什么? Immutable 詳解及 React 中實踐
- react 性能優(yōu)化有哪些?
- react diff算法
- react 虛擬dom原理
- react 是什么
- react和vue的區(qū)別
- ...
對于react技術(shù)棧 react-router、redux 當(dāng)然也有很多。
- redux react-redux 分別負(fù)責(zé)哪些功能
- provider connect的用法
- store數(shù)據(jù)流向
- redux的三個原則
- ...
Reference
https://reactjs.org/
https://github.com/chemdemo/chemdemo.github.io/issues/14
http://www.infoq.com/cn/articles/react-jsx-and-component
https://segmentfault.com/a/1190000009001924
http://www.oschina.net/translate/functional-setstate-is-the-future-of-react
https://segmentfault.com/a/1190000007454080
http://www.itdecent.cn/p/fb915d9c99c4