背景
以下是手写系列内容预告,初步计划一周一个主题。
前言
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 呢?
从上面的使用方式我们能得到这些信息:
- useState是一个函数
- useState执行返回一个数组,数组第一项是内部维护的数据(通过函数第一次调用的参数传入,可被修改),数组第二项是一个能修改内部数据的函数
- 当触发修改数据修改的方法时,会修改数据,并且会再次渲染组件
- 再次渲染组件时,会再次执行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 时,上述代码就没法正常工作了。因为只有一个全局的vulue ,无法同时代表多个数据。
可以沿着当前思路继续往下走。
把每个数据都放到一个对象节点里,这些节点构成一个单向链表,这样我们就能存储多个数据。
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源码的实现和思路,欢迎评论区多多交流。
欢迎收藏点赞!你的喜欢是我写作最大的动力:)