0%

从零开始实现 React hook useState

什么是hook

hook可以让你在不编写class组件的情况下使用state以及其他的React特性

具体的使用方法可见官网

实现useState

我们都知道useState会返回一个状态变量和修改它的函数,就像这样

1
const [state, setState] = useState();

单一状态

那么针对一个变量的情况就简单很多了,只需要用一个全局变量就能简单的实现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const state = null;

const useState = defaultState => {
if (!state) {
state = defaultState;
}
const setState = newState => {
state = newState;
};
return [state, setState()];
};

const singleState = () => {
const [count, setCount] = useState(0);
console.log(count);
setCount(count + 1);
};

多个状态

而对于多个状态可能就需要一个格外的变量num来标记,我们用的哪一 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const states = {};
let num = 0;

const useState = defaultState => {
if (!states[num]) {
states[num] = defaultState;
}
const setState = newState => {
states[num] = newState;
};
const result = [states[num], setState];
num += 1;
return result;
};

const withHook = renderFunc => {
return (...args) => {
// 重置
num = 0;
return renderFunc(...args);
};
};

withHook(function multipleState() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);

console.log(count1, count2);
setCount1(count1 + 1);
setCount2(count2 + 2);
});

注意每次调用函数组件的时候应该把num重置

多个函数

对于多个函数组件的话,函数相互调用会打乱我们num的顺序,那应该怎么保持有序执行呢?这里就要用一个stack,像我们平时调试程序的时候会在浏览器控制台里面看到一个call stack调用栈,每次运行一个函数就把它入栈,执行完毕就出栈,这样就保证了顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const contextStack = [];

const useState = defaultState => {
const context = contextStack[contextStack.length - 1];
const { num, states } = context;

if (!states[num]) {
states[num] = defaultState;
}
const setState = newState => {
states[num] = newState;
};
const result = [states[num], setState];
context.num += 1;
return result;
};

const withHook = renderFunc => {
return (...args) => {
contextStack.push({ num: 0, states: [] });
const result = renderFunc(...args);
contextStack.pop();
return result;
};
};

withHook(function state1() {
const [count1, setCount1] = useState(0);

console.log(count1);
setCount1(count1 + 1);
});

withHook(function state2() {
const [count2, setCount2] = useState(0);

console.log(count2);
setCount2(count2 + 2);
});

React结合起来

React是基于Fiber来实现,对于16.6以下的版本,我们用类组件来实现

根据上面的代码,我们只需要在setState里面去更新函数就可以了,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { Component } from 'react';

const contextStack = [];

const useState = defaultState => {
const context = contextStack[contextStack.length - 1];
const { component } = context;
const states = Object.values(component.state || {});

if (component.firstRender) {
states[context.num] = defaultState;
}

const setState = num => val => {
component.setState({ [num]: val });
};

const result = [states[context.num], setState(context.num, component)];
context.num += 1;
return result;
};

const withHook = renderFunc => {
const HooksComponent = class extends Component {
constructor(props) {
super(props);
// 标记组件是否是第一次渲染,对于useState初始值的优化
this.firstRender = true;
}

componentDidMount() {
this.firstRender = false;
}

render() {
contextStack.push({ num: 0, component: this });
const result = renderFunc(this.props);
contextStack.pop();
return result;
}
};
HooksComponent.displayName = renderFunc.name;
return HooksComponent;
};

export { withHook, useState };

总结

整个实现过程还是比较容易,先从最简单的一个函数一个状态到多个函数多个状态循序渐进

实现过程参考这篇文章