React 16.8 版本出现了React Hooks, 目标是让函数组件能彻底取代Class组件, ⼲掉state、⽣命周期这些概念。不管是Class组件还是函数组件,其底层思路都是类似的:
下面是函数组件以及使用React Hooks的一个范例。 codesandbox查看效果 。
import React, { useState } from "react"; import ReactDOM from "react-dom"; function Counter() { let [count, setCount] = useState(0); return ( <> <p>Clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click</button> </> ); } ReactDOM.render(<Counter />, document.querySelector("#root"));
如何造出上面代码中的 useState 呢?
从上面的使用方式我们能得到这些信息:
import React from "react"; import ReactDOM from "react-dom"; let value; function useState(initValue) { value = value === undefined ? initValue : value; function dispatch(newValue) { value = newValue; scheduleWork(); } return [value, dispatch]; } function Counter() { let [count, setCount] = useState(0); return ( <> <p>Clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Add count</button> </> ); } function scheduleWork() { ReactDOM.render(<Counter />, document.querySelector("#root")); } scheduleWork();
于是有了上述代码。 codesandbox查看效果。
当在组件里连续使用多个 useState 时,上述代码就没法正常工作了。因为只有一个全局的value ,无法同时代表多个数据。
可以沿着当前思路继续往下走。
把每个数据都放到一个对象节点里,这些节点构成一个单向链表,这样我们就能存储多个数据。
hook = { state: null, //数据 dispatch: null, //修改数据的方法 next: null //指向下一个节点 }
把执行过程分为 mount 和 update 两个阶段,两个阶段做的事情不一样。
在 mount 阶段依次执行 useState 时,会使用初始化的数据依次创建多个hook节点,构造链表。
在 update 阶段依次执行 useState 时,会从链表开头依次遍历 hook 节点,返回节点信息(如[age, setAge]) 。
执行修改数据的方法时会修改当前hook节点的数据,定位到链表开头, 修改mount阶段到update阶段。
代码如下:
import React from "react"; import ReactDOM from "react-dom"; const Dispatcher = (() => { let isMount = true; let firstWorkInProgressHook = null; let workInProgressHook = null; function mountWorkInProgressHook() { const hook = { state: null, dispatch: null, next: null }; if (workInProgressHook === null) { firstWorkInProgressHook = workInProgressHook = hook; } else { workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; } function updateWorkInProgressHook() { let curHook = workInProgressHook; workInProgressHook = workInProgressHook.next; return curHook; } function useState(initialState) { let hook; if (isMount) { hook = mountWorkInProgressHook(); hook.state = initialState; } else { hook = updateWorkInProgressHook(); } hook.dispatch = function (newState) { this.state = newState; workInProgressHook = firstWorkInProgressHook; isMount = false; scheduleWork(); }.bind(hook); return [hook.state, hook.dispatch]; } return { useState }; })(); function Counter() { let [count, setCount] = Dispatcher.useState(1); let [age, setAge] = Dispatcher.useState(10); return ( <> <p>Clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Add count</button> <p>Age is {age}</p> <button onClick={() => setAge(age + 1)}> Add age</button> </> ); } function scheduleWork() { ReactDOM.render(<Counter />, document.querySelector("#root")); } scheduleWork();
以上仅仅简单实现了React Hooks的 useState,里面参考了小部分React源码的实现和思路,欢迎评论区多多交流。
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com