<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>

          不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook

          共 4218字,需瀏覽 9分鐘

           ·

          2021-08-26 15:08


          只在最頂層使用 Hook


          不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook, 確保總是在你的 React 函數(shù)的最頂層調(diào)用他們。遵守這條規(guī)則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用。這讓 React 能夠在多次的 useState 和 useEffect 調(diào)用之間保持 hook 狀態(tài)的正確。(如果你對(duì)此感到好奇,我們?cè)谙旅鏁?huì)有更深入的解釋。)


          我們可以在單個(gè)組件中使用多個(gè) State Hook 或 Effect Hook


          function Form() {  // 1. 使用變量名為 name 的 state  const [name, setName] = useState('Mary');
          // 2. 使用 effect 以保存 form 操作 useEffect(function persistForm() { localStorage.setItem('formData', name); });
          // 3. 使用變量名為 surname 的 state const [surname, setSurname] = useState('Poppins');
          // 4. 使用 effect 以更新標(biāo)題 useEffect(function updateTitle() { document.title = name + ' ' + surname; });
          // ...}


          那么 React 怎么知道哪個(gè) state 對(duì)應(yīng)哪個(gè) useState?答案是 React 靠的是 Hook 調(diào)用的順序。


          let hookStates = []; // 放著此組件的所有的hooks數(shù)據(jù)let hookIndex = 0; // 代表當(dāng)前的hooks的索引function useState(initialState){  // 如果有老值取老值,沒(méi)有取默認(rèn)值  hookStates[hookIndex] = hookStates[hookIndex] || initialState;  // 暫存索引  let currentIndex = hookIndex;  function setState(newState){    hookStates[currentIndex] = newState;    render();  }  return [hookStates[hookIndex++], setState];}


          因?yàn)槲覀兊氖纠?,Hook 的調(diào)用順序在每次渲染中都是相同的,所以它能夠正常工作:


          // ------------// 首次渲染// ------------useState('Mary')           // 1. 使用 'Mary' 初始化變量名為 name 的 stateuseEffect(persistForm)     // 2. 添加 effect 以保存 form 操作useState('Poppins')        // 3. 使用 'Poppins' 初始化變量名為 surname 的 stateuseEffect(updateTitle)     // 4. 添加 effect 以更新標(biāo)題
          // -------------// 二次渲染// -------------useState('Mary') // 1. 讀取變量名為 name 的 state(參數(shù)被忽略)useEffect(persistForm) // 2. 替換保存 form 的 effectuseState('Poppins') // 3. 讀取變量名為 surname 的 state(參數(shù)被忽略)useEffect(updateTitle) // 4. 替換更新標(biāo)題的 effect
          // ...


          只要 Hook 的調(diào)用順序在多次渲染之間保持一致,React 就能正確地將內(nèi)部 state 和對(duì)應(yīng)的 Hook 進(jìn)行關(guān)聯(lián)。但如果我們將一個(gè) Hook (例如 persistForm effect) 調(diào)用放到一個(gè)條件語(yǔ)句中會(huì)發(fā)生什么呢?


          // ?? 在條件語(yǔ)句中使用 Hook 違反第一條規(guī)則  if (name !== '') {    useEffect(function persistForm() {      localStorage.setItem('formData', name);    });  }


          在第一次渲染中 name !== '' 這個(gè)條件值為 true,所以我們會(huì)執(zhí)行這個(gè) Hook。但是下一次渲染時(shí)我們可能清空了 name,表達(dá)式值變?yōu)?false。此時(shí)的渲染會(huì)跳過(guò)該 Hook,Hook 的調(diào)用順序發(fā)生了改變:


          useState('Mary')           // 1. 讀取變量名為 name 的 state(參數(shù)被忽略)// useEffect(persistForm)  // ?? 此 Hook 被忽略!useState('Poppins')        // ?? 2 (之前為 3)。讀取變量名為 surname 的 state 失敗useEffect(updateTitle)     // ?? 3 (之前為 4)。替換更新標(biāo)題的 effect 失敗


          React 不知道第二個(gè) useState 的 Hook 應(yīng)該返回什么。React 會(huì)以為在該組件中第二個(gè) Hook 的調(diào)用像上次的渲染一樣,對(duì)應(yīng)的是 persistForm 的 effect,但并非如此。從這里開(kāi)始,后面的 Hook 調(diào)用都被提前執(zhí)行,導(dǎo)致 bug 的產(chǎn)生。


          這就是為什么 Hook 需要在我們組件的最頂層調(diào)用。如果我們想要有條件地執(zhí)行一個(gè) effect,可以將判斷放到 Hook 的內(nèi)部:


          useEffect(function persistForm() {    // ??將條件判斷放置在 effect 中    if (name !== '') {      localStorage.setItem('formData', name);    }  });


          不過(guò)你現(xiàn)在知道了為什么 Hook 會(huì)這樣工作,也知道了這個(gè)規(guī)則是為了避免什么問(wèn)題。



          hooks 實(shí)現(xiàn)原理

          import React from "react";import ReactDOM from "react-dom";
          // ["Mary", undefined, "Poppins", undefined]let hookStates = [];let hookIndex = 0;function useState(initialState){ hookStates[hookIndex]=hookStates[hookIndex] || initialState; let currentIndex = hookIndex; function setState(newState){ hookStates[currentIndex] = newState; render(); } return [hookStates[hookIndex++], setState];}
          function useEffect(callback,dependencies){ if(hookStates[hookIndex]){ let lastDeps = hookStates[hookIndex]; let same = dependencies.every((item,index)=>item === lastDeps[index]); if(same){ hookIndex++; }else{ hookStates[hookIndex++] = dependencies; setTimeout(callback); } }else{ hookStates[hookIndex++] = dependencies;     setTimeout(callback); }}
          function Counter() { const [name, setName] = useState("Mary");
          useEffect(function persistForm() { localStorage.setItem("formData", name); });
          const [surname, setSurname] = useState("Poppins");
          useEffect(function updateTitle() { document.title = name + ' ' + surname; }); return ( <> <p> {name}:{surname} </p> <button onClick={() => setName("張")}>姓</button> <button onClick={() => setSurname("三")}>名</button> </> );}function render() { hookIndex = 0; ReactDOM.render(<Counter />, document.getElementById("root"));}render();


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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          <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>
                  天堂网在线中文 | 乌克兰毛片 | 成人丁香大香蕉 | 我要看日本一级片 | 中国一区二区 |