React Hook
React Hook的使用限制有哪些?
- 只能在函数内部最外层调用hooks
React Hooks 的限制主要有两条:
-
不要在循环、不要在条件或嵌套函数中调用 Hook;
- 因为hooks要保证相同的顺序进行渲染
-
保证顺序
-
使用的是单链表来存储状态的,比如useState出现的顺序来确定每个state的key的
-
所以要保证这个链表顺序一致,hooks的结果才能正确
-
为什么hooks只能在顶层使用,不能在内部循环或条件判断中调用它
- 因为hooks的实现原理是通过链表的,hooks存放在链表中,链表是有顺序的,要根据链表的索引顺序去读取hooks的状态
Hooks如何渲染的?
- hooks渲染的时候,因为是存储在链表,链表是有顺序的,会按顺序去遍历之前构建好的链表,会通过读取下标索引的,来取出对应的数据信息进行渲染
- 新的 hook 对象追加进链表尾部
React Hooks在什么情况是出现死循环
1、在hooks里面调用某个方法,这个方法有set的操作
- 在useEffect调用一个函数,函数里有set的操作
- setA之后会导致 state发生变化,因此组件也会更新重新渲染,组件更新的时候内部的函数也会重新创建
- 函数重新创建之后,函数引用也会发生变化
- useEffect也会重新执行
cosnt [A,setA] = useState()
useEffect(()=>{
func()
},[func])
function func(){
setA('')
}
2、useEffect没有正确指定依赖项,里面不断进行set和update
- 比如useEffec依赖性有B,然后setA,B是A的memo (A依赖项改变时才重新计算)
- 因此改变了A,B也会执行,由于useEffect依赖性是B,所以会重复执行死循环
多个hook如何获取数据
hook保存在对应的单向链表- 当
FunctionComponentrender时,每执行到一个hook,链表的指针向后移动一次,指向当前hook对应数据- 这也是为什么
React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。
- 这也是为什么
Hooks和生命周期的关系
函数组件 的本质是函数,没有state 状态概念的,因此不存在生命周期
(state是状态,可以用来保存信息,这些信息可以控制组件的渲染)
- 引入hooks之后可以在函数组件有了state,就有了生命周期的概念
- 使用了Hooks的函数组件才有生命周期
哪个hooks让函数组件有了状态?
userState、useReducer
- 简单结构的状态可以使用useState, 如果复杂的状态 (state逻辑复杂需要经常变化)可以使用useReducer
cosnt [A,setA] = useState()
useEffect(()=>{
func()
},[func])
function func(){
setA('')
}
React Hook实现原理
hooks的实现原理 是 用了闭包 + 链表,通过链表来保存组件的状态,通过下标索引来读取对应的数据信息
-
hooks的实现原理 是 用了闭包 + 链表
- 通过闭包环境特性,可以用来保存组件状态,使react组件每次渲染的时候,组件内部状态 保持着之前引用关系,可以保持拿到之前的状态,这样就可以保持这个状态的连续性
-
React Hooks 是用链表来保存状态的
- 通过下标索引来读取对应的数据信息
- 如useState出现的顺序来确定每个state的key的
-
hooks 链表是保存在fiber节点上
- 链表的每个节点就是表示一个fiber
- fiber:是React核心算法的重新实现,fiber的出现让每个节点都有了实例,拥有了保存状态的能力
- fiber就是时间切片,合理分配CPU资源,从而提高用户的响应速度
useState机制
注意:useState 修改 state 时,设置相同的值,函数组件不会重新渲染,而类组件,即便 setState 相同的值,也会触发渲染
- 这是因为useState的内部进行了性能优化,实现了类似Object.is()方法进行浅比较
var _state;
function useState(initialState) {
if (typeof _state === 'undefined'){
if(typeof initialState === 'function'){
_state = initialState()
}else{
_state = initialState;
}
}
// 产生闭包环境
var setState = function setState(value) {
// 相同的值,函数组件不会重新渲染
if(Object.is(_state,value)) return
if(typeof value === 'function'){
_state = value(_state)
}
_state = value;
// 通知视图更新逻辑
};
return [_state, setState];
}
/**
* 比如 [num,setNum] = useState(0)
* setNum(1), 第一次执行函数组件时,initialState为undefined, 进行赋值状态,num1为1,
* setNum(10), 第二次执行函数组件时,由于initialState已经存在值,num1为100
* 每次组件执行,useState都会重新创建,重新执行函数
*/
更新机制理解
useState的更新机制 内部进行了性能优化, 相同值不会重新渲染
- 1、不会立即更新状态和视图,而是加入到队列中
- 2、当上下文代码都执行处理完毕之后,会让更新队列中的任务,统一渲染/更新一次 【批处理执行一次】
const Demo1 = () => {
const [x, setX] = useState(10);
/**
* 经过10次循环,每次setX都会添加到更新队列中
* 代码执行完之后,批处理执行一次
* 通过作用域链,找到上级x变量的作用域,是10
* 第一次渲染为10、点击按钮,第二次渲染为11 */}
*/
const handle = () => {
for (let i = 0; i < 10; i++) {
setX(x + 1);
}
};
// 期望结果是20
const handle1 = () => {
for (let i = 0; i < 10; i++) {
// 通过setDate的回调函数,来获取上一次的状态值
setX((pre) => pre + 1);
}
};
return (
<div>
<span>{x}</span>
<button onClick={hanle}>新增</button>
</div>
);
};
useId实现原理
- 根据组件树来生成id,当组件树一样,那么生成的id是一致的
useEffect实现原理
hooks 函数组件渲染机制
每次组件重新渲染,函数也会重新执行,函数每次渲染都是新的地址
- 第一次执行 【第一次视图渲染】
- num = 0
- setNum = 函数 0x001
- handle = 函数 0x002
- 触发handle
- 产生上下文作用域
- setNum(100) => 视图更新
- setTimeout(( console.log( num ))=>2000)
- 注意,此时的num变量 上级作用域是 0
- 触发setNum(100) 【第二次视图更新,组件重新执行,函数也会重新执行,每次渲染都是新的地址,形成新的上下文】
- num = 100
- setNum = 函数 0x101
- handle = 函数 0x102
- 定时器等待2000后
- 定时器执行
- 输出num 【作用域链:当访问一个变量时,先找当前作用域查找,如果没找到,就去父级作用域去查找,也就是上一级去查找】
- 首先在定时器的当前作用域查找,发现没有,就去上一级handle函数查找,发现也没有,就继续上一级,上一级num = 0
const Demo = () => {
const [num, setNum] = useState(0);
/**
* 第一次执行 【第一次视图渲染】
* num = 0
* setNum = 函数 0x001
* handle = 函数 0x002
*/
/**
* 触发handle
* 产生上下文作用域
* setNum(100) => 视图更新
* setTimeout(( console.log( num ))=>2000)
*/
/**
* 触发setNum(100) 【第二次视图更新,组件重新执行,函数也会重新执行,每次渲染都是新的地址,形成新的上下文】
* num = 100
* setNum = 函数 0x101
* handle = 函数 0x102
*/
/**
* 定时器等待2000后
* 定时器执行
* 输出num 【作用域链:当访问一个变量时,先找当前作用域查找,如果没找到,就去父级作用域去查找,也就是上一级去查找】
* 首先在定时器的当前作用域查找,发现没有,就去上一级handle函数查找,发现也没有,就继续上一级,上一级num = 0
*/
const handle = () => {
setNum(100);
// 产生闭包,当前环境存在指向父级作用域链的引用
setTimeout(() => {
console.log(num); //输出0
}, 2000);
};
return (
<div>
<span>{num}</span>
<button onClick={handle}>新增</button>
</div>
);
};