React Hook源码解析(三)

1,509 阅读2分钟

写在前面

前两篇文章,

juejin.cn/post/684490…

juejin.cn/post/684490…

深入了Hook的源码。本文将以useStateuseEffect为主,总结一下Hook的相关内容, 先贴出示例代码:

const App = () => {

    const [title, setTitle] = useState('');
    const [fontSize, setFontSize] = useState('13');

    useEffect(() => {
       document.title = title;
    },[title]);

    useEffect(() => {
        document.body.style.fontSize = `${fontSize}px`;
    }, [fontSize]);

    return <React.Fragment>
        <input value={title} onChange={e => setTitle(e.target.value)} />
        <input value={fontSize} onChange={e => setFontSize(e.target.value)} />
        <p>Hello World</p>
    </React.Fragment>
};

组件初次挂载

组件首次挂载的时候,依次执行四行useXXX的代码,生成4个hook对象,按照顺序形成完整的hook链,挂载在Fiber对象的memorizedState属性上。hook对象上的memorizedState属性表示当前hook对象的值。对于effect-hook,其memorizedState属性的值为一个对象(effect对象),包含了一个指向下一下effect-hook的effect对象的指针,会形成一个环形链表。

组件更新

对于state-hook,其setState函数通过闭包保存了对应的hook对象的引用。调用setState对状态值进行更新时,每调用一次setState,都会修改hook对象上的queue属性。queue属性保存着hook的更新信息。setState执行完后,对应的hook对象上的memorized值就是最新的了。

对于effect-hook,每次组件渲染时,都会进入effect-hook的逻辑:首先判断deps数组内的值较上一次有无变化。如果无变化,则会给该effect打上忽略的tag。如果跟上一次组件渲染时有变化,最终会先执行destory函数(即useEffect中return的函数),然后执行create函数。

对于副作用,react每次都会生成新的effect链,如果deps数组内的值变化了,那么新的effect对象还会赋值给effect-hookmemorizedState属性。

本例中,假设我们在title那里,输入了hello,组件更新完成后,Fiber节点上的hook信息如下:

总结

稍微总结一下,关于react hook,我们需要知道以下几点:

1、react hook大量采用了链表的结构。

2、代码中hook的书写顺序,决定了最终hook在fiber上的存储顺序。

3、组件每次渲染的时候,hook的代码都会执行一次,并且将上一次的hook对象都clone一遍,返回最新的hook的state以及对应的更新函数。在state的更新函数中,以闭包的形式保存了对应hook对象的引用。

写在后面

至此,react hook源码解析系列暂时告一段落了。尽管有许多地方没有我还没完全理解,有的地方也没有讲述清楚,但是对react hook的整体实现方式已经有了进一步深入的理解了。符合预期。最后,推荐一篇hook相关的文章:

github.com/shanggqm/bl…