React hook | 自定义hook解决闭包问题

614 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

问题:

在下面的代码中定时器没有获取到最新的state
定时器中的count会一直都是0
因为定时器一直引用着旧的变量count
这也是闭包引起的问题 其实也可以侧面反映出useState返回的state并不是同一个引用

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      // 下面的count 会一直为0,因为定时器一直引用着旧的变量。
      // 
      console.log(count);
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

闭包

闭包是什么大家肯定都知道了,就是对函数作用域的引用,在函数外部可以访问到函数作用域,比如下面 这个例子
module函数返回了一个新的函数,当我们调用返回的函数export就可以得到module函数里面的变量了

这也是js模块化的实现方式之一

function module() {
  const a = 1
  return function () {
    return a
  };
}

const export = module()
const res = export() // a

解决

那么知道问题,就很好解决了 我们可以使用useRef来创建一个引用,只要state更新,就去更新引用的值,这样,在setInterval的函数内就可以保持一个引用,而获取到最新的值了

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(null);
  
  useEffect(() => {
     // 更新引用值,
    countRef.current = count;
  }, [count])
  
  useEffect(() => {
    const timer = setInterval(() => {
      // 这里的引用值会定期更新
      console.log(count, countRef.current, " == count, countRef.current");
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

提取hook

我们可以把上面的代码封装成一个hook,叫useRefState

function useRefState(initialState) {
  const [state, setState] = useState(initialState);
  const stateRef = useRef(null);
  useEffect(() => {
    stateRef.current = state;
  }, [state]);
  return [state, setState, stateRef]; // 喜欢的话也可以返回具名的键值对 {state ...}
}


function App() {
  const [count, setCount, countRef] = useRefState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      // 这里的引用值会定期更新
      console.log(count, countRef.current, " == count, countRef.current");
      setCount(precount => precount + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div className="App">{count}</div>;
}

代码来源自己的博客 github.com/lulusir/my-…


其他文章 在线等,后端悄悄改了接口文档被我抓住了怎么办?
和后端对线 | 前端如何保存base64字符串为文件
释放生产力 | Yapi,swagger2,swagger3生成请求代码
什么?在React中也可以使用vue响应式状态管理
clean-js | 在hooks的时代下,使用class管理你的状态
clean-js | 手把手教你写一个羊了个羊麻将版
有没有一种可能,你从来都没有真正理解async