谁说React Hooks不能放在if else里的?

3,471 阅读2分钟

废话不多说,直接上代码:

import { useState } from 'react';
export default function App() {
  let v = null;
  const [flag, toggle] = useState(true);
  if (flag) {
    const s1 = useState(1)[0];
    console.log("s1 is ", s1);
    v = s1;
  } else {
    const s2 = useState(2)[0];
    console.log("s2 is ", s2);
    v = s2;
  }
  return (
    <div>
      <button onClick={() => toggle(s => !s)}>Click me</button>
      <h1>v is {v}</h1>
      </div>
  );
}

上面的代码到底能不能运行?结果又是什么? 尝试一下的话,会发现,不断点击按钮,会不停输出:

s1 is 1
s2 is 1
s1 is 1
s2 is 1
.....

所以,useState确实是可以放在if else语句里面的,只不过,它们的值都是1。为什么?

这就要涉及一点React实现Hook的原理。

可能很多人已经知道了,React通过一个链表来保存所有的hooks。同时,在DEV模式下,又通过一个数组hookTypesDev依次保存所有hook的名字。

在每次更新的时候,会同时遍历current fiber node以及相对应的workInProcess fiber node的hook链表。如果某个链表先结束,那么意味着hooks数量不同。同时hookTypesDev又保证了调用的hook必须是类型相同的。

但是在一开始提到的代码里,虽然有if else,但是结果都是两个useStatehook,所以对React来说,两次生成的链表没有什么不同!else语句里的useState会被React当做之前的useState(1),这也解释了为什么每次输出的结果都是1

那么,这到底是不是个bug?要怎么解决呢?

React Hook是定义成下面这样的结构:

Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null,
  next: Hook | null,
};

个人认为,如果里面加一个id, 并且通过id来比较,那么或许就可以解决这个问题。不过,总体而言,我觉得Hook的这个问题也不是个大问题。到底值不值得改,或者不这么做是否是有意为之,我就不知道了。

就酱。