關(guān)于 react 那些小知識(shí)點(diǎn)兒

react.jpg
說在前面

關(guān)于 react 的總結(jié)過去半年就一直碎碎念著要搞起來,各(wo)種(tai)原(lan)因(le)。心心念的東西終于要重新拿了起來了。希望這個(gè)總結(jié)歸納能對(duì)你的日常開發(fā)或者跳槽面試有幫助哪怕只有那么一點(diǎn)點(diǎn),反正對(duì)我?guī)椭峭Υ螅瑴毓识侣铮?/p>

廢話說了一堆, 這個(gè)總結(jié)可能大概也許會(huì)以問答的形式總結(jié)
希望你能各個(gè)擊破,像闖關(guān)卡一樣一個(gè)一個(gè)過!開始吧 !Let's go!

【1】react component有幾種寫法?分別是什么?

① 函數(shù)式定義的無狀態(tài)組件(Stateless Functional)

  • 性能更高效、代碼更簡(jiǎn)潔
  • 沒有 state,也就是無狀態(tài)
  • 不需要管理/維護(hù) 組件的生命周期
  • 純函數(shù),相同的 props 會(huì)得到同樣的UI渲染結(jié)果
    function List (props) {
        return <div>我是一個(gè)函數(shù)式定義的react組件</div>
    }

ES5方式 React.createClass 定義的組件(該方式已經(jīng)被廢棄,推薦使用①和③)
③ ES6 方式定義的組件(Class Components)

    class List extends React.Component {
        render() {
            return <div>我是一個(gè)es6方式定義的react組件</div>
        }
    }

官方文檔寫的還是頗具神秘感的,先告訴我們①和③方式在 UI 渲染效果是一毛一樣的,但是'Classes have some additional features...' Class 這種方式比 Functional 這種方式多了些不一樣的地方。那么問題來了。多了哪些不一樣的呢? 不一樣的地方你可能也發(fā)現(xiàn)了,有無 state有無生命周期...等

【2】那什么時(shí)候該用 Stateless Functional 什么時(shí)候用 Class 呢?

推薦使用 Functional,能用 Functional 就用 Functional,就醬。
多說一句。
Class 是用來創(chuàng)建包含狀態(tài)生命周期用戶交互的復(fù)雜組件,而當(dāng)組件只是用來純展示或者 props 傳遞渲染時(shí)(展示性),二話不說請(qǐng)用 Stateless Functional 來快速創(chuàng)建組件。

【3】無狀態(tài)組件(Stateless Functional)有哪些優(yōu)缺點(diǎn)
  • 優(yōu)點(diǎn)
  1. 語(yǔ)法/代碼更加簡(jiǎn)潔
  2. 占用內(nèi)存小(沒有 props 等屬性), 首次 render 性能更好
  3. 不需要管理/維護(hù)組件的生命周期
  4. 純函數(shù),相同的 props 會(huì)得到同樣的 UI 渲染結(jié)果
  5. 單元測(cè)試更容易進(jìn)行。因?yàn)檫壿嫸急灰瞥隽?view 層,所以單元測(cè)試時(shí)不需要渲染任何東西,可以專注于單個(gè)邏輯。
  • 缺點(diǎn)
  1. 無生命周期函數(shù)。對(duì)于一個(gè)函數(shù)而言應(yīng)該是談不上生命周期。當(dāng)然了,我們其實(shí)可以使用高階組件去實(shí)現(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('我被點(diǎn)了')
        }
        
        render() {
            return <div onClick={this.onClickList}>點(diǎn)我點(diǎn)我點(diǎn)我</div>
        }
        
    }
    
    //第二種方法: 在render()行內(nèi)事件使用bind來綁定
    
    class List extends React.Component {
        
        onClickList() {
            console.log('我被點(diǎn)了')
        }
        
        render() {
            return <div onClick={this.onClickList.bind(this)}>點(diǎn)我點(diǎn)我點(diǎn)我</div>
        }
        
    }
    
    //第三種方法: 使用箭頭函數(shù) => 
    
    class List extends React.Component {
        
        onClickList = () => {
            console.log('我被點(diǎn)了')
        }
        
        render() {
            return <div onClick={this.onClickList}>點(diǎn)我點(diǎn)我點(diǎn)我</div>
        }
        
    }
    
    //第四種,當(dāng)然,你要在render()行內(nèi)使用箭頭函數(shù)也行
    
    class List extends React.Component {
        
        onClickList() {
            console.log('我被點(diǎn)了')
        }
        
        render() {
            return <div onClick={() => this.onClickList()}>點(diǎn)我點(diǎn)我點(diǎn)我</div>
        }
        
    }

我日常開發(fā)都比較喜歡用箭頭函數(shù)的方法,代碼量比第一種少??。當(dāng)然,官方說在 render 中創(chuàng)建函數(shù)(第二,和第四種)可能會(huì)有性能問題。但往往需要傳遞參數(shù)或者回調(diào)時(shí),都得用到。例如:

    <button onClick={this.handleClick.bind(this, id)} />
    <button onClick={() => this.handleClick(id)} />
【5】智能組件 vs 木偶組件 ?(容器組件 vs 展示組件)

Smart 組件 和 Dumb 組件對(duì)于開發(fā)過 react 項(xiàng)目的朋友來說應(yīng)該不陌生了。

Dumb 組件,聽名字你就知道這種組件很傻很木,因?yàn)槟九冀M件只關(guān)心一件事情就是 —— 根據(jù) props 進(jìn)行渲染。
Smart 組件就很聰明,它專門做數(shù)據(jù)相關(guān)的邏輯,和各路數(shù)據(jù)打交道,ajax獲取數(shù)據(jù),定義好數(shù)據(jù)操作的相關(guān)函數(shù),然后將這些數(shù)據(jù)、函數(shù)直接傳遞給具體實(shí)現(xiàn)的組件(Dumb 組件)即可。所以根據(jù)是否需要高度的復(fù)用性,把組件劃分為 Dumb 和 Smart 組件。

小提示1:Smart 組件復(fù)用性不強(qiáng)甚至不需要復(fù)用,Dumb 組件往往是復(fù)用性強(qiáng)的,但是Dumb 組件對(duì)于 Smart 組件的帶入性不要太強(qiáng),因?yàn)閹胩噙壿嫊?huì)導(dǎo)致復(fù)用性降低。二者選擇,設(shè)計(jì)組件時(shí)需要斟酌一下。

小提示2:Dumb 組件 的子組件也應(yīng)該是 Dumb 組件。

小提示3:redux store 相關(guān)的應(yīng)該和 Smart 組件連接起來 。

關(guān)于React生命周期

關(guān)于生命周期,面試的時(shí)候總喜歡問點(diǎn)兒react生命周期相關(guān)的,而且想要了解別人寫的 react 代碼,深刻理解 react 生命周期也是很重要的。先不要往下看,閉上眼睛想想看你所了解的 react 生命周期有哪些?

...

...

...

...

...

...

liftcycle.jpg

ok 你應(yīng)該想完了哈。是不是大概有下面這么一個(gè)流程?(忽略圖片的渣像素??圖片是經(jīng)典的組件掛載圖來源于網(wǎng)絡(luò))

react 組件的生命周期方法都可以被分割成四個(gè)階段:初始化、掛載階段(Mounting)、更新階段(Updating)、卸載階段(Unmounting)。

接下來就讓我們?nèi)タ纯瓷芷诙加心男┬≈R(shí)點(diǎn)。

【6】Mounting -- 下面這些方法將會(huì)在 component 實(shí)例被創(chuàng)建和插入到DOM后調(diào)用。
  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount()
【7】Updating -- props 或者 state 的變化都會(huì)導(dǎo)致更新。下面這些方法會(huì)在 component 重新渲染時(shí)調(diào)用。
  • componentWillReceiveProps()
  • shouldComponentUpdate()
  • componentWillUpdate()
  • render()
  • componentDidUpdate()
【8】Unmounting -- 該方法將會(huì)在 component 從DOM中移除時(shí)調(diào)用。
  • componentWillUnmount()

接下來簡(jiǎn)單的介紹一下幾個(gè)生命周期。

【9】1. componentWillMount

componentWillMount() 是在組件掛載(mount)之前被調(diào)用.
componentWillMount()是唯一一個(gè)在服務(wù)器端渲染(ssr)調(diào)用的生命周期鉤子

關(guān)于 setState 在 componentWillMount 使用:可以使用。因?yàn)樗?render 方法之前被調(diào)用,因此 setState 也不會(huì)導(dǎo)致重繪(re-render)

【10】2. componentDidMount

componentDidMount() 在組件掛載之后立即執(zhí)行

在這個(gè)鉤子里合適:

  • ajax 請(qǐng)求
  • 初始化DOM節(jié)點(diǎn)的操作
  • 設(shè)置計(jì)時(shí)器 setTimeout 或者 setInterval (溫馨提示,別忘了在 componentWillUnmount 關(guān)閉這些計(jì)時(shí)器)

關(guān)于 setState 在 componentDidMount 使用: 可以使用。但是經(jīng)常導(dǎo)致性能問題。當(dāng)然非要在 render 前拿到 DOM 節(jié)點(diǎn)的大小和位置,是可以用的。

插曲。面試題:ajax 請(qǐng)求應(yīng)該在哪個(gè)生命周期?為什么?

【11】3. componentWillReceiveProps(nextProps)

componentWillReceiveProps 將會(huì)在已掛載組件(mounted component)接收到新的 props 之前調(diào)用。所以初始化 props 的mount不會(huì)觸發(fā)這個(gè)函數(shù)。直接 setState不會(huì)觸發(fā)這個(gè)函數(shù)。

在這個(gè)鉤子里合適:

  • 更新 state 的值(比如重置)
  • 比較 this.props 和 nextProps

特別特別特別要注意的是,當(dāng)父組件導(dǎo)致該組件 re-render 時(shí),即便 props 沒有發(fā)生任何的改變,react 也有可能執(zhí)行該鉤子函數(shù)。所以呢,所以就是如果你想要真正處理 props 的變化,要記得比較當(dāng)前 props 和 nextProps.

關(guān)于setState在componentWillReceiveProps使用: 可以使用

【12】4. shouldComponentUpdate(nextProps, nextState)

當(dāng)改變state 或者 props 并且是在render之前會(huì)調(diào)用shouldComponentUpdate,說白了就是該鉤子函數(shù)用于告訴 React 組件是否需要重新渲染。

shouldComponentUpdate 默認(rèn)return true,如果return false componentWillUpdaterendercomponentDidUpdate都將不會(huì)被調(diào)用。千萬(wàn)記住一點(diǎn), 當(dāng)return false時(shí),當(dāng)他們的 state 發(fā)生改變時(shí),并不會(huì)阻止子組件(child component)進(jìn)行重新渲染。

shouldComponentUpdate在兩種情況下不會(huì)被調(diào)用:

  • 組件初始化
  • 使用forceUpdate的情況

大家應(yīng)該都是 shouldComponentUpdate 還有一個(gè)知識(shí)點(diǎn)就是和 react 組件性能優(yōu)化相關(guān)的。是的。你可以this.state 和 nextState、this.props 和 nextProps 做比較來決定出 return false 并告訴 react 可以不更新該組件。如果做的只是一些淺層的數(shù)據(jù)比較完全可以用 PureComponent 來代替(深層的嵌套數(shù)據(jù)PureComponent也無能為力)

react 不建議在 shouldComponentUpdate 做深層的對(duì)比或者用 JSON.stringify(),因?yàn)檫@樣反而損害到性能。

【13】5. componentWillUpdate(nextProps, nextState)

state 或者 props 更新后 re-render 之前調(diào)用。

注意:不要在componentWillUpdate 使用 this.setState, 或者 redux 分發(fā)一個(gè)action(dispatch a Redux action),因?yàn)樵?componentWillUpdate 之前會(huì)觸發(fā)組件的更新。 如果非要在做以上操作的話,可以在componentWillReceiveProps 哦

【14】6. componentDidUpdate(prevProps, prevState)

在組件更新之后馬上調(diào)用 componentDidUpdate。

在這個(gè)鉤子函數(shù)中你可以:

  • 操作 DOM
  • 發(fā)起網(wǎng)絡(luò)請(qǐng)求
【15】7. componentWillUnmount

在組件卸載(unmounted)和銷毀(destroyed)前調(diào)用。

在componentWillUnmount你可以執(zhí)行任何需要清除的方法。比如:

  • 清除計(jì)時(shí)器
  • 斷開網(wǎng)絡(luò)請(qǐng)求
  • 解綁dom事件
  • 等等
【16】生命周期table
生命周期 是否可以調(diào)用this.setState 初始化是否執(zhí)行
componentWillMount 可以
componentDidMount 可以
componentWillReceiveProps 可以
shouldComponentUpdate 不可以
componentWillUpdate 不可以
componentDidUpdate 可以
componentWillUnmount 不可以

特別特別特別注意:
①componentWillMount 和 componentWillReceiveProps 調(diào)用 setState 不會(huì)重復(fù)渲染(re-render)
②componentDidUpdate,不能直接 this.setState, 不然會(huì)溢出棧。需要對(duì) prevProps 與 this.props 和 prevState 和 this.state 做一個(gè)判斷再執(zhí)行 this.setState。就類似while循環(huán)不能陷入死循環(huán)。

好吧。長(zhǎng)篇大論了一番 react 的生命周期。不為什么,就因?yàn)樗浅5闹匾2还苁菍?duì)你的面試或者日常開發(fā)或者閱讀理解別人的代碼都是非常重要。哪個(gè)階段會(huì)觸發(fā)哪個(gè)生命周期,哪個(gè)能干什么哪個(gè)不能干什么,哪個(gè)更合適哪個(gè)不合適。來!干了它,咱們?cè)倮^續(xù)往下看!

......
.....
....
...
..
.
感謝你能看到這里,咱們繼續(xù)往下鑿....
.
..
...
....
.....
......

【17】props 和 state 的區(qū)別
  1. "props"是別人的, props實(shí)現(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)就是“丑”。說實(shí)在的一開始寫 jsx 我也是拒絕的,但是沒想到 jsx 其語(yǔ)法和背后的邏輯讓構(gòu)建react組件變得極其簡(jiǎn)單。
那 jsx 到底是什么呢?jsx 是一個(gè)看起來很像 XML 的 JavaScript 語(yǔ)法擴(kuò)展。說白了 jsx 并不是什么高深的技術(shù),可以說只是一個(gè)比較高級(jí)但很直觀的語(yǔ)法糖。它非常有用,卻不是一個(gè)必需品,沒有 jsx 的 React 也可以正常工作:只要你樂意用 JavaScript 代碼去創(chuàng)建這些虛擬 DOM 元素(但是真的超級(jí)麻煩)。

jsx優(yōu)點(diǎn):

  • 執(zhí)行更快,因?yàn)樗诰幾g為 JavaScript 代碼后進(jìn)行了優(yōu)化
  • 它是類型安全的,在編譯過程中就能發(fā)現(xiàn)錯(cuò)誤
  • 編寫模板更加簡(jiǎn)單快速
  • 更加直觀,可讀性高

來看看以下代碼:
1.當(dāng)我們用HTML描述一個(gè)按鈕的時(shí)候,你會(huì)發(fā)現(xiàn)一個(gè) DOM 元素包含的信息其實(shí)只有三個(gè):標(biāo)簽名,屬性,子元素

    <div id="btn-wrap">
        <button class="btn">click</button>
    </div>

2.我們?nèi)绻胘s描述,可以通過JSON對(duì)象,且依然包括元素的標(biāo)簽名、屬性,子元素

    {
        type: 'div',
        props: { id: 'btn-wrap' },
        children: {
            type: 'button',
            props: { className: 'btn' },
            children: 'click'
        }
    }

仔細(xì)觀察,你會(huì)發(fā)現(xiàn)HTML和js描述一個(gè)按鈕他們所對(duì)應(yīng)的結(jié)構(gòu)簡(jiǎn)直是一毛一樣的,就是說一個(gè)html構(gòu)建的UI界面我們完全可以用js來描述。你會(huì)發(fā)現(xiàn)HTML書寫一個(gè)按鈕遠(yuǎn)比js書寫方式來得蘇胡,而且結(jié)構(gòu)更加清晰。但是如果 你堅(jiān)決要用js來寫我也不會(huì)反對(duì)的。來!先寫個(gè)div十層嵌套試試?

react提供jsx語(yǔ)法糖,將html語(yǔ)法直接加入到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'));

這時(shí)候你會(huì)看到頁(yè)面瀏覽器自動(dòng)刷新了且頁(yè)面顯示了'happy react !'字樣。

如果以上代碼經(jīng)過編譯會(huì)變成:

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的代碼更加直觀便于維護(hù)了吧!

雖然你看到的html寫在了js代碼中,但是你永遠(yuǎn)要記住"jsx最終其實(shí)就是JavaScript對(duì)象"。react通這個(gè)對(duì)象來創(chuàng)建或者更新虛擬元素最終來管理virtual DOM(虛擬DOM)

jsx對(duì)象元素可以理解為和真實(shí)元素一一對(duì)應(yīng)的,它的創(chuàng)建、更新、刪除都是在內(nèi)存中完成的。并不會(huì)直接渲染到真實(shí)DOM中去,整個(gè)react應(yīng)用程序唯一操作到DOM就是:

    ReactDOM.render(<HappyReact />, document.getElementById('root'));
【20】大概知道jsx是什么了。我們是得花點(diǎn)兒時(shí)間學(xué)習(xí)/了解/回憶一下jsx的寫法。
  1. render函數(shù)只能return一個(gè)根節(jié)點(diǎn),只允許被一個(gè)標(biāo)簽包裹
  2. Component 命名首字大寫,HTML 標(biāo)簽用小寫
  3. 如果不存在子節(jié)點(diǎn),可以使用自閉合 <div />
  4. jsx的注釋 {/* */}
  5. JavaScript 屬性表達(dá)式,屬性值用 {}
  6. 三元表達(dá)式
  7. 數(shù)組遞歸(渲染列表) map
  8. 兩個(gè)特殊屬性 class, for. 因?yàn)閏lass, for在JavaScript中這個(gè)兩個(gè)單詞都是關(guān)鍵詞。因此需要做一手轉(zhuǎn)換。其他屬性可以像寫html一樣添加上去。
  9. jsx書寫樣式
  10. 事件處理,使用inline方式的駝峰式寫法,例如onClick、onChange
  11. HTML轉(zhuǎn)義 --> dangerouslySetInnerHTML={{__html: '<div>hhh</div>'}}
  12. 利用es6 中 ... 展開運(yùn)算符。例如
    const helloProps = {
        value: 'hello',
        show: true,
    }
 <HelloWorld ...helloProps />
  1. 如果屬性值是true 這里直接寫屬性名。例如
    <Button disabled={true} /> 
    可以寫成
    <Button disabled /> 
  1. false, null, undefined, true 是有效的子內(nèi)容,但是不會(huì)被渲染出來。以下表達(dá)式渲染結(jié)果是一樣的:
    <div />
    <div></div>
    <div>{false}</div>
    <div>{null}</div>
    <div>{undefined}</div>
    <div>{true}</div>

15 ...

...好吧我可能暫時(shí)想到這么多了。

【21】refs 是什么?(refs功能,如何實(shí)現(xiàn)?)

react 提供了一種特殊屬性, 允許您直接訪問DOM元素或組件實(shí)例。
ref 可以返回一個(gè)字符串(string) 或者 一個(gè)回調(diào)函數(shù)(cb),這個(gè)回調(diào)函數(shù)會(huì)在組件實(shí)例化或者銷毀之后立即執(zhí)行。

字符串refs在未來的版本中可能被移除,所以推薦回調(diào)的方式來代替。

    class TextInput extends Component {
        componentDidMount() {
            this.textInput.focus()
        }
        
        render() {
            return (
                <input ref={(input) => this.textInpuf = input} />
            )
        }
    }

官方推薦幾種很好的方式使用refs:

  • 管理焦點(diǎn),文本選擇或者媒體播放
  • 觸發(fā)重要的動(dòng)畫
  • 整合第三方DOM庫(kù)

當(dāng)然能不用refs就盡量不要用refs,不要太過度依賴refs來解決問題。

【22】什么是受控組件和什么是非受控組件

在react表單組件可被分為兩類:受控組件 和 非受控組件。

  • 受控組件
    我們簡(jiǎn)單的理解,設(shè)置了 value 的 <input>(表單標(biāo)簽) 是一個(gè)受控組件。
    當(dāng)我們?cè)O(shè)置了value為"hi"(某個(gè)值)時(shí),且在頁(yè)面上渲染出改值時(shí),我們?cè)阡秩境鰜淼脑乩镙斎肴魏沃刀疾黄鹱饔谩R驗(yàn)閞eact 已經(jīng)把value賦值為"hi"。 要想改變value值,還必須配合這onChange 和 setState 來實(shí)現(xiàn)。

當(dāng)然你也可以看看官網(wǎng)文檔來如何定義受控組件的。

在 HTML 中,表單元素如 <input>,<textarea> 和 <select> 表單元素通常保持自己的狀態(tài),并根據(jù)用戶輸入進(jìn)行更新。而在 React 中,可變狀態(tài)一般保存在組件的 state(狀態(tài)) 屬性中,并且只能通過 setState() 更新。

我們可以通過使 React 的 state 成為 “單一數(shù)據(jù)源原則” 來結(jié)合這兩個(gè)形式。然后渲染表單的 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ā)生變化時(shí),調(diào)用onChange事件
  3. 通過對(duì)象e拿到改變的狀態(tài),例如e.target.value
  4. 通過setState更新應(yīng)用value 并 觸發(fā)視圖重新渲染,最終完成表單組件的更新
    // 第四步 setState 我們還可以對(duì)表單值進(jìn)行直接修改或者驗(yàn)證
    // 受控組件支持即時(shí)字段驗(yàn)證,允許您有條件地禁用/啟用按鈕,強(qiáng)制輸入格式
    onInputChange = (e) => {
        this.setState({ value: e.target.value.substring(0, 140).toUpperCase() })
    }

特別特的注意!!!! 如果 value 為 undefined,則變成了非受控組件。

  • 非受控組件
    理解了受控組件,那你一定知道非受控組件就是沒有value(單選/復(fù)選按鈕為 checked)屬性的表單組件。可以通過設(shè)置 defalutValue / defalutChecked 來設(shè)置組件初始值。

多啰嗦一句,defalutValue / defalutChecked,僅僅會(huì)被渲染一次,在后續(xù)渲染并不起作用。

因?yàn)椴皇躶tate / props控制,我們需要為其添加 ref 來訪問渲染后的DOM元素,才能最終拿到改變后的value/checked。還記得refs那句話怎么說來著:“能不用refs就盡量不要用refs”。so react官方還是比較推薦使用受控組件。

......
.....
....
...
..
.
看累了,我們留一點(diǎn)兒明天再來嘛。。。
.
..
...
....
.....
......

關(guān)于setState

setState 對(duì)于每一個(gè)使用過react的盆友來說應(yīng)該并不陌生。與之還能立刻聯(lián)想出來幾個(gè)詞 “更改state” “異步” “重新渲染”...

來一個(gè)道題練練手?雖然平時(shí)不會(huì)寫這么*的代碼,但誰(shuí)知道面試會(huì)不會(huì)出現(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() 排隊(duì)更改組件的 state ,并通過更新 state 來告訴 React ,該組件及其子組件需要重新渲染。這是用于 響應(yīng)事件處理程序服務(wù)器響應(yīng) 更新用戶界面的主要方法。

我記得我剛學(xué)習(xí)react的時(shí)候,文檔上還沒有明確說調(diào)用setState是異步的,只是說了“不保證是同步的”。但最近去看了官方文檔,文檔說調(diào)用setState是異步的了。

【24】調(diào)用setState()實(shí)際上發(fā)生了什么?

簡(jiǎn)單的說,就是 更改state、更新UI
復(fù)雜的說,就是 怎么合并新的state,怎么根據(jù)新state來更新UI

【25】setState()第二個(gè)參數(shù)是什么?它有什么作用?

setState的第二個(gè)參數(shù)是一個(gè)可選的回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)將在 setState 完成后執(zhí)行,并且重新渲染組件。在這個(gè)回調(diào)函數(shù)中你可以拿到剛更新的state的值。但是這樣的邏輯 官方推薦 使用 componentDidUpdate。

【26】如何在 setState 后直接獲取修改后的值
  1. setState 第二個(gè)參數(shù),回調(diào)函數(shù)中獲取
  2. 使用setTimeout
    setTimeout(() => {
        this.setState({ value: 'hhh' })
        
        console.log(this.state.value) // hhh
    }, 0)
    // 看到這里最開始的那道練手題,是不是已經(jīng)可以迎刃而解了。哈哈哈哈哈
【27】setState 第一個(gè)參數(shù)有兩種傳遞方式 1.一個(gè)對(duì)象 2. 一個(gè)函數(shù) 這兩種寫法有什么區(qū)別呢?

舉個(gè)例子

    ...
    this.state = { text : '這是一個(gè)栗子' }
    ...

    // 使用傳遞對(duì)象的寫法
    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é)果是不一樣的。

  • 傳遞對(duì)象 => this.state.text => '這是一個(gè)栗子222'
  • 傳遞函數(shù) => this.state.text => '這是一個(gè)栗子111222'

setState為了提升性能,在批量執(zhí)行 state 改變?cè)谧鼋y(tǒng)一的DOM渲染。而在這個(gè)批量執(zhí)行的過程中,如果你多次傳遞的是一堆對(duì)象,它就會(huì)做一些對(duì)象合并或者組合的操作,例如Object.assign({}, { a: '111' }, { a: '222' })。如果key值一樣的話,后面的值會(huì)覆蓋掉前面的值。
但多次傳遞函數(shù)方式,每次 React 從 setState 執(zhí)行函數(shù),并通過傳遞已更新的狀態(tài)來更新你的狀態(tài)。這使得功能 setState 可以基于先前狀態(tài)設(shè)置狀態(tài)。

使用setState要注意!!!

  1. setState可能會(huì)引發(fā)不必要的渲染 (shouldComponentUpdate/PureComponent)
  2. setState無法完全掌控應(yīng)用中所有組件的狀態(tài)(Redux/Mbox)
【28】 什么是高階組件,它是如何使用?

高階組件它是一個(gè)函數(shù)。高階組件它是一個(gè)函數(shù)。高階組件它是一個(gè)函數(shù)。并不是一個(gè)組件。通俗的講就是它接收一個(gè)React組件作為輸入,輸出一個(gè)新的增強(qiáng)版的React組件。

舉一個(gè)可能不太恰當(dāng)?shù)睦樱蠹铱赡芏纪嫱跽咿r(nóng)藥,打藍(lán)爸爸或者紅爸爸就是對(duì)英雄自身的一個(gè)增強(qiáng)版。吃了藍(lán)爸爸并不會(huì)影響你吃紅爸爸,也不會(huì)影響你買了什么裝備等等。

好了,那么我們定義一個(gè)最最最簡(jiǎn)單的高階組件

    const MyContainer = (WrappedComponent) => {
        return class NewComponent extend Component {
            render() {
                return <WrappedComponent />
            }
        }
    }

將你的組件類作為參數(shù)傳入高階組件這個(gè)函數(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"項(xiàng),添加 "plugins": ["transform-decorators-legacy"]

在代碼優(yōu)化(抽離公共邏輯)或者組件解耦的時(shí)候我們可以考慮一下使用高階組件,這樣有助于提高我們代碼的靈活性,邏輯的復(fù)用性。

【29】什么是PureComponent? 介紹一下PureComponent和shouldComponentUpdate有什么區(qū)別?

PureComponent 和 Component是相同,只要把繼承類從 Component 換成 PureComponent 即可。PureComponent改變了shouldComponentUpdate,它會(huì)自動(dòng)檢查組件是否重新渲染。也就是說,只有當(dāng)PureComponent檢查到props或者state變化時(shí),才會(huì)調(diào)用render函數(shù),因此不用寫額外的檢查。還可以減少 Virtual DOM 的生成和比對(duì)過程,達(dá)到提升性能的目的。

注意:PureComponent 的 shouldComponentUpdate 只是進(jìn)行了淺比較(state,props對(duì)象結(jié)構(gòu)簡(jiǎn)單,可以理解為對(duì)象只有一層),對(duì)于復(fù)雜且嵌套更深層數(shù)據(jù)的比較會(huì)出現(xiàn)偏差。對(duì)于深比較,你可以選擇在 shouldComponentUpdate 進(jìn)行深比較檢查來確定組件是否渲染,但是你要知道 深比較 是非常昂貴的。 當(dāng)然,你可能知道 使用 Immutable 來幫助嵌套數(shù)據(jù)的快速比較。

【30】shouldComponentUpdate 的作用以及它的重要性?

shouldComponentUpdate 允許我們手動(dòng)地判斷是否要進(jìn)行組件更新,根據(jù)組件的應(yīng)用場(chǎng)景設(shè)置函數(shù)的合理返回值能夠幫我們避免不必要的更新。

【31】為什么我們利用循環(huán)產(chǎn)生的組件中要用上key這個(gè)特殊的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 屬性的話,會(huì)報(bào)一個(gè)警告: Warning: Each child in an array or iterator should have a unique "key" prop...

keys 是 React 用于追蹤哪些列表中元素被修改被添加或者被移除的輔助標(biāo)識(shí)
之所以需要key,因?yàn)閞eact 是非常高效的,它會(huì)借助元素的 key 值來判斷該元素是新創(chuàng)建的,或者移動(dòng)(交換位置)而來的,從而減少不必要的元素重渲染。更直觀一點(diǎn)兒就是 react 很懶,能復(fù)用的元素就復(fù)用,他不想重新創(chuàng)建新的元素。

那么,如果上面代碼 key={index} 呢?你會(huì)發(fā)現(xiàn)也不會(huì)有warning,但是這樣做的效率是非常非常非常低的。

看看以下例子:

    // list = [a, b, c, d]
    <div>
        list.map((item, index) => <div key={index}>{item}</div>)
    </div>

渲染完成后我們abcd 分別對(duì)應(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所對(duì)應(yīng)的key都改變了,這樣react Virtual DOM就不論有沒有相同的項(xiàng),更新都會(huì)重新渲染了。所以我們要保證某個(gè)元素的 key 在其同級(jí)元素中具有唯一性,這個(gè)key 的值可以直接后臺(tái)數(shù)據(jù)返回的 id,因?yàn)楹笈_(tái)的 id 都是唯一的。

記住實(shí)際開發(fā)中,就別再直接用循環(huán)計(jì)數(shù)器 index 了,那就有點(diǎn)兒騙自己了哈。剛用react我也老用index...

react組件間的通信

組件之間的通信也是老生常談了。不僅在實(shí)際開發(fā)中,面試時(shí)估計(jì)也經(jīng)常被提及。

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

  1. 父組件向子組件通信
  2. 子組件向父組件通信
  3. 兄弟組件之間通信
【32】父組件向子組件通信

在 react 中數(shù)據(jù)是單向傳遞的,父組件可以向子組件通過傳 props 的方式,子組件拿到 props 之后做相應(yīng)的處理,這就是父組件向子組件進(jìn)行通信方式。

    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 傳遞一個(gè)函數(shù),子組件調(diào)用這個(gè)函數(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】兄弟組件之間通信

兩個(gè)兄弟組件之間的數(shù)據(jù)傳遞,我們可以通過他們的共同父組件來實(shí)現(xiàn)。Child1 將要傳遞的信息傳遞給 Parent 然后 Parent 再將從 Child1 拿到的信息傳遞給 Child2 當(dāng)然,我們同樣是利用 props。

我們來寫一段點(diǎn)擊 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é)

以上三種方式是最常見到的。但是實(shí)際項(xiàng)目中往往比這種通信更復(fù)雜得多。因?yàn)閺?fù)雜項(xiàng)目的組件嵌套往往就像一顆枝繁葉茂的樹一樣。
比如:

1、跨n級(jí)組件之間通信,就是 Parent 組件和它子組件的子組件通信,或者子組件的子組件的子組件通信....

2、非嵌套組件的通信,剛剛說的兄弟組件是最簡(jiǎn)單非嵌套,還有更多不是同一父組件的非兄弟組件的嵌套。說得繞一點(diǎn)兒,你和你爺爺?shù)牡艿艿膶O子/兒子通信就是屬于這種情況。

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

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

多嘮叨一句,所有通信方式肯定都可以用在任何項(xiàng)目下。但,就像女朋友一樣,最適合的才是最好的

【36】ajax 應(yīng)該在哪個(gè)生命周期調(diào)用呢?why

既然有人問了這個(gè)問題,看來這個(gè)問題還有有很多討論的空間。

對(duì)于 ajax 應(yīng)該是在哪個(gè)生命周期調(diào)用呢? 備受爭(zhēng)議應(yīng)該就是在 componentDidmount 和 componentWillmount 這兩個(gè)生命周期之間了。網(wǎng)路上也眾說紛紜。看過官網(wǎng)文檔的小伙伴們應(yīng)該也是知道 官網(wǎng)說的是 應(yīng)該在 componentDidmount 。 然鵝。官網(wǎng)并沒有告訴我們 why ?

不少開發(fā)過 react 項(xiàng)目的同學(xué)應(yīng)該也分別嘗試過在 componentDidmount 和 componentWillmount 都做過 ajax 的請(qǐng)求,好像沒啥問題吧?好像都可以成功吧? 但是到底哪一個(gè)更合適呢?

咱們先來看點(diǎn)兒代碼熱熱場(chǎng)子......

代碼一:

    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 中并不會(huì)觸發(fā) re-render。 但是如果在 setState 在一個(gè)異步方法下結(jié)果可能就不一樣了。 你知道的,我們實(shí)際上獲取數(shù)據(jù)都是異步的,所以并不會(huì)阻礙組件渲染。而且我們往往都會(huì)在 ajax 請(qǐng)求成功后再 setState 來更新狀態(tài)。此時(shí)的 setState 會(huì)放入隊(duì)列中,等待組件掛載完成后,再更新組件。例如 將 setState 放入 setTimeout 或者 請(qǐng)求成功后的 fetch 或者 axios 中都是這種情況。

所以代碼二實(shí)際上就模擬了一次 在 componentWillMount 發(fā)送 ajax 請(qǐng)求。它的執(zhí)行效果或者說效率從上面代碼看上來和代碼四是一樣的(在 componentDidMount 發(fā)送 ajax)。所以 componentWillMount 和 componentDidMount 請(qǐng)求其實(shí)都是可以的!

但是!!!為什么官網(wǎng)沒這么說呢?文檔只推薦了 componentDidMount 。

React 下一代調(diào)和算法 Fiber 會(huì)通過開始或停止渲染的方式優(yōu)化應(yīng)用性能,其會(huì)影響到 componentWillMount 的觸發(fā)次數(shù)。對(duì)于 componentWillMount 調(diào)用次數(shù)變得不可確定。 react 可能會(huì)多次頻繁調(diào)用 componentWillMount 。ajax 放入這個(gè)生命周期顯然不是最好的選擇。

所以呢。我還是比較推薦在 componentDidMount 中調(diào)用ajax 。

更多...

當(dāng)然面試中可能還會(huì)有更深層次更開發(fā)性的問題。

  • 如果你能夠改進(jìn)React的一樣功能,那會(huì)是哪一個(gè)功能?(react 的缺點(diǎn))
  • immutable.js 原理是什么? Immutable 詳解及 React 中實(shí)踐
  • react 性能優(yōu)化有哪些?
  • react diff算法
  • react 虛擬dom原理
  • react 是什么
  • react和vue的區(qū)別
  • ...

對(duì)于react技術(shù)棧 react-router、redux 當(dāng)然也有很多。

  • redux react-redux 分別負(fù)責(zé)哪些功能
  • provider connect的用法
  • store數(shù)據(jù)流向
  • redux的三個(gè)原則
  • ...

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.lxweimin.com/p/fb915d9c99c4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。