關(guān)于 react 那些小知識點兒

react.jpg
說在前面

關(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)點
  1. 語法/代碼更加簡潔
  2. 占用內(nèi)存?。]有 props 等屬性), 首次 render 性能更好
  3. 不需要管理/維護組件的生命周期
  4. 純函數(shù),相同的 props 會得到同樣的 UI 渲染結(jié)果
  5. 單元測試更容易進行。因為邏輯都被移出了 view 層,所以單元測試時不需要渲染任何東西,可以專注于單個邏輯。
  • 缺點
  1. 無生命周期函數(shù)。對于一個函數(shù)而言應(yīng)該是談不上生命周期。當(dāng)然了,我們其實可以使用高階組件去實現(xiàn)生命周期。
  2. 沒有 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 生命周期有哪些?

...

...

...

...

...

...

liftcycle.jpg

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、rendercomponentDidUpdate都將不會被調(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ū)別
  1. "props"是別人的, props實現(xiàn)組件間的狀態(tài)傳遞,props從父組件到子組建的數(shù)據(jù)傳遞;"state"是自己的,state只能定義在組件內(nèi)部,定義組件的自己的狀態(tài)。
  2. 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的寫法。
  1. render函數(shù)只能return一個根節(jié)點,只允許被一個標(biāo)簽包裹
  2. Component 命名首字大寫,HTML 標(biāo)簽用小寫
  3. 如果不存在子節(jié)點,可以使用自閉合 <div />
  4. jsx的注釋 {/* */}
  5. JavaScript 屬性表達式,屬性值用 {}
  6. 三元表達式
  7. 數(shù)組遞歸(渲染列表) map
  8. 兩個特殊屬性 class, for. 因為class, for在JavaScript中這個兩個單詞都是關(guān)鍵詞。因此需要做一手轉(zhuǎn)換。其他屬性可以像寫html一樣添加上去。
  9. jsx書寫樣式
  10. 事件處理,使用inline方式的駝峰式寫法,例如onClick、onChange
  11. HTML轉(zhuǎn)義 --> dangerouslySetInnerHTML={{__html: '<div>hhh</div>'}}
  12. 利用es6 中 ... 展開運算符。例如
    const helloProps = {
        value: 'hello',
        show: true,
    }
 <HelloWorld ...helloProps />
  1. 如果屬性值是true 這里直接寫屬性名。例如
    <Button disabled={true} /> 
    可以寫成
    <Button disabled /> 
  1. 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的流程:

  1. 初始化state設(shè)置表單的默認(rèn)值,例如 this.state = { value: 'hi' }
  2. 每當(dāng)表單值發(fā)生變化時,調(diào)用onChange事件
  3. 通過對象e拿到改變的狀態(tài),例如e.target.value
  4. 通過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 后直接獲取修改后的值
  1. setState 第二個參數(shù),回調(diào)函數(shù)中獲取
  2. 使用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要注意?。?!

  1. setState可能會引發(fā)不必要的渲染 (shouldComponentUpdate/PureComponent)
  2. 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中的配置:

  1. npm run eject
  2. npm install --save-dev plugin-transform-decorators-legacy
  3. 在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)常被提及。

組件之間的通信大概可分為這么幾種:

  1. 父組件向子組件通信
  2. 子組件向父組件通信
  3. 兄弟組件之間通信
【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子/兒子通信就是屬于這種情況。

以上的解決方案肯定是有的。

  1. 你不嫌麻煩一層一層傳遞 props (三層以上就不推薦)
  2. 利用 react 提供的 context , 它類似一個全局大容器,我們把想傳遞的信息放在里面,需要的往里面取便是。
  3. 自定義事件的方式。自定義事件是典型的發(fā)布/訂閱模式,通過向事件對象上添加監(jiān)聽器和觸發(fā)事件來實現(xiàn)組件間通信。
  4. 狀態(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容