成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Ahooks 是怎么解決 React 的閉包問題的?

開發 前端
useEffect 接收了兩個參數,一個回調函數和一個數組。數組里面就是 useEffect 的依賴,當為 [] 的時候,回調函數只會在組件第一次渲染的時候執行一次。如果有依賴其他項,react 會判斷其依賴是否改變,如果改變了就會執行回調函數。

本文來探索一下 ahooks 是怎么解決 React 的閉包問題的?

React 的閉包問題

先來看一個例子:

import React, { useState, useEffect } from "react";

export default () => {
const [count, setCount] = useState(0);

useEffect(() => {
setInterval(() => {
console.log("setInterval:", count);
}, 1000);
}, []);

return (
<div>
count: {count}
<br />
<button onClick={() => setCount((val) => val + 1)}>增加 1</button>
</div>
);
};

代碼示例[4]

當我點擊按鈕的時候,發現 setInterval 中打印出來的值并沒有發生變化,始終都是 0。這就是 React 的閉包問題。

圖片

產生的原因

為了維護 Function Component 的 state,React 用鏈表的方式來存儲 Function Component 里面的 hooks,并為每一個 hooks 創建了一個對象。

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

useEffect(() => {
setInterval(() => {
console.log("setInterval:", count);
}, 1000);
}, []);

這個對象的 memoizedState 屬性就是用來存儲組件上一次更新后的 state,next 指向下一個 hook 對象。在組件更新的過程中,hooks 函數執行的順序是不變的,就可以根據這個鏈表拿到當前 hooks 對應的 Hook 對象,函數式組件就是這樣擁有了state的能力。

同時制定了一系列的規則,比如不能將 hooks 寫入到 if...else... 中。從而保證能夠正確拿到相應 hook 的 state。

useEffect 接收了兩個參數,一個回調函數和一個數組。數組里面就是 useEffect 的依賴,當為 [] 的時候,回調函數只會在組件第一次渲染的時候執行一次。如果有依賴其他項,react 會判斷其依賴是否改變,如果改變了就會執行回調函數。

回到剛剛那個例子:

// 解決方法一
useEffect(() => {
if (timer.current) {
clearInterval(timer.current);
}
timer.current = setInterval(() => {
console.log("setInterval:", count);
}, 1000);
}, [count]);

它第一次執行的時候,執行 useState,count 為 0。執行 useEffect,執行其回調中的邏輯,啟動定時器,每隔 1s 輸出 setInterval: 0。

當我點擊按鈕使 count 增加 1 的時候,整個函數式組件重新渲染,這個時候前一個執行的鏈表已經存在了。useState 將 Hook 對象 上保存的狀態置為 1, 那么此時 count 也為 1 了。執行 useEffect,其依賴項為空,不執行回調函數。但是之前的回調函數還是在的,它還是會每隔 1s 執行 console.log("setInterval:", count);,但這里的 count 是之前第一次執行時候的 count 值,因為在定時器的回調函數里面被引用了,形成了閉包一直被保存。

解決的方法

解決方法一:給 useEffect 設置依賴項,重新執行函數,設置新的定時器,拿到最新值。

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

useEffect(() => {
setInterval(() => {
console.log("setInterval:", count);
}, 1000);
}, []);

解決方法二:使用 useRef。useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(initialValue)。

useRef 創建的是一個普通 Javascript 對象,而且會在每次渲染時返回同一個 ref 對象,當我們變化它的 current 屬性的時候,對象的引用都是同一個,所以定時器中能夠讀到最新的值。

const lastCount = useRef(count);

// 解決方法二
useEffect(() => {
setInterval(() => {
console.log("setInterval:", lastCount.current);
}, 1000);
}, []);

return (
<div>
count: {count}
<br />
<button
onClick={() => {
setCount((val) => val + 1);
// +1
lastCount.current += 1;
}}
>
增加 1
</button>
</div>
);

useRef => useLatest

終于回到我們 ahooks 主題,基于上述的第二種解決方案,useLatest 這個 hook 隨之誕生。它返回當前最新值的 Hook,可以避免閉包問題。實現原理很簡單,只有短短的十行代碼,就是使用 useRef 包一層:

import { useRef } from 'react';
// 通過 useRef,保持每次獲取到的都是最新的值
function useLatest<T>(value: T) {
const ref = useRef(value);
ref.current = value;

return ref;
}

export default useLatest;

useEvent => useMemoizedFn

React 中另一個場景,是基于 useCallback 的。

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

const callbackFn = useCallback(() => {
console.log(`Current count is ${count}`);
}, []);

以上不管,我們的 count 的值變化成多少,執行 callbackFn 打印出來的 count 的值始終都是 0。這個是因為回調函數被 useCallback 緩存,形成閉包,從而形成閉包陷阱。

那我們怎么解決這個問題呢?官方提出了 useEvent。它解決的問題:如何同時保持函數引用不變與訪問到最新狀態。使用它之后,上面的例子就變成了。

const callbackFn = useEvent(() => {
console.log(`Current count is ${count}`);
});

在這里我們不細看這個特性,實際上,在 ahooks 中已經實現了類似的功能,那就是 useMemoizedFn。

useMemoizedFn 是持久化 function 的 Hook,理論上,可以使用 useMemoizedFn 完全代替 useCallback。使用 useMemoizedFn,可以省略第二個參數 deps,同時保證函數地址永遠不會變化。以上的問題,通過以下的方式就能輕松解決:

const memoizedFn = useMemoizedFn(() => {
console.log(`Current count is ${count}`);
});

Demo 地址[5]

我們來看下它的源碼,可以看到其還是通過 useRef 保持 function 引用地址不變,并且每次執行都可以拿到最新的 state 值。

function useMemoizedFn<T extends noop>(fn: T) {
// 通過 useRef 保持其引用地址不變,并且值能夠保持值最新
const fnRef = useRef<T>(fn);
fnRef.current = useMemo(() => fn, [fn]);
// 通過 useRef 保持其引用地址不變,并且值能夠保持值最新
const memoizedFn = useRef<PickFunction<T>>();
if (!memoizedFn.current) {
// 返回的持久化函數,調用該函數的時候,調用原始的函數
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args);
};
}

return memoizedFn.current as T;
}

總結與思考

React 自從引入 hooks,雖然解決了 class 組件的一些弊端,比如邏輯復用需要通過高階階段層層嵌套等。但是也引入了一些問題,比如閉包問題。

這個是 React 的 Function Component State 管理導致的,有時候會讓開發者產生疑惑。開發者可以通過添加依賴或者使用 useRef 的方式進行避免。

ahooks 也意識到了這個問題,通過 useLatest 保證獲取到最新的值和 useMemoizedFn 持久化 function 的方式,避免類似的閉包陷阱。

值得一提的是 useMemoizedFn 是 ahooks 輸出函數的標準,所有的輸出函數都使用 useMemoizedFn 包一層。另外輸入函數都使用 useRef 做一次記錄,以保證在任何地方都能訪問到最新的函數。

參考

從react hooks“閉包陷阱”切入,淺談react hooks[6]

React官方團隊出手,補齊原生Hook短板[7]

參考資料

[1]詳情: https://github.com/GpingFeng/hooks

[2]大家都能看得懂的源碼(一)ahooks 整體架構篇: https://juejin.cn/post/7105396478268407815

[3]如何使用插件化機制優雅的封裝你的請求hook : https://juejin.cn/post/7105733829972721677

[4]代碼示例: https://codesandbox.io/s/ji-chu-yong-fa-forked-wpk1s6?file=/App.tsx:0-487

[5]Demo 地址: https://codesandbox.io/s/ji-chu-yong-fa-forked-7pkp1r?file=/App.tsx

[6]從react hooks“閉包陷阱”切入,淺談react hooks: https://juejin.cn/post/6844904193044512782

[7]React官方團隊出手,補齊原生Hook短板: https://segmentfault.com/a/1190000041798153

責任編輯:武曉燕 來源: 前端雜貨鋪
相關推薦

2022-06-09 09:20:41

ahooksaxios

2024-01-08 08:35:28

閉包陷阱ReactHooks

2022-07-01 07:31:18

AhooksDOM場景

2011-05-23 13:54:04

閉包

2024-11-26 00:45:29

free區域字段

2022-05-04 10:38:58

React閉包組件

2021-10-26 13:18:52

Go底層函數

2022-06-28 07:41:38

useMountReactahooks

2019-11-07 21:51:18

閉包前端函數

2021-12-08 07:49:46

Ahooks 3.0React Hooks

2022-05-05 08:31:48

useRefuseEffecthook

2021-02-24 07:40:38

React Hooks閉包

2023-06-09 07:21:23

React前端框架

2024-01-22 09:51:32

Swift閉包表達式尾隨閉包

2009-07-22 07:43:00

Scala閉包

2023-01-09 08:00:41

JavaScript閉包

2022-06-10 08:01:01

ahookshook代碼

2021-01-13 11:25:12

JavaScript閉包函數

2010-06-17 10:22:47

PHP

2016-09-14 09:20:05

JavaScript閉包Web
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 福利视频二区 | 青青草在线视频免费观看 | 精品国产综合 | 亚洲精品欧美一区二区三区 | 午夜视频在线播放 | 成人h视频在线观看 | 欧美日韩国产精品一区 | 日韩欧美一区在线 | 91精品国产综合久久久动漫日韩 | 91视频在线观看 | 成年人在线播放 | 午夜av电影院 | 久久久精品网站 | 在线a视频网站 | jizz在线免费观看 | 亚洲视频手机在线 | 在线国产一区二区 | 亚欧午夜| 人人射人人 | 亚洲国产一区二区三区在线观看 | 韩日精品一区 | 干干干操操操 | 国产高清精品一区二区三区 | 操人网| 日日摸日日添日日躁av | 国产精品成人品 | 国产高清在线精品一区二区三区 | 久久精品1 | 欧美一级大片免费观看 | 丁香色婷婷 | 国产女人与拘做视频免费 | 国产精品久久久久久久白浊 | 欧美在线a | 精品欧美一区二区在线观看视频 | 在线观看视频一区二区三区 | 久久久激情视频 | 亚洲精品国产电影 | 日韩视频国产 | 亚州av | 欧美一区二区三区在线播放 | 91亚洲精华国产 |