useEffect的注意事项

584 阅读3分钟

在做记账-react项目时遇到的问题hook effect:

每次渲染后,从React Hook useEffect内部对'localTags'变量的分配将丢失。为了保存值随时间推移,将其存储在useRef挂钩中,并将可变值保留在'.current'属性中。否则,您可以移动此变量直接在useEffect内部

修改前代码:

const useTags =() =>{//封装一个自定义hook
  let localTags = JSON.parse(window.localStorage.getItem('tags') || '[]');
  const [tags,setTags] = useState<{id:number ; name:string}[]>(localTags);
  useEffect(()=>{
    if (localTags.length ===0){
      localTags = [
        {id:createId(),name:'衣服'},
        {id:createId(),name:'吃饭饭'},
        {id:createId(),name:'家庭消费'},
        {id:createId(),name:'粗去玩~'},
      ]

    }
    setTags(localTags);
  },[]);//deps是空数组代表第一次进来就执行

修改后代码:

const useTags =() =>{//封装一个自定义hook
  let localTags = JSON.parse(window.localStorage.getItem('tags') || JSON.stringify([
    {"id":createId(),"name":"衣服"},
    {"id":createId(),"name":"吃饭饭"},
    {"id":createId(),"name":"家庭消费"},
    {"id":createId(),"name":"粗去玩"}
  ]));
  const [tags,setTags] = useState<{id:number ; name:string}[]>(localTags);
  useEffect(()=>{
    //setTags(localTags)
    setTags((localTags)=>([...localTags]));
  },[]);//deps是空数组代表第一次进来就执行

传入空的依赖数组 [ ],意味着hook只在组件挂载时运行一次,不会再重新渲染,可我在useEffect函数的内部setTags(localTags) 用的值 'localTags' 是在函数外面声明的,在setTags(localTags) 回调时,localTags 的值不会发生改变,因为在执行effect 的时候,我们会创建一个闭包,并且将localTags的值保存在闭包中,可是在effect外部的localTags是在变化的,这时候TS就会报错,会问你你里面的setTags(localTags)中的localTags是在变化的,可是你的依赖值却是空。

可是我就是只要一次渲染!!!!因为这个是我需要的初始值,只需要渲染一次

方法:

就是将你setTags(localTag)中的值改为一个函数 如下所示

setTags((localtags)=>([...localtags]))

还有一个例子帮助理解:

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

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1); // 这个 effect 依赖于 `count` state
  }, 1000);
  return () => clearInterval(id);
}, []); // Bug: `count` 没有被指定为依赖

return <h1>{count}</h1>;
}

传入空的依赖数组 [],意味着该 hook 只在组件挂载时运行一次,并非重新渲染时。但如此会有问题,在 setInterval 的回调中,count 的值不会发生变化。因为当 effect 执行时,我们会创建一个闭包,并将 count 的值被保存在该闭包当中,且初值为 0。每隔一秒,回调就会执行 setCount(0 + 1),因此,count 永远不会超过 1。

指定 [count] 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。事实上,每个 setInterval 在被清除前(类似于 setTimeout)都会调用一次,但这并不是我们想要的。要解决这个问题,我们可以使用 setState 的函数式更新形式。它允许我们指定 state 该 如何 改变而不用引用 当前 state:

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
    }, 1000);
    return () => clearInterval(id);
  }, []); // 我们的 effect 不适用组件作用域中的任何变量

  return <h1>{count}</h1>;
}

setCount 函数的身份是被确保稳定的,所以可以放心的省略掉)

此时,setInterval 的回调依旧每秒调用一次,但每次 setCount 内部的回调取到的 count 是最新值(在回调中变量命名为 c)。