React 16.8 新特性 - Hook
在 React 16.8 版本出现了React Hooks, 目标是让函数组件能彻底取代Class组件, ⼲掉state、⽣命周期这些概念。它可以让你在不编写 Class 的情况下使用 state 以及其他的 React 特性。
下面我们通过React Hooks来编写一个简单的计数器,当你点击按钮,计数器的值就会增加:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
ReactDOM.render(<Counter />, document.querySelector("#root"));
useState 唯一的参数就是初始 state。在上面的例子中,我们的计数器是从零开始的,所以初始 state 就是 0。
那么我们可以实现这个 useState 吗?
从上述实例中我们可以看出来:
- 这个 useState 是一个返回值为数组的函数
- 返回的数组第一项是一个可修改的数据,第二项是一个可修改第一项的函数。
- 当通过函数修改数据时,整个组件会重新渲染
- 重新渲染组件时,获得的count是修改后的新值,而不是初始值
代码如下:
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();
但是当你想要使用多个state时,上述代码便不能使用了,因为只有一个全局的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