这是我参与「第四届青训营 」笔记创作活动的第 5 天
React 函数式组件里,如果在 return 之前 setState 会发生什么?
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const Component = () => {
const [count, setCount] = useState(0)
setCount(1)
return <div>count:{count}</div>
}
ReactDOM.render(<Component />, document.getElementById("app"))
可以看到控制台鲜红的报错信息
怎会如此?我们来打个 log,为避免死循环,我们加个判断条件,让它最多执行 5 次
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
const Component = () => {
const [count, setCount] = useState(0)
console.log('count:',count)
if (count < 5) {
setCount(count + 1)
}
// 上述 if 相当于一个没有任何 dep 的 useEffect
// useEffect(() => {
// if (count < 5) {
// setCount(count + 1)
// }
// })
return <div>count:{count}</div>
}
ReactDOM.render(<Component />, document.getElementById("app"))
控制台也会打出:count:0 至 count:5,也就是说 Component 组件 render 了 5 次
我们再来试一下类组件
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
render() {
console.log('count:',this.state.count)
if (this.state.count < 5) {
this.setState({ count: this.state.count + 1 })
}
return <div>count:{this.state.count}</div>
}
}
ReactDOM.render(<Component />, document.getElementById("app"))
会得到同样的结果,还会在控制台给你一个“温馨提示”
由此我们可以看到,函数式组件相当于类组件的 render()
每次 state 或者 props 有更新都会重新 render,所以要避免在 render 里 setState。
函数式组件可以用 useEffect,类组件里也有类似的 componentDidUpdate(会在更新后会被立即调用。首次渲染不会执行此方法。)相比 componentDidUpdate 来说,useState 要灵活得多,它可以根据第二个参数的不同,让 useEffect 成为 componentDidMount(第二个参数为空数组)或者 componentDidUpdate(第二个参数不传,则每次 render 都会调用,若为 [{变量 a}] 则只有在变量 a 改变时才会执行 useEffect,具体用法可以戳文档)
那么问题来了
为什么 useState 可以保存 state 呢?确保每次 render 的时候 state 都不会被初始化。它是怎么做到的?
于是好奇宝宝找来了源码。
function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = nextHook();
const state: S =
hook !== null
? hook.memoizedState
: typeof initialState === 'function'
? // $FlowFixMe: Flow doesn't like mixed types
initialState()
: initialState;
hookLog.push({primitive: 'State', stackError: new Error(), value: state});
return [state, (action: BasicStateAction<S>) => {}];
}
type Hook = {
memoizedState: any,
next: Hook | null,
};
let currentHook: null | Hook = null;
function nextHook(): null | Hook {
const hook = currentHook;
if (hook !== null) {
currentHook = hook.next;
}
return hook;
}
看起来每次调用 useState 的时候,会先调用 nextHook() 获取到当前的存放所有 Hook 的 currentHook 链表,链表为空的时候将 initialState 添加到链表里,否则取链表里上次记录到的 memoizedState 值。
(看来基础不牢的切图仔不是好的切图仔,数据结构与算法无处不在 orz 这就滚去学)