<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React Hooks 設(shè)計(jì)思想

          共 7266字,需瀏覽 15分鐘

           ·

          2020-08-26 05:39

          作者:繁星https://zhuanlan.zhihu.com/p/103692400

          聊聊 React 的 class 組件

          組件是 React 應(yīng)用的構(gòu)建塊,自上而下的數(shù)據(jù)流結(jié)合組件可以將 UI 解構(gòu)為獨(dú)立且可復(fù)用的單元。組件主要做的事情主要有以下三點(diǎn):

          • 將傳入的 props 和 內(nèi)部 state 渲染到頁面上;

          • 管理內(nèi)部 state,并根據(jù) state 變化渲染出最新的結(jié)果;

          • 處理與組件外部的交互;

          假如現(xiàn)在有一個(gè)新聞列表頁面,列表的每一項(xiàng)都包含有標(biāo)題、概要、詳情和縮略圖,如圖所示:

          只是渲染內(nèi)容。如果不考慮查看詳情這個(gè)交互,新聞列表的每一項(xiàng)是很純的,也就是 props 傳入什么數(shù)據(jù),就能渲染出一一對(duì)應(yīng)的結(jié)果:

          1. let NewsItem = (props) => {

          2. return (

          3. <img src={props.imgUrl} />

          4. {props.title}h2>

          5. {props.summary}p>

          6. <p style={{display: 'none'}}>{props.detail}p>

          7. 查看詳情a>

          8. div>

          9. li>

          10. )

          11. }

          要考慮查看詳情這個(gè)交互,就必須在 NewsItem 里加入一個(gè) isDetailShow 的 state 來表示新聞?wù)c詳情的互斥顯示。到目前為止,NewsItem 還是很純的,并沒有和外部有交互。

          要實(shí)現(xiàn)新聞圖片的懶加載,只有 NewsItem 進(jìn)入可視區(qū)時(shí)才將 img 的 src 替換為真實(shí)的 url,這就要求 NewsItem 必須監(jiān)聽瀏覽器事件,并在組件被卸載時(shí)移除這些監(jiān)聽(防止內(nèi)存泄漏)。此時(shí),NewsItem 便不是一個(gè)純的組件了,因?yàn)榕c外部有了交互,這種與外部的交互被稱為副作用(函數(shù)式編程里沒有任何副作用的函數(shù)被稱為純函數(shù))。

          組件的副作用是不可避免的,最常見的有 fetch data,訂閱事件,進(jìn)行 DOM 操作,使用其他 JavaScript 庫(比如 jQuery,Map 等)。在這個(gè)例子中,NewsItem 并沒有 fetch data,相關(guān)職責(zé)由不純的父組件來承擔(dān)。

          綜上,我們的組件需要 state 來存儲(chǔ)一定的邏輯狀態(tài),并且需要可以訪問并更改 state 的方法函數(shù)。

          class 就是一個(gè)很好的表現(xiàn)形式:要渲染的內(nèi)容(props 或 state)放在類的屬性里,那些處理用戶交互的回調(diào)函數(shù)和生命周期函數(shù)放在類的方法里。方法與屬性通過 class 的形式建立了關(guān)聯(lián),有能力訪問和更改屬性?;卣{(diào)函數(shù)通過更改對(duì)應(yīng)屬性處理用戶操作,生命周期函數(shù)則給予開發(fā)者處理組件與外部的交互能力(處理副作用)。

          這樣通過 class 組件,ReactDOM 就能做到渲染數(shù)據(jù),綁定事件,并在不同的生命周期調(diào)用開發(fā)者所編寫的代碼,按需求將數(shù)據(jù)渲染成 HTML DOM,然后被瀏覽器渲染展示出來。

          將組件渲染粗暴地分為若干個(gè)階段,通過生命周期函數(shù)處理副作用會(huì)帶來一些問題:

          重復(fù)邏輯,被吐槽最多的例子如下:

          1. async componentDidMount() {

          2. const res = await get(`/users`);

          3. this.setState({ users: res.data });

          4. };


          5. async componentDidUpdate(prevProps) {

          6. if (prevProps.resource !== this.props.resource) {

          7. const res = await get(`/users`);

          8. this.setState({ users: res.data });

          9. }

          10. };

          同一職責(zé)代碼有可能需要被強(qiáng)行分拆到不同的生命周期,例如同一個(gè)事件的訂閱與取消訂閱;

          一部分代碼被分割到不同生命周期中,會(huì)導(dǎo)致組件沒有優(yōu)雅的復(fù)用 state 邏輯代碼的能力,高階組件或 render props 等模式引入了嵌套,復(fù)雜且不靈活;

          越來越多邏輯被放入不同生命周期函數(shù)中,這種組織方式導(dǎo)致代碼越來越復(fù)雜難懂;

          除了這些,class 組件中的 this 也常被人們拿出來吐槽。那么,是否有更優(yōu)雅的設(shè)計(jì)呢?

          閉包為什么在某種程度上能取代 class?

          我們的程序在執(zhí)行的時(shí)候主要做了兩件事:

          為了實(shí)現(xiàn)復(fù)用,我們將具有特定單一功能的邏輯放在函數(shù)里,這樣既可以消滅掉重復(fù)代碼,又可以讓我們在思考問題時(shí)能夠進(jìn)行合理的分解,降低代碼復(fù)雜度。

          但是只有函數(shù)是不夠的,函數(shù)是一個(gè)標(biāo)準(zhǔn)的輸入-加工-輸出模型,輸入和輸出的都是變量里所存儲(chǔ)的數(shù)據(jù),當(dāng)一個(gè)系統(tǒng)的復(fù)雜度高到一定程度的時(shí)候,將函數(shù)與其所操作的數(shù)據(jù)(環(huán)境)關(guān)聯(lián)起來就很有必要了。

          注:函數(shù)式編程要求把I/O限制到最小,干掉所有不必要的讀寫行為,保持計(jì)算過程的單純性。

          最常見的將變量與函數(shù)關(guān)聯(lián)起來方式有:

          函數(shù)對(duì)于其詞法環(huán)境(lexical environment)的引用共同構(gòu)成閉包(closure),簡單說,一個(gè)函數(shù)內(nèi)部能夠訪問到函數(shù)外的變量,如果這 個(gè)函數(shù)內(nèi)部引用了其外部的變量,且自身又被別處引用,那這個(gè)不會(huì)被銷毀的函數(shù)就和它所引用的外部變量一起構(gòu)成閉包。例如:

          1. // 模塊化下可以將 makeCounter 內(nèi)部代碼放在 makeCounter.js 中,并將 return 改為 export

          2. const makeCounter = () => {

          3. let privateCounter = 0;


          4. function changeBy(val) {

          5. privateCounter += val;

          6. }


          7. return {

          8. increment: function() {

          9. changeBy(1);

          10. },

          11. decrement: function() {

          12. changeBy(-1);

          13. },

          14. value: function() {

          15. return privateCounter;

          16. }

          17. }

          18. };


          19. // 使用 makeCounter

          20. const counter = makeCounter();

          21. console.log(counter.value()); /* logs 0 */

          22. counter.increment();

          23. counter.increment();

          24. console.log(counter.value()); /* logs 2 */

          25. counter.decrement();

          26. console.log(counter.value()); /* logs 1 */

          看,我們使用閉包將變量 privateCounter 與幾個(gè)函數(shù)關(guān)聯(lián)了起來,從這點(diǎn)來講能力與面向?qū)ο缶幊滔嗤?/p>

          組件的 API 設(shè)計(jì)

          API 的核心在于表達(dá)能力,對(duì)于 React 組件來說,就是如何讓開發(fā)者將需求良好地表達(dá)出來,然后被 ReactDOM 識(shí)別并渲染。

          class 組件和 functional 組件所要表達(dá)的內(nèi)容是是一樣的,只是表現(xiàn)形式不同。它們都努力做到了一點(diǎn):將存儲(chǔ)組件狀態(tài)的 state 與處理這些 state 的方法關(guān)聯(lián)起來。具體一點(diǎn)說就是一下三點(diǎn):

          2 中函數(shù)的執(zhí)行是確定的,用戶的操作觸發(fā)某個(gè)事件后就會(huì)執(zhí)行相應(yīng)的回調(diào)函數(shù),更改 state,觸發(fā)新的渲染。開發(fā)者需要有能力控制 3 中的函數(shù)執(zhí)行,確定要不要執(zhí)行以及在什么時(shí)候執(zhí)行。在 class 組件中,生命周期函數(shù)給開發(fā)者提供了這種控制能力。

          那么,如果我們通過一套 API 設(shè)計(jì)實(shí)現(xiàn)以上三點(diǎn)且避開 class 組件的缺陷,提供更好的分離關(guān)注點(diǎn)能力,讓代碼復(fù)用更加簡易,是不是一件很值得期待的事情呢?React Hooks 就是滿足這些要求的新設(shè)計(jì)。

          React Hooks 原理

          先來看一個(gè)使用 React Hooks 的例子:

          1. function Counter() {

          2. const [counter, setCounter] = useState(0);


          3. function increment() {

          4. setCounter(counter+1);

          5. }


          6. function decrement() {

          7. setCounter(counter-1);

          8. }


          9. return (

          10. <div className="content">

          11. My Awesome Counter h1>

          12. <hr/>

          13. <h2 className="count">{counter}h2>

          14. <div className="buttons">

          15. <button onClick={increment}>+button>

          16. <button onClick={decrement}>-button>

          17. div>

          18. div>

          19. );

          20. }

          是的,你看到了這個(gè)例子與閉包例子中的 makeCounter 十分相似。makerCounter 使用程序控制并通過 console 出結(jié)果,Counter 通過用戶點(diǎn)擊控制,輸出包含結(jié)果且可以被渲染的組件。除了這點(diǎn)不同,其他部分代碼原理是完全一致的,只是 Hook 進(jìn)行了一些封裝,讓開發(fā)者編寫代碼體驗(yàn)更好。

          我們來看下 useState 的簡化實(shí)現(xiàn):

          1. // React useState hooks

          2. const React = (function() {

          3. let hooks = [];

          4. let idx = 0;


          5. return {

          6. render(Component) {

          7. const C = Component();

          8. C.render();

          9. idx = 0; // reset for next render

          10. return C;

          11. },

          12. useState(initVal) {

          13. const state = hooks[idx] || initVal;

          14. const _idx = idx;

          15. const setState = newVal => {

          16. hooks[_idx] = newVal;

          17. };

          18. idx++;

          19. return [state, setState];

          20. }

          21. };

          22. })();


          23. // Component which use useState

          24. const { useState, render } = React;

          25. function Counter() {

          26. const [count, setCount] = useState(0);

          27. const [text, setText] = useState('apple');


          28. return {

          29. render() {

          30. console.log(`text: ${text}, count: ${count}`);

          31. },

          32. click() {

          33. setCount(count + 1);

          34. },

          35. type(type) {

          36. setText(type)

          37. }

          38. };

          39. }


          40. // simulate render

          41. const counter = render(Counter); // text: apple, count: 0

          42. counter.click();

          43. render(Counter); // text: apple, count: 1

          44. counter.type("pear");

          45. render(Counter); //text: pear, count: 1

          代碼很簡單,這里不做解讀,這里重點(diǎn)說幾點(diǎn):

          正是因?yàn)?hooks 是這樣實(shí)現(xiàn)的,我們在調(diào)用 hooks 的時(shí)候必須要嚴(yán)格保證每一次 render 都能獲得一致的執(zhí)行順序,所以必須要做到:

          到目前為止,我們已經(jīng)可以通過 hooks 的形式管理 state,并通過調(diào)用包含 setState 的回調(diào)函數(shù)處理用戶操作。剩下要解決的便是副作用的問題,useEffect 是 hooks 所提供的方案,下面來看一下 useEffect 的簡化實(shí)現(xiàn)原理(并不完整):

          1. useEffect(cb, depArray) {

          2. const hasNoDeps = !depArray;

          3. hooks[idx] = hooks[idx] || {};

          4. const {deps, cleanup} = hooks[idx]; // undefined when first render

          5. const hasChanged = deps

          6. ? !depArray.every((el, i) => el === deps[i])

          7. : true;

          8. if (hasNoDeps || hasChanged) {

          9. cleanup && cleanup();

          10. hooks[idx].cleanup = cb();

          11. hooks[idx].deps = depArray;

          12. }

          13. idx++;

          14. }

          完整簡化代碼地址:https://stackblitz.com/edit/behind-react-hook

          useEffect 提供了一個(gè)函數(shù)(上面代碼中的 cb)運(yùn)行的容器,這個(gè)容器有以下幾個(gè)特點(diǎn):

          通過將副作用相關(guān)代碼放在 useEffect 的 cb 中,并在 cb 返回的函數(shù)里移除副作用,我們可以在一個(gè) useEffect 中實(shí)現(xiàn)任何想要的生命周期控制:

          這種設(shè)計(jì)最大的好處就是我們可以將單一職責(zé)的代碼放在一個(gè)獨(dú)立的 useEffect 容器里,而不是粗暴地將它們拆分在各個(gè)生命周期函數(shù)中。同時(shí)也要注意的是,useEffect 的 cb 必須要返回一個(gè) cleanup 函數(shù)或者 undefined,所以不可以是 async 函數(shù);

          React Hooks 的優(yōu)點(diǎn)

          通過 Hooks 我們可以對(duì) state 邏輯進(jìn)行良好的封裝,輕松做到隔離和復(fù)用,優(yōu)點(diǎn)主要體現(xiàn)在:


          本文主要介紹了 React Hooks 設(shè)計(jì)思想和優(yōu)點(diǎn),但 hooks 也是有不少”坑點(diǎn)“的,我們在使用的時(shí)候要利用好優(yōu)點(diǎn),努力避開”坑點(diǎn)“。后面我會(huì)單獨(dú)寫一篇文章來介紹 React Hooks 的實(shí)踐。

          點(diǎn)分享
          點(diǎn)點(diǎn)贊
          點(diǎn)在看
          瀏覽 109
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  内射网| 四虎性爱视频 | 大奶一区二区 | 91在线视频观看 | www性欧美 |