必须知道的react hooks中闭包问题

1,319 阅读1分钟

1 首先老生常谈的闭包

function createIncrement(val) {
  let value = 0;

  function increment() {
    value += incBy;
    console.log(value);
  }

  const message = `Current value is ${value}`;
  function log() {
    console.log(message);
  }
  
  return [increment, log];
}

const [increment, log] = createIncrement(1);
increment(); // logs 1
increment(); // logs 2
increment(); // logs 3
// Does not work!
log();       // logs "Current value is 0"

[increment, log] = createIncrement(1) 返回了两个函数,一个是对value的累加,另一个是打印当前的value值。
当increment调用三次后value的值为3。
最终log()打印的message是“current value is 0”,这是不符合预期的value为3。
log()就是一个闭包,这个闭包捕获message变量。
尽管多次调用increment增加vulue的值,message变量也没有保持更新。

2 修复闭包

修复log函数使其打印更新后的value,移动message= ...;到log函数内部

function createIncrement(incBy) {
  let value = 0;

  function increment() {
    value += incBy;
    console.log(value);
  }

  function log() {
    const message = `Current value is ${value}`;
    console.log(message);
  }
  
  return [increment, log];
}

const [increment, log] = createIncrement(1);
increment(); // logs 1
increment(); // logs 2
increment(); // logs 3
// Works!
log();       // logs "Current value is 3"

现在log函数的value就是3了。

\

3 hooks中的闭包

3.1 useEffect()

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

  useEffect(function() {
    setInterval(function log() {
      console.log(`Count is: ${count}`);
    }, 2000);
  }, []);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1) }>
        Increase
      </button>
    </div>
  );
}

多次点击button后,控制台的打印Count is 0, 实际上count已经被加了多次。

为什么会这样呢?

在第一次渲染的时候count被初始化为0

组件被挂载后,useEffect调用setInterval(log, 2000)每隔2秒打印一次,闭包log捕获的count变量是0。

即使count被增加多次,log闭包函数仍旧使用count=0的初始化渲染的值

我们知道useEffect的闭包log依赖count变量

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

  useEffect(function() {
    const id = setInterval(function log() {
      console.log(`Count is: ${count}`);
    }, 2000);
    return function() {
      clearInterval(id);
    }
  }, [count]);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1) }>
        Increase
      </button>
    </div>
  );
}

正确的设置依赖,useEffect随着count变化更新闭包

3.2 useState

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

  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 1000);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

快速点击button两次,count的值仍旧是1,而不是2

每次点击后延迟1秒调用delay函数,delay函数捕获的count始终是0

两个delay函数都是闭包,更新相同的value:setCount(count + 1) = setCount(0 + 1) = setCount(1)

两次点击后的闭包delay捕获的仍旧是更新前的count:0

为了修复这个问题,我们用函数的方式setCount(count => count + 1)更新count

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

  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(count => count + 1);
    }, 1000);
  }

  function handleClickSync() {
    setCount(count + 1);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
      <button onClick={handleClickSync}>Increase sync</button>
    </div>
  );
}

回调函数返回一个新的state依据之前的state。

4 总结

闭包问题经常捕获一个为更新的变量,一个有效的解决闭包问题的方法是在React hooks里设置正确的依赖,或者用函数的方式更新state