一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
核心
- 贯穿hooks的核心就是index和数组
- 一定要理解惰性初始化
前言
使用了react技术栈的同学,想必对hooks都比较熟悉,他的优点也是很多的,为了让我们能够更加顺手的使用hooks,我们来探索一下他的原理,并且实现一下他
希望大家在学完这几篇文章以后,能懂的一下几个问题
- 为什么只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用?
Q:useState是用来干什么的?
A:简单来说就是用来保存状态的和更改状态的
保存状态大家很熟悉,第一反应就是闭包,所以useState是如何用闭包来保存状态和更改状态的,这就是我们今天实现的核心内容
准备工作
先使用create-react-app创建一个项目
create-react-app hooks
进入他的App.js文件
我们把没用的东西都删除掉,制造一个干净的环境
function App() {
return (
<div>
</div>
);
}
export default App;
然后把App.js换成App.tsx,这一步非必要,主要是我ts太菜了,所以能用ts的地方,我绝对不用js
我们要实现的功能
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
{count}
<button onClick={()=>setCount(count+1)}>点击</button>
</div>
);
}
export default App;
这段功能很简单
动手实现
// 首先我们建立一个target,也就是我们要改变的state值
let target: any;
// 改变state的值的方法
function setState(state:any) {
// update阶段
target = state;
}
// 我们要实现的useState
function useState(initialState:any) {
// mount阶段
target = initialState;
return [target, setState]
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
{count}
<button onClick={()=>setCount(count+1)}>点击</button>
</div>
);
}
export default App;
此时我们点击按钮还是不会改变的,因为我们还没有更新组件
现在我们引入ReactDOM去更新组件
import ReactDOM from 'react-dom';
let target: any;
function setState(state) {
// update阶段
target = state;
// 更新组件
ReactDOM.render(<App />, document.getElementById('root'));
}
此时我们点击button发现count还是没有变,这是为啥呢?
因为每次在执行render的时候,又会走useState,此时一走,就会又赋值初始值,所以我们对useState方法在更改一下,如果有值那么取state,没有值的话,在取初始值
所以这也就是**惰性初始化(useState的初始值只会执行一次)**的原因
function useState(initialState) {
target = target ? target : initialState;
return [target, setState]
}
此时我们在点击按钮,终于可以动了
问题又来了,我再添加一个state,发现错误出现了
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
return (
<div>
<div>
count:{count}
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
<div>
name:{name}
<button onClick={() => setName('李四')}>点击</button>
</div>
</div>
);
}
因为他们使用的是同一个变量target,那么你第二次初始化的时候,他自然把target值给改了,所以在渲染的时候,两个target值是相同的
所以接下来我们要做的就是解耦,解耦就要改变target的数据结构,因为基础类型,只能保存一个值,复杂类型就不一样了,能够保存的值就多了,复杂类型保存数据主要有两种,一个数组,一个对象,因为对象需要定义不同的key值,所以我们用简单一些的数组
let target: any[]=[];
let index = 0;
有数组了,那么肯定要有索引的,所以上面我们又定义了一个index
此时我们的useState中就要变成往数组中存储数据了
function useState(initialState) {
let value = target[index] ? target[index] : initialState;
target[index] = value;
// 这一块大家可能会有疑问,稍后我们会讲
let set = setState(index);
// 每次usetState以后,index需要++
index++;
return [value, set]
}
上面这段代码,其他地方都比较好理解,主要是
setState(index)可能会有疑问,为啥要这么写呢,主要是为了存储index,否则每次setState更新的值都不是我们想要更新的值,这一块就是useState的核心,这一块大家一定要弄明白
然后我们在改写一下我们的setState
function setState(currentIndex:number) {
return function (state: any) {
// update阶段
target[currentIndex] = state;
// 每次渲染都要把index设置为0,这样才能取到正确的值
index = 0;
// 更新组件
ReactDOM.render(<App />, document.getElementById('root'));
}
}
存储index,一般要存储一个值要用什么方法呢?对闭包了解的同学,想必都会清楚,用闭包呀
完整内容
import ReactDOM from 'react-dom';
let target: any[]=[];
let index = 0;
function setState(currentIndex:number) {
return function (state: any) {
// update阶段
target[currentIndex] = state;
render();
}
}
function useState(initialState) {
let value = target[index] ? target[index] : initialState;
target[index] = value;
let set = setState(index);
index++;
return [value, set]
}
function render() {
// 更新组件
index = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('张三');
return (
<div>
<div>
count:{count}
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
<div>
name:{name}
<button onClick={() => setName('李四')}>点击</button>
</div>
</div>
);
}
export default App;
总结
上面的这个内容并非真实的useState源码,但是原理还是差不多的,我们理解一个内容,主要是理解他的原理,而不是非要一模一样。
如果感觉上面的内容对你有帮助,还请点个赞哦👍🏻