正在学习 React 的 Hooks,打算根据学习笔记,写下总结,这样印象和理解回加深一下吧!
对于 React 的学习还处于初级阶段,会结合大佬的博客以及官网自己整理,我能做好的就是尽可能的去提出疑问,然后寻找答案,写的博客也算是一个整合吧(笑)。首先可以明确的是:Hook 是一个特殊的函数,它可以让你“钩入” React 的特性
开始学习得时候,资料里面将组件分为状态组件(类组件)和无状态组件(函数组件),因为类式组件拥有状态,生命周期等,可以处理相关逻辑,实现需求。而函数组件通常在主要负责渲染的时候,性能会比较高,这点在 react-redux 的设计中体现得尤为明显:类组件充当容器,和redux连接,负责逻辑交互,而函数组件充当UI组件,通过 props 与容器组件联系,主要负责渲染。
一、基本使用
但是现在,函数也能有自己的组件了,函数组件不能实例化,没有this,那要怎样拥有自己的状态 state 呢?答案就是 useState,React 官网给出的示例代码如下:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState 的参数就是状态的初始值,在左边使用的是数组结构赋值,count 是状态变量,setCount 是更新状态的方法。刚开始的时候还是需要从习惯类组件的思维转换过来。count 可以比作类组件 state 对象,我们还可以多次调用 useState,下一次的状态就可以取其他名字,可以是基本数据类型,也可以是引用类型。这样一看是不是就和state很像了呢!当然有状态,就要有改变状态的方法,这里每个状态都有自己的改变状态的方法,并且类组件里面的 setState 是合并,这里是替换
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用,这是惰性初始 state。
二、一些疑惑
上边的小 demo 实现记录点击按钮的次数,但是细心的朋友会发现好些个问题:首先,一般函数运行完成后,变量会销毁的。
1.如何记录状态
Hook 使用了 JavaScript 的闭包机制,useState为什么能记住上一次执行的状态,应该就是全局变量+闭包,useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props),这也是闭包的作用。
let _val // 将我们的状态保持在模块作用域中
const MyReact = (function() {
return {
render(Component) {
const Comp = Component()
Comp.render()
return Comp
},
useState(initialValue) {
_val = _val || initialValue // 每次运行都重新赋值
function setState(newVal) {
_val = newVal
}
return [_val, setState]
}
}
})()
2.useState 是否每次渲染都执行
从上面这段模拟 useState 原理的代码可以看出,useState 完全可以每次都执行,使用了或运算符 ||
3.如何实现多个状态
就是利用数组,初次渲染,数组为空,使用 useState 的参数,后面的渲染,数组值不为空,使用数组值
let _state = [];// 利用全局数组来保存状态,就能解释为什么可以记录上一次的状态。
let index = 0;//各个状态值的数组下标
function myUseState(initialValue) {
const currentIndex = index;
index += 1;
//判断是否是第一次的步骤,是第一次的话,_state[currentIndex]为null,将 initialValue赋给它,不是第一次就保持原状。这里就解决了每一次useState都会执行,但不是默认值的问题
_state[currentIndex] = _state[currentIndex] || initialValue;
const setState = newState => {
_state[currentIndex] = newState;//更新状态,注意细节了是替换,而项类组件里边setState是合并
render();// 触发页面重新渲染
};
return [_state[currentIndex], setState];
}
三、给 setSatate 传函数
setState 有两种传参的方法,一种就是传递 count+1,也就是将count 替换成 count+1,第二种就是传递一个函数,该函数将接收先前的 state,并返回一个更新后的值 。需要用到其他状态,使用函数会更加方便。
但是,还有一个更加重要的特性,需要注意。我们知道 useEffect 的第二个参数,能决定 effect 依赖哪些数据,根据这些数据的变化决定在一次重新渲染中是否执行。
我们实现一个数字自增通常会这样写
useEffect(() => {
console.log('执行effect')//自增到几,这句话就会打印多少次
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
初次渲染,setCount 改变 count,同时 count 改变会引发页面的重新渲染,而 effect 对比一下,发现依赖 count 改变了,于是执行,又会执行 setCount 一直这样下去,实现了定时自增。
同时,我们还可以移除依赖:
useEffect(() => {
console.log('执行effect')//就打印一次,因为第二个参数传的是空数组,但是自增的功能不会有影响
const id = setInterval(() => {
setCount(c => c+1)
}, 1000);
return () => clearInterval(id);
},[]);
尽管effect只运行了一次,第一次渲染中的定时器回调函数可以完美地在每次触发的时候给React发送c => c + 1更新指令。它不再需要知道当前的count值。因为React已经知道了。
React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 setState
四、Hook 的使用规则
这个官网上边讲的比较清楚了:
- 只在最顶层使用 Hook,不要在循环,条件或嵌套函数中调用 Hook, React 靠的是 Hook 调用的顺序来区分哪个 state 对应哪个
useState - 只在 React 函数(函数组件和其他hook)中调用 Hook,不要在普通的 JavaScript 函数中调用 Hook,这也是为了保证调用顺序,避免出错
其实就是因为,使用了数组来保存状态,数组值都是靠渲染的顺序决定,上边两条规定都是为了保证每次渲染的时候 hook 函数的调用顺序是一样的。
参考博客