2025-05-09 日报 Day181

2025-05-09 日报 Day181

Yuyang 前端小白🥬

今日的鸡汤

“日月不肯迟,四时相催迫。”在这个属于奋斗者的新时代,人人都有追梦的权利,人人也都是梦想的筑造者。

今日学习内容

1、https://www.youtube.com/watch?v=EVFZazcxAbo&t=4112s

今日笔记

React-Hooks设计动机与工作模式(上):
函数组件与类组件的对比:

  • 类组件需要继承class,函数组件不需要
  • 类组件可以访问生命周期方法,函数组件不能
  • 类组件可以获取到实例化后的this,并基于这个this做各种各样的事情,而函数组件不可以
  • 类组件可以定义并维护state,而函数组件不可以

函数组件会捕获render内部的状态,这是两类组件最大的不同。

useState(): 为函数组件引入状态

引入状态:早期的函数组件相比于类组件,其一大劣势是缺乏定义和维护state的能力,useState正是这样一个能够为函数组件引入状态的API。

useEffect(): 允许函数组件执行副作用操作

useEffect则在一定程度上弥补了生命周期的缺席
useEffect能够为函数组件引入副作用 完成类组件中放在componentDidMount、componentDidUpdate、componentWillUnmount三个生命周期里来做的事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React,{ useState, useEffect } from 'react';

function IncreasingTodoList(){
const [count, setCount] = useState(0);

useEffect(()=>{
const todoList = document.getElementById("todoList");
const newItem = document.createElement("li");
newItem.innerHTML = `我是第${count}个待办项`;
todoList.append(newItem);
})

return(
<div>
<p>当前共计{count}个todo item</p>
<ul id="todoList"></ul>
<button onClick={
()=>setCount(count+1)
}></button>
</div>
)
}

useEffect(callBack, [])

每一次渲染后都执行的副作用:传入回调函数,不传依赖数组
useEffect(callBack)

仅在挂载阶段执行一次的副作用:传入回调函数,且这个函数的返回值不是一个函数,同时传入一个空数组
useEffect(()=>{
// 这是业务逻辑
},[])

在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组
useEffect(()=>{

<!-- 返回一个函数B -->
return()=>{

}

}.[])

useEffect 回调中返回的函数被称为“清除函数”
这个规律不会受第二个参数或者其他因素的影响只要你在useEffect 回调中返回了一个函数它就会被作为清除函数来处理

每一次渲染都触发,且卸载阶段也会被触发的副作用:
传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数
useEffect(()=>{

})

Hooks不能放在条件语句中
若不保证hooks的执行顺序:

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
import React, { useState } from 'react';

// 使用isMounted记录是否已挂载(是否首次渲染)
// let isMounted = false;

function PersonalInfoComponent(){
let name,age,career,setName,setAge,setCareer;

// 获取姓名状态
[name, setName] = useState('张三');
// 获取年龄状态
[age, setAge] = useState(18);
// 获取职业状态
[career, setCareer] = useState('前端工程师');
// 输出职业信息
console.log("🚀 ~ PersonalInfoComponent ~ career:", career)

// console.log('isMounted is', isMounted);

// 追加if逻辑,只有在首次渲染(组件还未挂载)时,才获取name、age两个状态
return(
<div className="personalInfo">
<p>姓名: {name}</p>
<p>年龄: {age}</p>
<p>职业: {career}</p>
<button onClick={()=>{
setName('李四');
}}>修改姓名</button>
</div>
)
}
export default PersonalInfoComponent;
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
import React, { useState } from 'react';

// 使用isMounted记录是否已挂载(是否首次渲染)
let isMounted = false;

function PersonalInfoComponent(){
let name,age,career,setName,setAge,setCareer;

console.log('isMounted is', isMounted);

// 追加if逻辑,只有在首次渲染(组件还未挂载)时,才获取name、age两个状态
if(!isMounted) {
// eslint-disable-next-line
[name, setName] = useState('修言');
// eslint-disable-next-line
[age, setAge] = useState(20);
isMounted = true; // 标记组件已挂载
}

[career, setCareer] = useState('前端工程师');
// 输出职业信息
console.log("🚀 ~ PersonalInfoComponent ~ career:", career)


return(
<div className="personalInfo">
{ name ? <p>姓名: {name}</p> : null}
{ age ? <p>年龄: {age}</p> : null}

<p>职业: {career}</p>
<button onClick={()=>{
setName('李四');
}}>修改姓名</button>
</div>
)
}
export default PersonalInfoComponent;

以useState为例,分析React-Hooks的调用链路:
React链路在首次渲染和更新不同
image-20250705131719421

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
function mountState(initialState){
// 将新的hook对象追加进链表尾部
var hook = mountWorkInProgressHook();

// initialState可以是一个回调函数,若是回调函数,则取回调执行后的值
if(typeof initialState === 'function'){
initialState = initialState();
}

// 创建当前hook对象的更新队列,这一步主要是为了能够依序保留dispatch
const queue = hook.queue = {
last: null, // 队列的尾部
dispatch: null, // 用于更新状态的函数
lastRenderedReducer: basicStateReducer, // 用于更新状态的函数
lastRenderedState: initialState // 用于记录上一次渲染的状态
};

// 将initialState作为一个“记忆值”存下来
hook.memoizedState = hook.baseState = initialState;

// dispatch是由上下文中一个叫dispatchAction的方法创建的
var dispatch = queue.dispatch = dispatchAction.bind(
null,
currentlyrenderingFiber$1,
queue
);
// 返回目标数组,dispatch其实就是示例中常常见到的setXXX这个函数
return [hook.memoizedState, dispatch];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function mountWorkInProgressHook(){
// 获取当前正在渲染的fiber对象
const hook = {
memoizedState: null, // 用于存储hook的状态
baseState: null, // 用于存储hook的初始状态
baseQueue: null, // 用于存储hook的初始更新队列
queue: null, // 用于存储hook的更新队列
next: null // 用于指向下一个hook
};

// 将hook添加到workInProgressHook链表中
if(workInProgressHook === null){
// 这行代码每个React版本不同,但都是做一个事情:将hook作为链表的头节点处理
firstWorkInProgressHook = workInProgressHook = hook;
}else{
// 若链表不为空,则将hook追加到链表尾部
workInProgressHook = workInProgressHook.next = hook;
}

// 返回当前的hook
return workInProgressHook;
}

更新hook调用链表:

image-20250705135806767

updateState: 按顺序去遍历之前构建好的链表取出对应的数据信息进行渲染

mountState(首次渲染)构建链表并渲染
updateState依次遍历链表并渲染

hooks的渲染是通过依次遍历来定位每个hooks内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果自然是不可控的。

hooks的本质是链表

站在底层视角,重现PersonalInfoComponent组件的执行过程:

1
2
3
[name, setName] = useState('张三');
[age, setAge] = useState(18);
[career, setCareer] = useState('前端工程师');

image-20250705143438178

更新后只有career的useState:

image-20250705143625536

虚拟DOM -> Diff算法 -> Fiber架构

此页目录
2025-05-09 日报 Day181