概览

用于构建用户界面的 JavaScript 库,组件化 + 声明式,提升开发效率及复用率

  • JSX: 描述 UI (官方文档)
  • @babel/preset-react: JSX => React.creactElement(tagName, props, children)
  • React.createElement: JS 对象 => VDom
  • ReactDom: 渲染真实 Dom,VDom => Dom

组件

function 组件

无状态组件,从 v16.8 引入 hooks 之后拥有状态

联系及区别

  1. 相同点: 组件是 React 可复用的最小编码单位,均会返回要在页面中渲染的 React 元素。除少数极端场景外,两者基本一致
  2. 不同点:
    • 侧重点: 类组件基于面向对象,主打继承、生命周期等概念;函数组件基于函数式编程,主打 immutable、无副作用、引用透明等特点
    • 性能: 类组件 shouldComponentUpdate 阻断渲染来提升性能;函数组件依靠 React.memo 缓存渲染结果来提升性能
    • 可维护性: 由于生命周期带来的复杂度,类组件并不易于优化。函数组件轻量简单,Hooks 更细粒度的逻辑组织与复用,更能适应 React 的未来发展

Hook

  • 只能在函数最外层调用 Hook ,不要在循环、条件判断或者子函数中调⽤
  • 只能在 React 的函数组件中或⾃定义的 Hook 中 调用 Hook

分类

  1. State Hook: 状态帮助组件记住用户 输入 的信息
    • 使用 useState 声明可以直接更新的状态变量
    • 使用 useReducer 在 reducer 函数中声明带有更新逻辑的 state 变量
  2. Context Hook: 上下文帮助组件 从祖先组件接收信息,而无需将其作为 props 传递
    • 使用 useContext 读取订阅上下文
  3. Ref Hook: ref 允许组件 保存一些不用于渲染的信息,比如 DOM 节点或 timeout ID。更新 ref 不会重新渲染组件,escape hatch 机制。常搭配非 React 系统如浏览器内置 API 一同工作
    • 使用 useRef 声明 ref,可以在其中保存任何值,最常用于保存 DOM 节点
    • 使用 useImperativeHandle 自定义从组件中暴露的 ref
  4. Effect Hook: Effect 允许组件 连接到外部系统并与之同步,比如处理网络、浏览器、DOM、动画、使用不同 UI 库编写的小部件以及其他非 React 代码。避免使用 Effect 协调应用程序的数据流。如果不需要与外部系统交互,那么可能不需要 Effect
    • 使用 useEffect 将组件连接到外部系统
    • useLayoutEffect 在浏览器重新绘制屏幕前执行,可以在此处测量布局
    • useInsertionEffect 在 React 对 DOM 进行更改之前触发,可以在此处插入动态 CSS
  5. 性能 Hook: 跳过不必要的工作,或者有时由于屏幕确实需要更新,无法跳过重新渲染。此时可以通过将必须同步的阻塞更新与不需要阻塞用户界面的非阻塞更新分离以提高性能
    • 使用 useMemo 缓存计算代价昂贵的计算结果
    • 使用 useCallback 将函数传递给优化组件之前缓存函数定义
    • useTransition 允许将状态转换标记为非阻塞,并允许其他更新中断它
    • useDeferredValue 允许延迟更新 UI 的非关键部分,以让其他部分先更新

setState

  1. 传递对象: 批处理。对相同对象的多次处理会合并成一个,并渲染最后一次处理的结果

    < v18

    • 合成事件和生命周期方法中: 异步;setTimeout 或者原生事件处理函数中: 同步,不会批处理
    • useState 的更新是批处理的,而类组件的 setState 在合成事件中是批处理的,但在其他情况下可能不是

    v18+

    • 引入并发模式: createRoot,默认启用批处理,包括 Promise、setTimeout 等
    • 在并发模式下,允许中断低优先级更新,确保高优先级交互更流畅,应避免依赖状态更新的即时性,始终通过 useEffect 或回调函数处理副作用
    • 并发模式下,useState 和类组件的 setState 都会自动批处理
    • flushSync 强制同步更新,立即获取状态
  2. 传递函数: 链式调用。多个函数会放入队列并依次执行,从而可以获取实时状态

useEffect

useMemo

  • 利用 memoization 技术缓存结果,它仅会在某个依赖项改变时才重新计算 memoized 值
  • 使用 Object.is 将每个依赖项与其之前的值进行比较

useCallback

使用场景: 子组件某个事件触发依赖调用父组件的方法进行计算,这种场景通常是父组件通过 props 将函数传给子组件,但是这样会有个问题,父组件 state 发生改变,会导致子组件重新渲染,使用 useCallback 可以避免子组件的重复渲染

  • 如果子组件为 class 组件,则要继承 PureComponent
  • 如果子组件是 function 组件,则要使用 useMemo 缓存子组件

useReducer

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

export default function UseReducerUsage() {
function counter(state, action) {
switch (action.type) {
case "ADD":
return state + 1;
default:
return state;
}
}
const [state, dispatch] = useReducer(counter, 0);

return (
<div>
<p>{state}</p>
<button onClick={() => dispatch({ type: "ADD" })}>ADD</button>
</div>
);
}

自定义 Hook

实现一个能够实时获取最新值的 hook

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
import { useEffect, useRef, useState } from "react";

const useSyncState: any = (state: any) => {
const cbRef: { current: any } = useRef();
const [data, setData] = useState(state);

useEffect(() => {
cbRef.current && cbRef.current(data);
}, [data]);

return [
data,
(val: any, callback: any) => {
cbRef.current = callback;
setData(val);
},
];
};

export default useSyncState;

//
const [data, setData] = useSyncState(0);

setData(1, function (data) {
console.log("我是最新的值: ", data);
});

生命周期

v16.4 之前的生命周期不做演示

v16.4 新增的生命周期

getDerivedStateFromProps(props, state)

  • render 之前调用,并且在初始挂载及后续更新时都会被调用
  • 返回⼀个对象来更新 state,如果返回 null 则不更新 state,但 render 还是会执行

getSnapshotBeforeUpdate

  • render 之后,componentDidUpdate 之前
  • 返回值将作为参数传递给 componentDidUpdate(prevProps, prevState, snapshot)

v17 废弃的三个生命周期函数用 getDerivedStateFromProps 替代

componentWillMount、componentWillReceiveProps、componentWillUpdate: 使用这些生命周期的代码将更有可能在未来的 React 版本中存在缺陷,特别是一旦启用了异步渲染

状态管理

中心化状态管理

强调应用状态的中心化存储和管理,以及明确的数据流

  • Redux / Redux Toolkit: 通过 action-reducer 模式来管理状态的变更。纯函数 reducer 保证能识别新旧 state 状态变化
  • easy-peasy: 在 Redux 之上构建,提供更简单的 API
  • react-redux: Provider 为后代组件提供 store,connect 为组件提供数据和变更方法

响应式状态管理

调响应式和自动的状态更新机制,通常采用观察者模式

  • MobX / mobx-state-tree: 基于响应式和可观察状态的自动管理

原子化或声明式状态管理

将状态分解为更小的、可组合的单位,允许直接的读写操作和声明式的依赖管理

  • Recoil: 使用原子和选择器来管理状态,支持并行和异步操作
  • jotai: 类似于 Recoil,提供了更简洁的 API 和概念

Recoil 简介

初始化

使用 Recoil 的组件需要使用 RecoilRoot 组件包裹

定义状态

Atom 是一种新的状态,和传统的 state 不同,可以被任何组件订阅,当一个 Atom 被更新时,每个被订阅的组件都会用新的值来重新渲染

1
2
3
4
export const orgState = atom({
key: "orgState",
default: "百度",
});
订阅和更新状态
  • useRecoilState: 类似 useState 的一个 Hook,可以取到 atom 的值以及 setter 函数
  • useSetRecoilState: 只获取 setter 函数,如果只使用了这个函数,状态变化不会导致组件重新渲染
  • useRecoilValue: 只获取状态
派生状态

selector 表示一段派生状态,建立依赖于其他 atom 的状态。它有一个强制性的 get 函数,其作用与 redux 的 reselect, MobX 的 @computed, vue 的 computed 相似

1
2
3
4
5
6
7
8
9
10
11
12
const depState = selector({
key: "depState",
get: ({ get }) => {
const dep = get(orgState);
return dep;
},
});

function getDep() {
const dep = useRecoilValue(depState);
return <>dep: {dep}</>;
}
异步状态

选择器 get 回调中返回 Promise ,而不是返回值本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const userInfo = selector({
key: "userName",
get: async ({ get }) => {
const response = await axios({
userID: get(userIDState),
});
return response.name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(userInfo);
return <div>{userName}</div>;
}

状态机和状态管理模式

状态机(FSM)和状态图(Statecharts)的概念来管理状态的复杂逻辑和转换

  • XState: 提供了有限状态机和状态图的实现,适用于复杂状态逻辑的管理

轻量级和灵活的状态管理

提供了简单直接的状态管理功能,不强制采用特定的架构模式

  • Zustand: 提供了简单、轻量级的状态管理,可以用于中心化或非中心化的数据流

React 自带的状态管理工具

框架本身提供的状态管理机制,适合轻量级的状态管理需求

  • useState / useReducer: React 内置的 Hooks,用于组件内部状态管理
  • useContext + useReducer: 组合使用这两个 Hooks 可以实现跨组件的状态共享,类似于 Redux