亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

精讀《Function Component 入門》

Scholer / 1296人閱讀

摘要:比如就是一種,它可以用來(lái)管理狀態(tài)返回的結(jié)果是數(shù)組,數(shù)組的第一項(xiàng)是值,第二項(xiàng)是賦值函數(shù),函數(shù)的第一個(gè)參數(shù)就是默認(rèn)值,也支持回調(diào)函數(shù)。而之所以輸出還是正確的,原因是的回調(diào)函數(shù)中,值永遠(yuǎn)指向最新的值,因此沒(méi)有邏輯漏洞。

1. 引言

如果你在使用 React 16,可以嘗試 Function Component 風(fēng)格,享受更大的靈活性。但在嘗試之前,最好先閱讀本文,對(duì) Function Component 的思維模式有一個(gè)初步認(rèn)識(shí),防止因思維模式不同步造成的困擾。

2. 精讀 什么是 Function Component?

Function Component 就是以 Function 的形式創(chuàng)建的 React 組件:

function App() {
  return (
    

App

); }

也就是,一個(gè)返回了 JSX 或 createElement 的 Function 就可以當(dāng)作 React 組件,這種形式的組件就是 Function Component。

所以我已經(jīng)學(xué)會(huì) Function Component 了嗎?

別急,故事才剛剛開(kāi)始。

什么是 Hooks?

Hooks 是輔助 Function Component 的工具。比如 useState 就是一種 Hook,它可以用來(lái)管理狀態(tài):

function Counter() {
  const [count, setCount] = useState(0);

  return (
    

You clicked {count} times

); }

useState 返回的結(jié)果是數(shù)組,數(shù)組的第一項(xiàng)是 ,第二項(xiàng)是 賦值函數(shù),useState 函數(shù)的第一個(gè)參數(shù)就是 默認(rèn)值,也支持回調(diào)函數(shù)。更詳細(xì)的介紹可以參考 Hooks 規(guī)則解讀。

先賦值再 setTimeout 打印

我們?cè)賹?useStatesetTimeout 結(jié)合使用,看看有什么發(fā)現(xiàn)。

創(chuàng)建一個(gè)按鈕,點(diǎn)擊后讓計(jì)數(shù)器自增,但是延時(shí) 3 秒后再打印出來(lái)

function Counter() {
  const [count, setCount] = useState(0);

  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(count);
    }, 3000);
  };

  return (
    

You clicked {count} times

); }

如果我們 在三秒內(nèi)連續(xù)點(diǎn)擊三次,那么 count 的值最終會(huì)變成 3,而隨之而來(lái)的輸出結(jié)果是。。?

0
1
2

嗯,好像對(duì),但總覺(jué)得有點(diǎn)怪?

使用 Class Component 方式實(shí)現(xiàn)一遍呢?

敲黑板了,回到我們熟悉的 Class Component 模式,實(shí)現(xiàn)一遍上面的功能:

class Counter extends Component {
  state = { count: 0 };

  log = () => {
    this.setState({
      count: this.state.count + 1
    });
    setTimeout(() => {
      console.log(this.state.count);
    }, 3000);
  };

  render() {
    return (
      

You clicked {this.state.count} times

); } }

嗯,結(jié)果應(yīng)該等價(jià)吧?3 秒內(nèi)快速點(diǎn)擊三次按鈕,這次的結(jié)果是:

3
3
3

怎么和 Function Component 結(jié)果不一樣?

這是用好 Function Component 必須邁過(guò)的第一道坎,請(qǐng)確認(rèn)完全理解下面這段話:

首先對(duì) Class Component 進(jìn)行解釋:

首先 state 是 Immutable 的,setState 后一定會(huì)生成一個(gè)全新的 state 引用。

但 Class Component 通過(guò) this.state 方式讀取 state,這導(dǎo)致了每次代碼執(zhí)行都會(huì)拿到最新的 state 引用,所以快速點(diǎn)擊三次的結(jié)果是 3 3 3。

那么對(duì) Function Component 而言:

useState 產(chǎn)生的數(shù)據(jù)也是 Immutable 的,通過(guò)數(shù)組第二個(gè)參數(shù) Set 一個(gè)新值后,原來(lái)的值會(huì)形成一個(gè)新的引用在下次渲染時(shí)。

但由于對(duì) state 的讀取沒(méi)有通過(guò) this. 的方式,使得 每次 setTimeout 都讀取了當(dāng)時(shí)渲染閉包環(huán)境的數(shù)據(jù),雖然最新的值跟著最新的渲染變了,但舊的渲染里,狀態(tài)依然是舊值。

為了更容易理解,我們來(lái)模擬三次 Function Component 模式下點(diǎn)擊按鈕時(shí)的狀態(tài):

第一次點(diǎn)擊,共渲染了 2 次,setTimeout 生效在第 1 次渲染,此時(shí)狀態(tài)為:

function Counter() {
  const [0, setCount] = useState(0);

  const log = () => {
    setCount(0 + 1);
    setTimeout(() => {
      console.log(0);
    }, 3000);
  };

  return ...
}

第二次點(diǎn)擊,共渲染了 3 次,setTimeout 生效在第 2 次渲染,此時(shí)狀態(tài)為:

function Counter() {
  const [1, setCount] = useState(0);

  const log = () => {
    setCount(1 + 1);
    setTimeout(() => {
      console.log(1);
    }, 3000);
  };

  return ...
}

第三次點(diǎn)擊,共渲染了 4 次,setTimeout 生效在第 3 次渲染,此時(shí)狀態(tài)為:

function Counter() {
  const [2, setCount] = useState(0);

  const log = () => {
    setCount(2 + 1);
    setTimeout(() => {
      console.log(2);
    }, 3000);
  };

  return ...
}

可以看到,每一個(gè)渲染都是一個(gè)獨(dú)立的閉包,在獨(dú)立的三次渲染中,count 在每次渲染中的值分別是 0 1 2,所以無(wú)論 setTimeout 延時(shí)多久,打印出來(lái)的結(jié)果永遠(yuǎn)是 0 1 2。

理解了這一點(diǎn),我們就能繼續(xù)了。

如何讓 Function Component 也打印 3 3 3?

所以這是不是代表 Function Component 無(wú)法覆蓋 Class Component 的功能呢?完全不是,我希望你讀完本文后,不僅能解決這個(gè)問(wèn)題,更能理解為什么用 Function Component 實(shí)現(xiàn)的代碼更佳合理、優(yōu)雅

第一種方案是借助一個(gè)新 Hook - useRef 的能力:

function Counter() {
  const count = useRef(0);

  const log = () => {
    count.current++;
    setTimeout(() => {
      console.log(count.current);
    }, 3000);
  };

  return (
    

You clicked {count.current} times

); }

這種方案的打印結(jié)果就是 3 3 3。

想要理解為什么,首先要理解 useRef 的功能:通過(guò) useRef 創(chuàng)建的對(duì)象,其值只有一份,而且在所有 Rerender 之間共享。

所以我們對(duì) count.current 賦值或讀取,讀到的永遠(yuǎn)是其最新值,而與渲染閉包無(wú)關(guān),因此如果快速點(diǎn)擊三下,必定會(huì)返回 3 3 3 的結(jié)果。

但這種方案有個(gè)問(wèn)題,就是使用 useRef 替代了 useState 創(chuàng)建值,那么很自然的問(wèn)題就是,如何不改變?cè)贾档膶懛?,達(dá)到同樣的效果呢?

如何不改造原始值也打印 3 3 3?

一種最簡(jiǎn)單的做法,就是新建一個(gè) useRef 的值給 setTimeout 使用,而程序其余部分還是用原始的 count:

function Counter() {
  const [count, setCount] = useState(0);
  const currentCount = useRef(count);

  useEffect(() => {
    currentCount.current = count;
  });

  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return (
    

You clicked {count} times

); }

通過(guò)這個(gè)例子,我們引出了一個(gè)新的,也是 最重要的 Hook - useEffect,請(qǐng)務(wù)必深入理解這個(gè)函數(shù)。

useEffect 是處理副作用的,其執(zhí)行時(shí)機(jī)在 每次 Render 渲染完畢后,換句話說(shuō)就是每次渲染都會(huì)執(zhí)行,只是實(shí)際在真實(shí) DOM 操作完畢后。

我們可以利用這個(gè)特性,在每次渲染完畢后,將 count 此時(shí)最新的值賦給 currentCount.current,這樣就使 currentCount 的值自動(dòng)同步了 count 的最新值。

為了確保大家準(zhǔn)確理解 useEffect,筆者再啰嗦一下,將其執(zhí)行周期拆解到每次渲染中。假設(shè)你在三秒內(nèi)快速點(diǎn)擊了三次按鈕,那么你需要在大腦中模擬出下面這三次渲染都發(fā)生了什么:

第一次點(diǎn)擊,共渲染了 2 次,useEffect 生效在第 2 次渲染:

function Counter() {
  const [1, setCount] = useState(0);
  const currentCount = useRef(0);

  useEffect(() => {
    currentCount.current = 1; // 第二次渲染完畢后執(zhí)行一次
  });

  const log = () => {
    setCount(1 + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return ...
}

第二次點(diǎn)擊,共渲染了 3 次,useEffect 生效在第 3 次渲染:

function Counter() {
  const [2, setCount] = useState(0);
  const currentCount = useRef(0);

  useEffect(() => {
    currentCount.current = 2; // 第三次渲染完畢后執(zhí)行一次
  });

  const log = () => {
    setCount(2 + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return ...
}

第三次點(diǎn)擊,共渲染了 4 次,useEffect 生效在第 4 次渲染:

function Counter() {
  const [3, setCount] = useState(0);
  const currentCount = useRef(0);

  useEffect(() => {
    currentCount.current = 3; // 第四次渲染完畢后執(zhí)行一次
  });

  const log = () => {
    setCount(3 + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return ...
}

注意對(duì)比與上面章節(jié)展開(kāi)的 setTimeout 渲染時(shí)有什么不同。

要注意的是,useEffect 也隨著每次渲染而不同的,同一個(gè)組件不同渲染之間,useEffect 內(nèi)閉包環(huán)境完全獨(dú)立。對(duì)于本次的例子,useEffect 共執(zhí)行了 四次,經(jīng)歷了如下四次賦值最終變成 3:

currentCount.current = 0; // 第 1 次渲染
currentCount.current = 1; // 第 2 次渲染
currentCount.current = 2; // 第 3 次渲染
currentCount.current = 3; // 第 4 次渲染

請(qǐng)確保理解了這句話再繼續(xù)往下閱讀:

setTimeout 的例子,三次點(diǎn)擊觸發(fā)了四次渲染,但 setTimeout 分別生效在第 1、2、3 次渲染中,因此值是 0 1 2。

useEffect 的例子中,三次點(diǎn)擊也觸發(fā)了四次渲染,但 useEffect 分別生效在第 1、2、3、4 次渲染中,最終使 currentCount 的值變成 3

用自定義 Hook 包裝 useRef

是不是覺(jué)得每次都寫一堆 useEffect 同步數(shù)據(jù)到 useRef 很煩?是的,想要簡(jiǎn)化,就需要引出一個(gè)新的概念:自定義 Hooks

首先介紹一下,自定義 Hooks 允許創(chuàng)建自定義 Hook,只要函數(shù)名遵循以 use 開(kāi)頭,且返回非 JSX 元素,就是 Hooks 啦!自定義 Hooks 內(nèi)還可以調(diào)用包括內(nèi)置 Hooks 在內(nèi)的所有自定義 Hooks。

也就是我們可以將 useEffect 寫到自定義 Hook 里:

function useCurrentValue(value) {
  const ref = useRef(0);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref;
}

這里又引出一個(gè)新的概念,就是 useEffect 的第二個(gè)參數(shù),dependences。dependences 這個(gè)參數(shù)定義了 useEffect 的依賴,在新的渲染中,只要所有依賴項(xiàng)的引用都不發(fā)生變化,useEffect 就不會(huì)被執(zhí)行,且當(dāng)依賴項(xiàng)為 [] 時(shí),useEffect 僅在初始化執(zhí)行一次,后續(xù)的 Rerender 永遠(yuǎn)也不會(huì)被執(zhí)行。

這個(gè)例子中,我們告訴 React:僅當(dāng) value 的值變化了,再將其最新值同步給 ref.current。

那么這個(gè)自定義 Hook 就可以在任何 Function Component 調(diào)用了:

function Counter() {
  const [count, setCount] = useState(0);
  const currentCount = useCurrentValue(count);

  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(currentCount.current);
    }, 3000);
  };

  return (
    

You clicked {count} times

); }

封裝以后代碼清爽了很多,而且最重要的是將邏輯封裝起來(lái),我們只要理解 useCurrentValue 這個(gè) Hook 可以產(chǎn)生一個(gè)值,其最新值永遠(yuǎn)與入?yún)⑼健?/p>

看到這里,也許有的小伙伴已經(jīng)按捺不住迸發(fā)的靈感了:useEffect 第二個(gè)參數(shù)設(shè)置為空數(shù)組,這個(gè)自定義 Hook 就代表了 didMount 生命周期!

是的,但筆者建議大家 不要再想生命周期的事情,這樣會(huì)阻礙你更好的理解 Function Component。因?yàn)橄乱粋€(gè)話題,就是要告訴你:永遠(yuǎn)要對(duì) useEffect 的依賴誠(chéng)實(shí),被依賴的參數(shù)一定要填上去,否則會(huì)產(chǎn)生非常難以察覺(jué)與修復(fù)的 BUG。

setTimeout 換成 setInterval 會(huì)怎樣

我們回到起點(diǎn),將第一個(gè) setTimeout Demo 中換成 setInterval,看看會(huì)如何:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return 

{count}

; }

這個(gè)例子將引發(fā)學(xué)習(xí) Function Component 的第二個(gè)攔路虎,理解了它,才深入理解了 Function Component 的渲染原理。

首先介紹一下引入的新概念,useEffect 函數(shù)的返回值。它的返回值是一個(gè)函數(shù),這個(gè)函數(shù)在 useEffect 即將重新執(zhí)行時(shí),會(huì)先執(zhí)行上一次 Rerender useEffect 第一個(gè)回調(diào)的返回函數(shù),再執(zhí)行下一次渲染的 useEffect 第一個(gè)回調(diào)。

以兩次連續(xù)渲染為例介紹,展開(kāi)后的效果是這樣的:

第一次渲染:

function Counter() {
  useEffect(() => {
    // 第一次渲染完畢后執(zhí)行
    // 最終執(zhí)行順序:1
    return () => {
      // 由于沒(méi)有填寫依賴項(xiàng),所以第二次渲染 useEffect 會(huì)再次執(zhí)行,在執(zhí)行前,第一次渲染中這個(gè)地方的回調(diào)函數(shù)會(huì)首先被調(diào)用
      // 最終執(zhí)行順序:2
    }
  });

  return ...
}

第二次渲染:

function Counter() {
  useEffect(() => {
    // 第二次渲染完畢后執(zhí)行
    // 最終執(zhí)行順序:3
    return () => {
      // 依此類推
    }
  });

  return ...
}

然而本 Demo 將 useEffect 的第二個(gè)參數(shù)設(shè)置為了 [],那么其返回函數(shù)只會(huì)在這個(gè)組件被銷毀時(shí)執(zhí)行。

讀懂了前面的例子,應(yīng)該能想到,這個(gè) Demo 希望利用 [] 依賴,將 useEffect 當(dāng)作 didMount 使用,再結(jié)合 setInterval 每次時(shí) count 自增,這樣期望將 count 的值每秒自增 1。

然而結(jié)果是:

1
1
1
...

理解了 setTimeout 例子的讀者應(yīng)該可以自行推導(dǎo)出原因:setInterval 永遠(yuǎn)在第一次 Render 的閉包中,count 的值永遠(yuǎn)是 0,也就是等價(jià)于:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(0 + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return 

{count}

; }

然而罪魁禍?zhǔn)拙褪?沒(méi)有對(duì)依賴誠(chéng)實(shí) 導(dǎo)致的。例子中 useEffect 明明依賴了 count,依賴項(xiàng)卻非要寫 [],所以產(chǎn)生了很難理解的錯(cuò)誤。

所以改正的辦法就是 對(duì)依賴誠(chéng)實(shí)

永遠(yuǎn)對(duì)依賴項(xiàng)誠(chéng)實(shí)

一旦我們對(duì)依賴誠(chéng)實(shí)了,就可以得到正確的效果:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]);

  return 

{count}

; }

我們將 count 作為了 useEffect 的依賴項(xiàng),就得到了正確的結(jié)果:

1
2
3
...

既然漏寫依賴的風(fēng)險(xiǎn)這么大,自然也有保護(hù)措施,那就是 eslint-plugin-react-hooks 這個(gè)插件,會(huì)自動(dòng)訂正你的代碼中的依賴,想不對(duì)依賴誠(chéng)實(shí)都不行!

然而對(duì)這個(gè)例子而言,代碼依然存在 BUG:每次計(jì)數(shù)器都會(huì)重新實(shí)例化,如果換成其他費(fèi)事操作,性能成本將不可接受。

如何不在每次渲染時(shí)重新實(shí)例化 setInterval?

最簡(jiǎn)單的辦法,就是利用 useState 的第二種賦值用法,不直接依賴 count,而是以函數(shù)回調(diào)方式進(jìn)行賦值:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return 

{count}

; }

這這寫法真正做到了:

不依賴 count,所以對(duì)依賴誠(chéng)實(shí)。

依賴項(xiàng)為 [],只有初始化會(huì)對(duì) setInterval 進(jìn)行實(shí)例化。

而之所以輸出還是正確的 1 2 3 ...,原因是 setCount 的回調(diào)函數(shù)中,c 值永遠(yuǎn)指向最新的 count 值,因此沒(méi)有邏輯漏洞。

但是聰明的同學(xué)仔細(xì)一想,就會(huì)發(fā)現(xiàn)一個(gè)新問(wèn)題:如果存在兩個(gè)以上變量需要使用時(shí),這招就沒(méi)有用武之地了。

同時(shí)使用兩個(gè)以上變量時(shí)?

如果同時(shí)需要對(duì) countstep 兩個(gè)變量做累加,那 useEffect 的依賴必然要寫上一種某一個(gè)值,頻繁實(shí)例化的問(wèn)題就又出現(xiàn)了:

function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + step);
    }, 1000);
    return () => clearInterval(id);
  }, [step]);

  return 

{count}

; }

這個(gè)例子中,由于 setCount 只能拿到最新的 count 值,而為了每次都拿到最新的 step 值,就必須將 step 申明到 useEffect 依賴中,導(dǎo)致 setInterval 被頻繁實(shí)例化。

這個(gè)問(wèn)題自然也困擾了 React 團(tuán)隊(duì),所以他們拿出了一個(gè)新的 Hook 解決問(wèn)題:useReducer。

什么是 useReducer

先別聯(lián)想到 Redux。只考慮上面的場(chǎng)景,看看為什么 React 團(tuán)隊(duì)要將 useReducer 列為內(nèi)置 Hooks 之一。

先介紹一下 useReducer 的用法:

const [state, dispatch] = useReducer(reducer, initialState);

useReducer 返回的結(jié)構(gòu)與 useState 很像,只是數(shù)組第二項(xiàng)是 dispatch,而接收的參數(shù)也有兩個(gè),初始值放在第二位,第一位就是 reducer。

reducer 定義了如何對(duì)數(shù)據(jù)進(jìn)行變換,比如一個(gè)簡(jiǎn)單的 reducer 如下:

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return {
        ...state,
        count: state.count + 1
      };
    default:
      return state;
  }
}

這樣就可以通過(guò)調(diào)用 dispatch({ type: "increment" }) 的方式實(shí)現(xiàn) count 自增了。

那么回到這個(gè)例子,我們只需要稍微改寫一下用法即可:

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: "tick" });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return 

{count}

; } function reducer(state, action) { switch (action.type) { case "tick": return { ...state, count: state.count + state.step }; } }

可以看到,我們通過(guò) reducertick 類型完成了對(duì) count 的累加,而在 useEffect 的函數(shù)中,竟然完全繞過(guò)了 count、step 這兩個(gè)變量。所以 useReducer 也被稱為解決此類問(wèn)題的 “黑魔法”。

其實(shí)不管被怎么稱呼也好,其本質(zhì)是讓函數(shù)與數(shù)據(jù)解耦,函數(shù)只管發(fā)出指令,而不需要關(guān)心使用的數(shù)據(jù)被更新時(shí),需要重新初始化自身。

仔細(xì)的讀者會(huì)發(fā)現(xiàn)這個(gè)例子還是有一個(gè)依賴的,那就是 dispatch,然而 dispatch 引用永遠(yuǎn)也不會(huì)變,因此可以忽略它的影響。這也體現(xiàn)了無(wú)論如何都要對(duì)依賴保持誠(chéng)實(shí)。

這也引發(fā)了另一個(gè)注意項(xiàng):盡量將函數(shù)寫在 useEffect 內(nèi)部

將函數(shù)寫在 useEffect 內(nèi)部

為了避免遺漏依賴,必須將函數(shù)寫在 useEffect 內(nèi)部,這樣 eslint-plugin-react-hooks 才能通過(guò)靜態(tài)分析補(bǔ)齊依賴項(xiàng):

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    function getFetchUrl() {
      return "https://v?query=" + count;
    }

    getFetchUrl();
  }, [count]);

  return 

{count}

; }

getFetchUrl 這個(gè)函數(shù)依賴了 count,而如果將這個(gè)函數(shù)定義在 useEffect 外部,無(wú)論是機(jī)器還是人眼都難以看出 useEffect 的依賴項(xiàng)包含 count。

然而這就引發(fā)了一個(gè)新問(wèn)題:將所有函數(shù)都寫在 useEffect 內(nèi)部豈不是非常難以維護(hù)?

如何將函數(shù)抽到 useEffect 外部?

為了解決這個(gè)問(wèn)題,我們要引入一個(gè)新的 Hook:useCallback,它就是解決將函數(shù)抽到 useEffect 外部的問(wèn)題。

我們先看 useCallback 的用法:

function Counter() {
  const [count, setCount] = useState(0);

  const getFetchUrl = useCallback(() => {
    return "https://v?query=" + count;
  }, [count]);

  useEffect(() => {
    getFetchUrl();
  }, [getFetchUrl]);

  return 

{count}

; }

可以看到,useCallback 也有第二個(gè)參數(shù) - 依賴項(xiàng),我們將 getFetchUrl 函數(shù)的依賴項(xiàng)通過(guò) useCallback 打包到新的 getFetchUrl 函數(shù)中,那么 useEffect 就只需要依賴 getFetchUrl 這個(gè)函數(shù),就實(shí)現(xiàn)了對(duì) count 的間接依賴。

換句話說(shuō),我們利用了 useCallbackgetFetchUrl 函數(shù)抽到了 useEffect 外部。

為什么 useCallbackcomponentDidUpdate 更好用

回憶一下 Class Component 的模式,我們是如何在函數(shù)參數(shù)變化時(shí)進(jìn)行重新取數(shù)的:

class Parent extends Component {
  state = {
    count: 0,
    step: 0
  };
  fetchData = () => {
    const url =
      "https://v?query=" + this.state.count + "&step=" + this.state.step;
  };
  render() {
    return ;
  }
}

class Child extends Component {
  state = {
    data: null
  };
  componentDidMount() {
    this.props.fetchData();
  }
  componentDidUpdate(prevProps) {
    if (
      this.props.count !== prevProps.count &&
      this.props.step !== prevProps.step // 別漏了!
    ) {
      this.props.fetchData();
    }
  }
  render() {
    // ...
  }
}

上面的代碼經(jīng)常用 Class Component 的人應(yīng)該很熟悉,然而暴露的問(wèn)題可不小。

我們需要理解 props.count props.stepprops.fetchData 函數(shù)使用了,因此在 componentDidUpdate 時(shí),判斷這兩個(gè)參數(shù)發(fā)生了變化就觸發(fā)重新取數(shù)。

然而問(wèn)題是,這種理解成本是不是過(guò)高了?如果父級(jí)函數(shù) fetchData 不是我寫的,在不讀源碼的情況下,我怎么知道它依賴了 props.countprops.step 呢?更嚴(yán)重的是,如果某一天 fetchData 多依賴了 params 這個(gè)參數(shù),下游函數(shù)將需要全部在 componentDidUpdate 覆蓋到這個(gè)邏輯,否則 params 變化時(shí)將不會(huì)重新取數(shù)。可以想象,這種方式維護(hù)成本巨大,甚至可以說(shuō)幾乎無(wú)法維護(hù)。

換成 Function Component 的思維吧!試著用上剛才提到的 useCallback 解決問(wèn)題:

function Parent() {
  const [ count, setCount ] = useState(0);
  const [ step, setStep ] = useState(0);

  const fetchData = useCallback(() => {
    const url = "https://v/search?query=" + count + "&step=" + step;
  }, [count, step])

  return (
    
  )
}

function Child(props) {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
}

可以看出來(lái),當(dāng) fetchData 的依賴變化后,按下保存鍵,eslint-plugin-react-hooks 會(huì)自動(dòng)補(bǔ)上更新后的依賴,而下游的代碼不需要做任何改變,下游只需要關(guān)心依賴了 fetchData 這個(gè)函數(shù)即可,至于這個(gè)函數(shù)依賴了什么,已經(jīng)封裝在 useCallback 后打包透?jìng)飨聛?lái)了。

不僅解決了維護(hù)性問(wèn)題,而且對(duì)于 只要參數(shù)變化,就重新執(zhí)行某邏輯,是特別適合用 useEffect 做的,使用這種思維思考問(wèn)題會(huì)讓你的代碼更 “智能”,而使用分裂的生命周期進(jìn)行思考,會(huì)讓你的代碼四分五裂,而且容易漏掉各種時(shí)機(jī)。

useEffect 對(duì)業(yè)務(wù)的抽象非常方便,筆者舉幾個(gè)例子:

依賴項(xiàng)是查詢參數(shù),那么 useEffect 內(nèi)可以進(jìn)行取數(shù)請(qǐng)求,那么只要查詢參數(shù)變化了,列表就會(huì)自動(dòng)取數(shù)刷新。注意我們將取數(shù)時(shí)機(jī)從觸發(fā)端改成了接收端。

當(dāng)列表更新后,重新注冊(cè)一遍拖拽響應(yīng)事件。也是同理,依賴參數(shù)是列表,只要列表變化,拖拽響應(yīng)就會(huì)重新初始化,這樣我們可以放心的修改列表,而不用擔(dān)心拖拽事件失效。

只要數(shù)據(jù)流某個(gè)數(shù)據(jù)變化,頁(yè)面標(biāo)題就同步修改。同理,也不需要在每次數(shù)據(jù)變化時(shí)修改標(biāo)題,而是通過(guò) useEffect “監(jiān)聽(tīng)” 數(shù)據(jù)的變化,這是一種 “控制反轉(zhuǎn)” 的思維。

說(shuō)了這么多,其本質(zhì)還是利用了 useCallback 將函數(shù)獨(dú)立抽離到 useEffect 外部。

那么進(jìn)一步思考,可以將函數(shù)抽離到整個(gè)組件的外部嗎?

這也是可以的,需要靈活運(yùn)用自定義 Hooks 實(shí)現(xiàn)。

將函數(shù)抽到組件外部

以上面的 fetchData 函數(shù)為例,如果要抽到整個(gè)組件的外部,就不是利用 useCallback 做到了,而是利用自定義 Hooks 來(lái)做:

function useFetch(count, step) {
  return useCallback(() => {
    const url = "https://v/search?query=" + count + "&step=" + step;
  }, [count, step]);
}

可以看到,我們將 useCallback 打包搬到了自定義 Hook useFetch 中,那么函數(shù)中只需要一行代碼就能實(shí)現(xiàn)一樣的效果了:

function Parent() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const [other, setOther] = useState(0);
  const fetch = useFetch(count, step); // 封裝了 useFetch

  useEffect(() => {
    fetch();
  }, [fetch]);

  return (
    
); }

隨著使用越來(lái)越方便,我們可以將精力放到性能上。觀察可以發(fā)現(xiàn),countstep 都會(huì)頻繁變化,每次變化就會(huì)導(dǎo)致 useFetchuseCallback 依賴的變化,進(jìn)而導(dǎo)致重新生成函數(shù)。然而實(shí)際上這種函數(shù)是沒(méi)必要每次都重新生成的,反復(fù)生成函數(shù)會(huì)造成大量性能損耗。

換一個(gè)例子就可以看得更清楚:

function Parent() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const [other, setOther] = useState(0);
  const drag = useDraggable(count, step); // 封裝了拖拽函數(shù)
}

假設(shè)我們使用 Sortablejs 對(duì)某個(gè)區(qū)域進(jìn)行拖拽監(jiān)聽(tīng),這個(gè)函數(shù)每次都重復(fù)執(zhí)行的性能損耗非常大,然而這個(gè)函數(shù)內(nèi)部可能因?yàn)閮H僅要上報(bào)一些日志,所以依賴了沒(méi)有實(shí)際被使用的 count step 變量:

function useDraggable(count, step) {
  return useCallback(() => {
    // 上報(bào)日志
    report(count, step);

    // 對(duì)區(qū)域進(jìn)行初始化,非常耗時(shí)
    // ... 省略耗時(shí)代碼
  }, [count, step]);
}

這種情況,函數(shù)的依賴就特別不合理。雖然依賴變化應(yīng)該觸發(fā)函數(shù)重新執(zhí)行,但如果函數(shù)重新執(zhí)行的成本非常高,而依賴只是可有可無(wú)的點(diǎn)綴,得不償失。

利用 Ref 保證耗時(shí)函數(shù)依賴不變

一種辦法是通過(guò)將依賴轉(zhuǎn)化為 Ref:

function useFetch(count, step) {
  const countRef = useRef(count);
  const stepRef = useRef(step);

  useEffect(() => {
    countRef.current = count;
    stepRef.current = step;
  });

  return useCallback(() => {
    const url =
      "https://v/search?query=" + countRef.current + "&step=" + stepRef.current;
  }, [countRef, stepRef]); // 依賴不會(huì)變,卻能每次拿到最新的值
}

這種方式比較取巧,將需要更新的區(qū)域與耗時(shí)區(qū)域分離,再將需更新的內(nèi)容通過(guò) Ref 提供給耗時(shí)的區(qū)域,實(shí)現(xiàn)性能優(yōu)化。

然而這樣做對(duì)函數(shù)的改動(dòng)成本比較高,有一種更通用的做法解決此類問(wèn)題。

通用的自定義 Hooks 解決函數(shù)重新實(shí)例化問(wèn)題

我們可以利用 useRef 創(chuàng)造一個(gè)自定義 Hook 代替 useCallback,使其依賴的值變化時(shí),回調(diào)不會(huì)重新執(zhí)行,卻能拿到最新的值!

這個(gè)神奇的 Hook 寫法如下:

function useEventCallback(fn, dependencies) {
  const ref = useRef(null);

  useEffect(() => {
    ref.current = fn;
  }, [fn, ...dependencies]);

  return useCallback(() => {
    const fn = ref.current;
    return fn();
  }, [ref]);
}

再次體會(huì)到自定義 Hook 的無(wú)所不能。

首先看這一段:

useEffect(() => {
  ref.current = fn;
}, [fn, ...dependencies]);

當(dāng) fn 回調(diào)函數(shù)變化時(shí), ref.current 重新指向最新的 fn 這個(gè)邏輯中規(guī)中矩。重點(diǎn)是,當(dāng)依賴 dependencies 變化時(shí),也重新為 ref.current 賦值,此時(shí) fn 內(nèi)部的 dependencies 值是最新的,而下一段代碼:

return useCallback(() => {
  const fn = ref.current;
  return fn();
}, [ref]);

又僅執(zhí)行一次(ref 引用不會(huì)改變),所以每次都可以返回 dependencies 是最新的 fn,并且 fn 還不會(huì)重新執(zhí)行。

假設(shè)我們對(duì) useEventCallback 傳入的回調(diào)函數(shù)稱為 X,則這段代碼的含義,就是使每次渲染的閉包中,回調(diào)函數(shù) X 總是拿到的總是最新 Rerender 閉包中的那個(gè),所以依賴的值永遠(yuǎn)是最新的,而且函數(shù)不會(huì)重新初始化。

React 官方不推薦使用此范式,因此對(duì)于這種場(chǎng)景,利用 useReducer,將函數(shù)通過(guò) dispatch 中調(diào)用。 還記得嗎?dispatch 是一種可以繞過(guò)依賴的黑魔法,我們?cè)?“什么是 useReducer” 小節(jié)提到過(guò)。

隨著對(duì) Function Component 的使用,你也漸漸關(guān)心到函數(shù)的性能了,這很棒。那么下一個(gè)重點(diǎn)自然是關(guān)注 Render 的性能。

用 memo 做 PureRender

在 Fucntion Component 中,Class Component 的 PureComponent 等價(jià)的概念是 React.memo,我們介紹一下 memo 的用法:

const Child = memo((props) => {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
})

使用 memo 包裹的組件,會(huì)在自身重渲染時(shí),對(duì)每一個(gè) props 項(xiàng)進(jìn)行淺對(duì)比,如果引用沒(méi)有變化,就不會(huì)觸發(fā)重渲染。所以 memo 是一種很棒的性能優(yōu)化工具。

下面就介紹一個(gè)看似比 memo 難用,但真正理解后會(huì)發(fā)現(xiàn),其實(shí)比 memo 更好用的渲染優(yōu)化函數(shù):useMemo

用 useMemo 做局部 PureRender

相比 React.memo 這個(gè)異類,React.useMemo 可是正經(jīng)的官方 Hook:

const Child = (props) => {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return useMemo(() => (
    // ...
  ), [props.fetchData])
}

可以看到,我們利用 useMemo 包裹渲染代碼,這樣即便函數(shù) Child 因?yàn)?props 的變化重新執(zhí)行了,只要渲染函數(shù)用到的 props.fetchData 沒(méi)有變,就不會(huì)重新渲染。

這里發(fā)現(xiàn)了 useMemo 的第一個(gè)好處:更細(xì)粒度的優(yōu)化渲染。

所謂更細(xì)粒度的優(yōu)化渲染,是指函數(shù) Child 整體可能用到了 AB 兩個(gè) props,而渲染僅用到了 B,那么使用 memo 方案時(shí),A 的變化會(huì)導(dǎo)致重渲染,而使用 useMemo 的方案則不會(huì)。

useMemo 的好處還不止這些,這里先留下伏筆。我們先看一個(gè)新問(wèn)題:當(dāng)參數(shù)越來(lái)越多時(shí),使用 props 將函數(shù)、值在組件間傳遞非常冗長(zhǎng):

function Parent() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const fetchData = useFetch(count, step);

  return ;
}

雖然 Child 可以通過(guò) memouseMemo 進(jìn)行優(yōu)化,但當(dāng)程序復(fù)雜時(shí),可能存在多個(gè)函數(shù)在所有 Function Component 間共享的情況 ,此時(shí)就需要新 Hook: useContext 來(lái)拯救了。

使用 Context 做批量透?jìng)?/b>

在 Function Component 中,可以使用 React.createContext 創(chuàng)建一個(gè) Context:

const Store = createContext(null);

其中 null 是初始值,一般置為 null 也沒(méi)關(guān)系。接下來(lái)還有兩步,分別是在根節(jié)點(diǎn)使用 Store.Provider 注入,與在子節(jié)點(diǎn)使用官方 Hook useContext 拿到注入的數(shù)據(jù):

在根節(jié)點(diǎn)使用 Store.Provider 注入:

function Parent() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const fetchData = useFetch(count, step);

  return (
    
      
    
  );
}

在子節(jié)點(diǎn)使用 useContext 拿到注入的數(shù)據(jù)(也就是拿到 Store.Providervalue):

const Child = memo((props) => {
  const { setCount } = useContext(Store)

  function onClick() {
    setCount(count => count + 1)
  }

  return (
    // ...
  )
})

這樣就不需要在每個(gè)函數(shù)間進(jìn)行參數(shù)透?jìng)髁?,公共函?shù)可以都放在 Context 里。

但是當(dāng)函數(shù)多了,Providervalue 會(huì)變得很臃腫,我們可以結(jié)合之前講到的 useReducer 解決這個(gè)問(wèn)題。

使用 useReducer 為 Context 傳遞內(nèi)容瘦身

使用 useReducer,所有回調(diào)函數(shù)都通過(guò)調(diào)用 dispatch 完成,那么 Context 只要傳遞 dispatch 一個(gè)函數(shù)就好了:

const Store = createContext(null);

function Parent() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 0 });

  return (
    
      
    
  );
}

這下無(wú)論是根節(jié)點(diǎn)的 Provider,還是子元素調(diào)用都清爽很多:

const Child = useMemo((props) => {
  const dispatch = useContext(Store)

  function onClick() {
    dispatch({
      type: "countInc"
    })
  }

  return (
    // ...
  )
})

你也許很快就想到,將 state 也通過(guò) Provider 注入進(jìn)去豈不更妙?是的,但此處請(qǐng)務(wù)必注意潛在性能問(wèn)題。

state 也放到 Context 中

稍稍改造下,將 state 也放到 Context 中,這下賦值與取值都非常方便了!

const Store = createContext(null);

function Parent() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 0 });

  return (
    
      
      
    
  );
}

對(duì) Count Step 這兩個(gè)子元素而言,可需要謹(jǐn)慎一些,假如我們這么實(shí)現(xiàn)這兩個(gè)子元素:

const Count = memo(() => {
  const { state, dispatch } = useContext(Store);
  return (
    
  );
});

const Step = memo(() => {
  const { state, dispatch } = useContext(Store);
  return (
    
  );
});

其結(jié)果是:無(wú)論點(diǎn)擊 incCount 還是 incStep,都會(huì)同時(shí)觸發(fā)這兩個(gè)組件的 Rerender。

其問(wèn)題在于:memo 只能擋在最外層的,而通過(guò) useContext 的數(shù)據(jù)注入發(fā)生在函數(shù)內(nèi)部,會(huì) 繞過(guò) memo。

當(dāng)觸發(fā) dispatch 導(dǎo)致 state 變化時(shí),所有使用了 state 的組件內(nèi)部都會(huì)強(qiáng)制重新刷新,此時(shí)想要對(duì)渲染次數(shù)做優(yōu)化,只有拿出 useMemo 了!

useMemo 配合 useContext

使用 useContext 的組件,如果自身不使用 props,就可以完全使用 useMemo 代替 memo 做性能優(yōu)化:

const Count = () => {
  const { state, dispatch } = useContext(Store);
  return useMemo(
    () => (
      
    ),
    [state.count, dispatch]
  );
};

const Step = () => {
  const { state, dispatch } = useContext(Store);
  return useMemo(
    () => (
      
    ),
    [state.step, dispatch]
  );
};

對(duì)這個(gè)例子來(lái)說(shuō),點(diǎn)擊對(duì)應(yīng)的按鈕,只有使用到的組件才會(huì)重渲染,效果符合預(yù)期。 結(jié)合 eslint-plugin-react-hooks 插件使用,連 useMemo 的第二個(gè)參數(shù)依賴都是自動(dòng)補(bǔ)全的。

讀到這里,不知道你是否聯(lián)想到了 Redux 的 Connect?

我們來(lái)對(duì)比一下 ConnectuseMemo,會(huì)發(fā)現(xiàn)驚人的相似之處。

一個(gè)普通的 Redux 組件:

const mapStateToProps = state => (count: state.count);

const mapDispatchToProps = dispatch => dispatch;

@Connect(mapStateToProps, mapDispatchToProps)
class Count extends React.PureComponent {
  render() {
    return (
      
    );
  }
}

一個(gè)普通的 Function Component 組件:

const Count = () => {
  const { state, dispatch } = useContext(Store);
  return useMemo(
    () => (
      
    ),
    [state.count, dispatch]
  );
};

這兩段代碼的效果完全一樣,F(xiàn)unction Component 除了更簡(jiǎn)潔之外,還有一個(gè)更大的優(yōu)勢(shì):全自動(dòng)的依賴推導(dǎo)

Hooks 誕生的一個(gè)原因,就是為了便于靜態(tài)分析依賴,簡(jiǎn)化 Immutable 數(shù)據(jù)流的使用成本。

我們看 Connect 的場(chǎng)景:

由于不知道子組件使用了哪些數(shù)據(jù),因此需要在 mapStateToProps 提前寫好,而當(dāng)需要使用數(shù)據(jù)流內(nèi)新變量時(shí),組件里是無(wú)法訪問(wèn)的,我們要回到 mapStateToProps 加上這個(gè)依賴,再回到組件中使用它。

useContext + useMemo 的場(chǎng)景:

由于注入的 state 是全量的,Render 函數(shù)中想用什么都可直接用,在按保存鍵時(shí),eslint-plugin-react-hooks 會(huì)通過(guò)靜態(tài)分析,在 useMemo 第二個(gè)參數(shù)自動(dòng)補(bǔ)上代碼里使用到的外部變量,比如 state.count、dispatch。

另外可以發(fā)現(xiàn),Context 很像 Redux,那么 Class Component 模式下的異步中間件實(shí)現(xiàn)的異步取數(shù)怎么利用 useReducer 做呢?答案是:做不到。

當(dāng)然不是說(shuō) Function Component 無(wú)法實(shí)現(xiàn)異步取數(shù),而是用的工具錯(cuò)了。

使用自定義 Hook 處理副作用

比如上面拋出的異步取數(shù)場(chǎng)景,在 Function Component 的最佳做法是封裝成一個(gè)自定義 Hook:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: "FETCH_INIT" });

      try {
        const result = await axios(url);
        if (!didCancel) {
          dispatch({ type: "FETCH_SUCCESS", payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: "FETCH_FAILURE" });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  const doFetch = url => setUrl(url);

  return { ...state, doFetch };
};

可以看到,自定義 Hook 擁有完整生命周期,我們可以將取數(shù)過(guò)程封裝起來(lái),只暴露狀態(tài) - 是否在加載中:isLoading 是否取數(shù)失?。?b>isError 數(shù)據(jù):data。

在組件中使用起來(lái)非常方便:

function App() {
  const { data, isLoading, isError } = useDataApi("https://v", {
    showLog: true
  });
}

如果這個(gè)值需要存儲(chǔ)到數(shù)據(jù)流,在所有組件之間共享,我們可以結(jié)合 useEffectuseReducer

function App(props) {
  const { dispatch } = useContext(Store);

  const { data, isLoading, isError } = useDataApi("https://v", {
    showLog: true
  });

  useEffect(() => {
    dispatch({
      type: "updateLoading",
      data,
      isLoading,
      isError
    });
  }, [dispatch, data, isLoading, isError]);
}

到此,F(xiàn)unction Component 的入門概念就講完了,最后附帶一個(gè)彩蛋:Function Component 的 DefaultProps 怎么處理?

Function Component 的 DefaultProps 怎么處理?

這個(gè)問(wèn)題看似簡(jiǎn)單,實(shí)則不然。我們至少有兩種方式對(duì) Function Component 的 DefaultProps 進(jìn)行賦值,下面一一說(shuō)明。

首先對(duì)于 Class Component,DefaultProps 基本上只有一種大家都認(rèn)可的寫法:

class Button extends React.PureComponent {
  defaultProps = { type: "primary", onChange: () => {} };
}

然而在 Function Component 就五花八門了。

利用 ES6 特性在參數(shù)定義階段賦值
function Button({ type = "primary", onChange = () => {} }) {}

這種方法看似很優(yōu)雅,其實(shí)有一個(gè)重大隱患:沒(méi)有命中的 props 在每次渲染引用都不同。

看這種場(chǎng)景:

const Child = memo(({ type = { a: 1 } }) => {
  useEffect(() => {
    console.log("type", type);
  }, [type]);

  return 
Child
; });

只要 type 的引用不變,useEffect 就不會(huì)頻繁的執(zhí)行。現(xiàn)在通過(guò)父元素刷新導(dǎo)致 Child 跟著刷新,我們發(fā)現(xiàn),每次渲染都會(huì)打印出日志,也就意味著每次渲染時(shí),type 的引用是不同的。

有一種不太優(yōu)雅的方式可以解決:

const defaultType = { a: 1 };

const Child = ({ type = defaultType }) => {
  useEffect(() => {
    console.log("type", type);
  }, [type]);

  return 
Child
; };

此時(shí)不斷刷新父元素,只會(huì)打印出一次日志,因?yàn)?type 的引用是相同的。

我們使用 DefaultProps 的本意必然是希望默認(rèn)值的引用相同, 如果不想多帶帶維護(hù)變量的引用,還可以借用 React 內(nèi)置的 defaultProps 方法解決。

利用 React 內(nèi)置方案

React 內(nèi)置方案能較好的解決引用頻繁變動(dòng)的問(wèn)題:

const Child = ({ type }) => {
  useEffect(() => {
    console.log("type", type);
  }, [type]);

  return 
Child
; }; Child.defaultProps = { type: { a: 1 } };

上面的例子中,不斷刷新父元素,只會(huì)打印出一次日志。

因此建議對(duì)于 Function Component 的參數(shù)默認(rèn)值,建議使用 React 內(nèi)置方案解決,因?yàn)榧兒瘮?shù)的方案不利于保持引用不變。

最后補(bǔ)充一個(gè)父組件 “坑” 子組件的經(jīng)典案例。

不要坑了子組件

我們做一個(gè)點(diǎn)擊累加的按鈕作為父組件,那么父組件每次點(diǎn)擊后都會(huì)刷新:

function App() {
  const [count, forceUpdate] = useState(0);

  const schema = { b: 1 };

  return (
    
forceUpdate(count + 1)}>Count {count}
); }

另外我們將 schema = { b: 1 } 傳遞給子組件,這個(gè)就是埋的一個(gè)大坑。

子組件的代碼如下:

const Child = memo(props => {
  useEffect(() => {
    console.log("schema", props.schema);
  }, [props.schema]);

  return 
Child
; });

只要父級(jí) props.schema 變化就會(huì)打印日志。結(jié)果自然是,父組件每次刷新,子組件都會(huì)打印日志,也就是 子組件 [props.schema] 完全失效了,因?yàn)橐靡恢痹谧兓?/strong>

其實(shí) 子組件關(guān)心的是值,而不是引用,所以一種解法是改寫子組件的依賴:

const Child = memo(props => {
  useEffect(() => {
    console.log("schema", props.schema);
  }, [JSON.stringify(props.schema)]);

  return 
Child
; });

這樣可以保證子組件只渲染一次。

可是真正罪魁禍?zhǔn)资歉附M件,我們需要利用 Ref 優(yōu)化一下父組件:

function App() {
  const [count, forceUpdate] = useState(0);
  const schema = useRef({ b: 1 });

  return (
    
forceUpdate(count + 1)}>Count {count}
); }

這樣 schema 的引用能一直保持不變。如果你完整讀完了本文,應(yīng)該可以充分理解第一個(gè)例子的 schema 在每個(gè)渲染快照中都是一個(gè)新的引用,而 Ref 的例子中,schema 在每個(gè)渲染快照中都只有一個(gè)唯一的引用。

3. 總結(jié)

所以使用 Function Component 你入門了嗎?

本次精讀留下的思考題是:Function Component 開(kāi)發(fā)過(guò)程中還有哪些容易犯錯(cuò)誤的細(xì)節(jié)?

討論地址是:精讀《Function Component 入門》 · Issue #157 · dt-fe/weekly

如果你想?yún)⑴c討論,請(qǐng) 點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀 - 幫你篩選靠譜的內(nèi)容。

關(guān)注 前端精讀微信公眾號(hào)

special Sponsors

DevOps 全流程平臺(tái)

版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享 3.0 許可證)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/109865.html

相關(guān)文章

  • 精讀《Vue3.0 Function API》

    摘要:拿到的都是而不是原始值,且這個(gè)值會(huì)動(dòng)態(tài)變化。精讀對(duì)于的與,筆者做一些對(duì)比。因此采取了作為優(yōu)化方案只有當(dāng)?shù)诙€(gè)依賴參數(shù)變化時(shí)才返回新引用。不需要使用等進(jìn)行性能優(yōu)化,所有性能優(yōu)化都是自動(dòng)的。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 Vue 3.0 的發(fā)布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細(xì)了解一下 Vue 團(tuán)隊(duì)是怎么想的吧! 首先官方回答了幾個(gè)最受關(guān)注的...

    voyagelab 評(píng)論0 收藏0
  • 精讀Function VS Class 組件》

    摘要:未來(lái)可能成為官方之一。討論地址是精讀組件如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 為什么要了解 Function 寫法的組件呢?因?yàn)樗谧兊迷絹?lái)越重要。 那么 React 中 Function Component 與 Class Component 有何不同? how-are-function-components-di...

    FWHeart 評(píng)論0 收藏0
  • 精讀《react-easy-state 源碼》

    摘要:會(huì)自動(dòng)觸發(fā)函數(shù)內(nèi)回調(diào)函數(shù)的執(zhí)行。因此利用并將依賴置為使代碼在所有渲染周期內(nèi),只在初始化執(zhí)行一次。同時(shí)代碼里還對(duì)等公共方法進(jìn)行了包裝,讓這些回調(diào)函數(shù)中自帶效果。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 react-easy-state 是個(gè)比較有趣的庫(kù),利用 Proxy 創(chuàng)建了一個(gè)非常易用的全局?jǐn)?shù)據(jù)流管理方式。 import React from react; import { stor...

    curlyCheng 評(píng)論0 收藏0
  • 精讀《Epitath 源碼 - renderProps 新用法》

    摘要:精讀源碼一共行,我們分析一下其精妙的方式。更多討論討論地址是精讀新用法如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀幫你篩選靠譜的內(nèi)容。 1 引言 很高興這一期的話題是由 epitath 的作者 grsabreu 提供的。 前端發(fā)展了 20 多年,隨著發(fā)展中國(guó)家越來(lái)越多的互聯(lián)網(wǎng)從業(yè)者涌入,現(xiàn)在前端知識(shí)玲瑯滿足,概念、庫(kù)也越來(lái)越多。雖然內(nèi)容越來(lái)越多,但作為個(gè)體的...

    Magicer 評(píng)論0 收藏0
  • 精讀《useEffect 完全指南》

    摘要:引言工具型文章要跳讀,而文學(xué)經(jīng)典就要反復(fù)研讀。原文非常長(zhǎng),所以概述是筆者精簡(jiǎn)后的。這是理解以及的關(guān)鍵,后面還會(huì)詳細(xì)介紹。從幾個(gè)疑問(wèn)開(kāi)始假設(shè)讀者有比較豐富的前端開(kāi)發(fā)經(jīng)驗(yàn),并且寫過(guò)一些。 1. 引言 工具型文章要跳讀,而文學(xué)經(jīng)典就要反復(fù)研讀。如果說(shuō) React 0.14 版本帶來(lái)的各種生命周期可以類比到工具型文章,那么 16.7 帶來(lái)的 Hooks 就要像文學(xué)經(jīng)典一樣反復(fù)研讀。 Hooks...

    April 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<