https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Dan Abramov 文章很好地解释了如何使用钩子、状态和 API 的 setInterval() 类型!
丹·阿布拉莫夫!是React 维护团队的一员!众所周知,我个人爱他!
快速解释
问题是如何使用仅执行一次(第一次渲染)的 useEffect() 访问状态的问题!
简短的回答是:通过使用引用(useRef)!另一个 useEffect() 在需要更新时再次运行!或者在每次渲染时!
让我解释一下!并检查 Dan Abramov 解决方案!最后你会得到更好的上面的陈述!第二个例子与 setInterval() 无关!
=>
useEffect() 要么只运行一次,要么在每次渲染中运行!或者当依赖更新时(提供时)!
只有通过在每个相关时间运行和渲染的 useEffect() 才能访问状态!
或通过setState((state/*here the state*/) => <newStateExpression>)
但如果你想访问 useEffect() => 中的状态,则需要重新运行!意思是传递和执行新的回调!
这不适用于 setInterval!如果你每次都设置它!计数器重置!如果组件快速重新渲染,将导致不执行!
如果你只渲染一次!状态未更新!作为第一次运行,运行一个回调!并关闭!状态是固定的! useEffect(() => { <run once, state will stay the same> setInterval(() => { <state fixed> }) }, []).
对于所有这种情况!我们需要使用 useRef! (refs)!
保存一个保存状态的回调!来自每次重新渲染的 useEffect() !或者通过将状态值本身保存在 ref 中!视用途而定!
setInterval 的 Dan abramov 解决方案(简单而干净)
这就是你要找的东西!
useInteval 钩子(作者 Dan Abramov)
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
用法
import React, { useState, useEffect, useRef } from 'react';
function Counter() {
let [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>;
}
我们可以看到他如何在每次重新渲染时保存新的回调!包含新状态的回调!
使用!这是一个干净简单的钩子!好美啊!
请务必阅读 Dan 的文章!正如他解释并解决了很多事情!
setState()
Dan Abramov 在他的文章中提到了这一点!
如果我们需要设置状态!在一个 setInteral 内!可以简单地将 setState() 与回调版本一起使用!
useState(() => {
setInterval(() => {
setState((state/*we have the latest state*/) => {
// read and use state
return <newStateExpression>;
})
}, 1000);
}, []) // run only once
我们甚至可以使用它!即使我们没有设置状态!可能的!虽然不好!我们只是返回相同的状态值!
setState((state) => {
// Run right away!
// access latest state
return state; // same value (state didn't change)
});
但是这会使不同的反应内部代码部分运行(1,2,3),并检查!最终摆脱重新渲染!很高兴知道!
我们仅在更新状态时使用它!如果不 !那我们就需要用到refs了!
另一个例子:使用 getter 版本的 useState()
展示如何使用 refs 和状态访问!让我们再举一个例子!这里是另一种模式!在回调中传递状态!
import React from 'react';
function useState(defaultVal) {
// getting the state
const [state, setState] = React.useState(defaultValue);
// state holding ref
const stateRef = React.useRef();
stateRef.current = state; // setting directly here!
// Because we need to return things at the end of the hook execution
// not an effect
// getter
function getState() {
// returning the ref (not the state directly)
// So getter can be used any where!
return stateRef.current;
}
return [state, setState, getState];
}
示例属于同一类!但是这里没有效果!
但是我们可以使用上面的钩子来访问钩子中的状态,如下所示!
const [state, useState, getState] = useState(); // Our version! not react
// ref is already uptated by this call
React.useEffect(() => {
setInteval(() => {
const state = getState();
// do what you want with the state!
// it works because of the ref! Get State return a value to the same ref!
// which is already updated
}, 1000)
}, []); // running only once
对于 setInterval()!好的解决方案是 Dan Abramov 钩子!为一个东西制作一个强大的自定义钩子是一件很酷的事情!第二个例子更多地展示了 refs 的用法和重要性,在这种状态下访问需求或问题!
很简单!我们总是可以制作自定义钩子!使用参考!并更新 ref 中的状态!或者一个保持新状态的回调!视使用情况而定!我们在渲染上设置 ref(直接在自定义钩子中 [render() 中的块执行])!或者在 useEffect() 中!在每次渲染时重新运行或根据依赖项重新运行!
关于 useEffect() 和 refs 设置的注意事项
关于useEffect()
的注意事项
useEffect => useEffect 在渲染被绘制到屏幕后异步运行。
- 您以某种方式导致渲染(更改状态或父级重新渲染)
- React 呈现您的组件(调用它)
- 屏幕视觉更新
- 然后 useEffect 运行
一件很重要的事情! useEffect() 在 render() 完成后运行,屏幕视觉更新!最后运行!你应该知道!
不过一般! 效果应该在 useEffect() 上运行!所以任何自定义钩子都可以!因为它的 useEffect() 将在绘制之后运行,在渲染 useEffect() 之前运行!如果不!就像需要直接在渲染中运行一样!那么你应该直接传递状态!有些人可能会通过回调!想象一些逻辑组件!并传递给它的 getState 回调!不是一个好习惯!
如果你在某种意义上做某事!谈论参考!确保正确更新参考!之前!
但通常你永远不会有问题!如果你这样做,那就是一种气味!您尝试的方式很高可能不是正确的好方法!
总的来说,我希望这能给你一个很好的感觉!