为什么 react-hook 函数不允许在if中使用,要按顺序写

3,272 阅读2分钟

随着 react-hook 正式发布,大家一夜之间都爱上了这个小家伙。useState, useEffect, useMemo ... 大家体验了它们的姿势之后,都深表喜欢。极大的减少了组件的代码量,让组件看起来干净整洁。但使用时想必大家都出现了疑惑为什么这些函数要按顺序写。比如以下代码是不被允许的。

let value = true;
const App = (props) => {
    if(value) {
      const [v,setV] = useState('v');
    }else {
      const [t,setT] = useState('t');
    }
    return <div>这是不被允许的</div>;
};

接下来我就通过编写一个阉割版 useState 函数的方法来为大家解答这个问题。 首先我们编写一个简单的渲染函数,一旦调用这个函数组件进行重渲染。( 这只是使用了最简单粗暴的方法进行重渲染,真实的渲染环境较为复杂), stateIndex会在下文讲解。

const reRender = () => {
  stateIndex = -1 
  ReactDOM.render(<App/>,document.getElementById('root'))
}

下面我们在来简单实现一下 useState 。

let stateQueue = []; // 用于存放每个useState返回值。
let stateIndex = -1;  //给每个 useState的返回值一个序号。
function useState(initState) {
  stateIndex++;
  stateQueue[stateIndex] = stateQueue[stateIndex] || initState;
  const currentIndex = stateIndex
  function setState(newState) {
    stateQueue[currentIndex] = newState;
    reRender(); //组件重渲染
  }
  return [stateQueue[stateIndex],setState]
}

从这可以看出每次调用一次 useState, stateQueue 就会存储一个值,比如你在App组件中使用 [v,setV] = useState('v'),那么 stateQueue[0] 的值会变为 'v',其次stateIndex会加1。 再次调用 [t, setT] = useState('t') stateQueue[1]的值会是't',以此类推。

在看看 setState , 假如你在此时调用setV('v1'),那么由于闭包的原因将会执行stateQueue[0] = 'v1',从而改变 stateQueue[0] 的值,接下来也是最关键的一步,reRender 被调用,stateIndex重新变为-1,App组件重渲染, [v,setV] = useState('v')将会再次被调用,但此时将会调用stateQueue[0] || iniState而他现在的返回值为 'v1', 变量 v 的值也就会等于 v1 渲染在页面上。这也就是 useState 的大致处理过程。

此时在来回答最开始的问题, 为什么最开始的那个案例是不被允许的。想象一下假如最开始那个 value的值是 true,那么他会让 变量 v = stateQueue[0] = 'v' , 此时再调用 setV('v1') ,页面重渲染,如果此时 value 的值是 false, 那么他会让 变量 t = stateQueue[0] = 'v1' ,这显然不是我们想看见的。所以 react-hook 为了杜绝这种事情发生,不允许 hook 函数在 if 语句中使用。