一、useReducer
import { useReducer } from 'react';
// 定义reducer函数
function reducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.payload, completed: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload? {...todo, completed:!todo.completed } : todo
);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(reducer, []);
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text });
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
return (
<div>
<input type="text" placeholder="Add a new todo" onKeyPress={(e) => {
if (e.key === 'Enter') {
addTodo(e.target.value);
e.target.value = '';
}
}} />
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.completed? 'line-through' : 'none' }} onClick={() => toggleTodo(todo.id)}>{todo.text}</li>
))}
</ul>
</div>
);
}
1. 基础使用
- 作用:让 React 管理多个相对关联的状态数据。
- 使用步骤
- 定义
reducer
函数,根据不同的action
返回不同的新状态。 - 在组件中使用
useReducer
分派action
,得到当前状态state
和用于触发reducer
的dispatch
函数。 - 通过调用
dispatch
函数传入action
对象,触发reducer
函数,分派action
操作,更新视图。
2. 更新流程
- 组件调用
dispatch
函数发送action
。 reducer
函数接收当前state
和action
,根据action
类型计算新的state
。- 组件使用新的
state
更新视图。
3. 分派 action 传参
- 分派
action
时如果想传递参数,需在action
对象中添加payload
参数放置状态参数。reducer
函数根据action
类型和payload
值计算新状态。
二、渲染性能优化
1. useMemo
import { useMemo, useState } from 'react';
function expensiveCalculation(num) {
console.log('执行昂贵计算');
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.random() * num;
}
return result;
}
function App() {
const [count, setCount] = useState(1);
const [otherValue, setOtherValue] = useState('');
const memoizedResult = useMemo(() => expensiveCalculation(count), [count]);
return (
<div>
<input type="number" value={count} onChange={(e) => setCount(Number(e.target.value))} />
<input type="text" value={otherValue} onChange={(e) => setOtherValue(e.target.value)} />
<p>计算结果: {memoizedResult}</p>
</div>
);
}
- 作用:在每次重新渲染时缓存计算结果。
- 使用场景及优化方式
- 当一个计算结果依赖于某个状态,但该状态变化不应该每次都重新计算时使用。
- 将计算逻辑放在
useMemo
的回调函数中,并将依赖的状态作为useMemo
的依赖项数组传入,只有依赖项变化时才重新计算。
2. React.memo
import React, { useState } from 'react';
const ChildComponent = React.memo(function Child({ value }) {
console.log('子组件渲染');
return <div>{value}</div>;
});
function App() {
const [count, setCount] = useState(0);
const [otherValue, setOtherValue] = useState('');
return (
<div>
<input type="number" value={count} onChange={(e) => setCount(Number(e.target.value))} />
<input type="text" value={otherValue} onChange={(e) => setOtherValue(e.target.value)} />
<ChildComponent value={count} />
</div>
);
}
- 作用:允许组件在
props
没有改变的情况下跳过重新渲染。 - 组件默认渲染机制及优化方式
- 默认顶层组件重新渲染时,子组件都会重新渲染。
- 使用
React.memo
包裹组件,只有props
发生变化时才重新渲染。 - 对于
props
的比较是浅比较,针对对象数据类型对比引用是否相等,可自定义比较函数实现深度比较。
3. useCallback
import React, { useCallback, useState } from 'react';
const ChildComponent = React.memo(function Child({ onButtonClick }) {
console.log('子组件渲染');
return <button onClick={onButtonClick}>点击我</button>;
});
function App() {
const [count, setCount] = useState(0);
const handleButtonClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<ChildComponent onButtonClick={handleButtonClick} />
<p>计数: {count}</p>
</div>
);
}
- 作用:缓存回调函数,使函数在组件渲染时保持引用稳定。
- 使用场景及优化方式
- 当给子组件传递回调函数类型的
prop
且希望在父组件重新渲染时子组件不重新渲染时使用。 - 将回调函数用
useCallback
包裹,并传入依赖项数组,只有依赖项变化时回调函数引用才会改变。
三、forwardRef + useImperativeHandle
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const MyInputComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
useImperativeHandle(ref, () => ({
focus: focusInput
}));
return <input type="text" ref={inputRef} {...props} />;
});
function App() {
const myInputRef = useRef(null);
const handleFocus = () => {
myInputRef.current.focus();
};
return (
<div>
<MyInputComponent ref={myInputRef} />
<button onClick={handleFocus}>聚焦输入框</button>
</div>
);
}
1. forwardRef
- 作用:允许组件使用
ref
将一个 DOM 节点暴露给父组件。
2. useImperativeHandle
- 作用:如果不想暴露子组件中的 DOM 而是想暴露子组件内部的方法,可使用该函数。
四、Class API
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
incrementCount = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
return (
<div>
<p>计数: {this.state.count}</p>
<button onClick={this.incrementCount}>增加计数</button>
</div>
);
}
}
function App() {
return (
<div>
<Counter />
</div>
);
}
1. 基础体验
- 使用 ES6 原生 Class API 编写 React 组件,包含状态变量定义、事件回调定义和 UI 模版渲染。
2. 组件生命周期
3. 组件通信
- 父传子:通过
props
传递数据。 - 子传父:子组件通过调用父组件传递的回调函数传递数据。
五、zustand
1. 快速上手
// store.js
import { create } from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}));
// App.js
import React from 'react';
import useStore from './store';
function App() {
const { count, increment } = useStore();
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加计数</button>
</div>
);
}
- 创建 store:使用
create
函数创建store
,定义状态和相关操作函数(如修改状态的函数)。 - 绑定组件:在组件中使用
useStore
获取store
中的状态和操作函数,并在视图中使用。
2. 异步支持
// store.js
import { create } from 'zustand';
const useStore = create(set => ({
data: null,
fetchData: async () => {
const response = await fetch('https://example.com/api/data');
const jsonData = await response.json();
set({ data: jsonData });
}
}));
// App.js
import React, { useEffect } from 'react';
import useStore from './store';
function App() {
const { data, fetchData } = useStore();
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<div>
{data? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>正在加载数据...</p>}
</div>
);
}
- 直接在
store
的操作函数中编写异步逻辑,将获取的数据通过set
函数更新store
状态。
3. 切片模式
// counterSlice.js
import { create } from 'zustand';
const createCounterSlice = set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
});
// userSlice.js
import { create } from 'zustand';
const createUserSlice = set => ({
user: null,
setUser: userData => set({ user: userData })
});
// store.js
import { create } from 'zustand';
import { createCounterSlice, createUserSlice } from './slices';
const useStore = create(set => ({
...createCounterSlice(set),
...createUserSlice(set)
}));
// App.js
import React from 'react';
import useStore from './store';
function App() {
const { count, increment, user, setUser } = useStore();
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加计数</button>
<p>用户信息: {user? JSON.stringify(user) : '未登录'}</p>
<button onClick={() => setUser({ name: 'John', age: 30 })}>设置用户信息</button>
</div>
);
- 当
store
较大时,可将其拆分成多个切片(每个切片定义相关的状态和操作),然后组合这些切片创建最终的store
。
4. 对接 DevTools
- 安装
simple-zustand-devtools
调试工具,在开发环境下配置并使用该工具调试store
状态变化。