前言
在React的世界里,useState、useState、useRef等是我们最早接触、最常用的Hooks,没有他们我们根本很难完成项目的开发,他们简单、直接,适合管理组件内部的局部状态。但日新月异的今天,useState像一名老者,垂垂老矣,面向企业级的复杂项目时,useState似乎有些力不从心。而useReducer与useContext便是两位强大的“援手”。
useState:小而美的状态管理
useState的优势就在于简单且直观。它适合一些简单独立的状态。
const [count, setCount] = useState(0);
不适于复杂的状态,比如许许多多不同的action更新不同的state,或者多个组件需要共享同一份状态的时候,useState就会显得相当笨重,代码也变得很混乱,可维护性差。
假设我们有一个表单,包含计数器、用户名、登录等多种状态,伪代码就会是:
function ProfileForm() {
const [count, setCount] = useState(0);
const [username, setUsername] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [theme, setTheme] = useState('light');
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const handleLogin = () => setIsLoggedIn(true);
const handleLogout = () => setIsLoggedIn(false);
const handleThemeChange = (e) => setTheme(e.target.value);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>加一</button>
<button onClick={decrement}>减一</button>
<input
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="用户名"
/>
<button onClick={handleLogin} disabled={isLoggedIn}>登录</button>
<button onClick={handleLogout} disabled={!isLoggedIn}>登出</button>
<select value={theme} onChange={handleThemeChange}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
<p>当前主题:{theme}</p>
<p>登录状态:{isLoggedIn ? '已登录' : '未登录'}</p>
<p>用户名:{username}</p>
</div>
);
}
useReducer:复杂状态的“指挥官”
useReducer 的出现,正是为了解决复杂状态管理的问题。它的思想类似于 Redux,通过 action 和 reducer 来集中管理状态变化。这样,状态的变更逻辑更加清晰、可维护。
基本用法:
const [state, dispatch] = useReducer(reducer, initialState);
其中state表示当前的状态对象,dispatch是派发action的函数,用于触发状态的更新,reducer是一个纯函数,接受当前state和action,返回state,initialState就是字面初始状态的意思。
参数1:state(当前状态)
state就是当前组件的状态数据,可以理解为就是我们熟知的useState返回的state,state由useReducer返回,其值会随着dispatch的action发生变化,用来渲染UI或者作为业务逻辑的依据。
const initialState = {
count: 0,
username: '',
isLoggedIn: false,
theme: 'light'
};
比如在上面demo中,state就是theme,isLogged,count,username等,可以通过state.username、state.theme来访问。
参数2:关于dispatch(派发函数)
dispatch(action)就是用来派发action,触发reducer执行,进而更新state的函数,比如下面代码,按钮中触发点击事件,用dispatch派发action(通常是一个对象,至少包含type,还可以用payload携带数据),进而触发reducer执行加一/减一操作。
<button onClick={() => dispatch({ type: 'increment' })}>加一</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减一</button>
参数3:reducer(状态处理函数)
由上面的参数2可以知道,reducer是一个状态处理函数,reducer的概念是伴随Redux的出现在JS中逐渐流行起来,但学习reducer并不需要去学习Redux,简单点就是它本质上是一个纯函数(没有任何UI和副作用,相同的输入输出),接收当前的state和action,加一操作可以按count => count + 1 处理,返回新的state对象,因此我们通过reducer函数就可以很容易知道state的变化。
参数4: initialState(初始状态)
initialState很好理解就是字面的初始状态的意思,它是组件状态的初始值,它可以是任意的类型(对象、数组、数字、字符串等),但通常是对象,便于管理多个字段,当useReducer第一次执行时,会用initialState作为state的初始值。
const initialState = {
count: 0,
username: '',
isLoggedIn: false,
theme: 'light'
};
于是就可以对上面代码进行改写为下面的伪代码:
const initialState = {
count: 0,
username: '',
isLoggedIn: false,
theme: 'light'
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'setUsername':
return { ...state, username: action.payload };
case 'login':
return { ...state, isLoggedIn: true };
case 'logout':
return { ...state, isLoggedIn: false };
case 'setTheme':
return { ...state, theme: action.payload };
default:
throw new Error('未知 action');
}
}
function ProfileForm() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>加一</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减一</button>
<input
value={state.username}
onChange={e => dispatch({ type: 'setUsername', payload: e.target.value })}
placeholder="用户名"
/>
<button onClick={() => dispatch({ type: 'login' })} disabled={state.isLoggedIn}>登录</button>
<button onClick={() => dispatch({ type: 'logout' })} disabled={!state.isLoggedIn}>登出</button>
<select
value={state.theme}
onChange={e => dispatch({ type: 'setTheme', payload: e.target.value })}
>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
<p>当前主题:{state.theme}</p>
<p>登录状态:{state.isLoggedIn ? '已登录' : '未登录'}</p>
<p>用户名:{state.username}</p>
</div>
);
}
上述我们定义的6个state来描述页面的状态,可以预见当更多状态加入到页面,这个页面可想而知有多乱,出现一个bug更是一个噩梦了。
useContext:跨组件的“传送带”
useContext 则解决了“组件传参地狱”的问题。当多个组件需要访问同一份数据时,useContext 可以让我们轻松地在组件树中传递数据,而无需层层 props 传递。
// 创建Context
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
// 提供Context
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// ThemeSwitcher.jsx 在子组件中消费 Context
function ThemeSwitcher() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<span>当前主题:{theme}</span>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</div>
);
}
// App.jsx 应用Provider
function App() {
return (
<ThemeProvider>
<ThemeSwitcher />
{/* 其他组件 */}
</ThemeProvider>
);
}
在App中,被<ThemeProvider>包裹的<ThemeSwitcher />和其他组件,都可以共享消费Context,比如ThemeSwitcher中可以直接访问到theme,让我们轻松地在组件树中传递数据,而无需层层props传递
useReducer与useContext:状态管理之神!
useReducer与useContext的全局状态管理
使用useReducer与useContext后上面的demo就变成了:
// 1. 创建 context
const AppContext = createContext();
const initialState = {
count: 0,
username: '',
isLoggedIn: false,
theme: 'light'
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'setUsername':
return { ...state, username: action.payload };
case 'login':
return { ...state, isLoggedIn: true };
case 'logout':
return { ...state, isLoggedIn: false };
case 'setTheme':
return { ...state, theme: action.payload };
default:
throw new Error('未知 action');
}
}
// 2. Provider 组件
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
function ProfileForm() {
const { state, dispatch } = useAppContext();
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>加一</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减一</button>
<input
value={state.username}
onChange={e => dispatch({ type: 'setUsername', payload: e.target.value })}
placeholder="用户名"
/>
<button onClick={() => dispatch({ type: 'login' })} disabled={state.isLoggedIn}>登录</button>
<button onClick={() => dispatch({ type: 'logout' })} disabled={!state.isLoggedIn}>登出</button>
<select
value={state.theme}
onChange={e => dispatch({ type: 'setTheme', payload: e.target.value })}
>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
<p>当前主题:{state.theme}</p>
<p>登录状态:{state.isLoggedIn ? '已登录' : '未登录'}</p>
<p>用户名:{state.username}</p>
</div>
);
}
import React from 'react';
import ReactDOM from 'react-dom';
import { AppProvider } from './AppContext'; // 假设上面代码在 AppContext.js
import ProfileForm from './ProfileForm';
ReactDOM.render(
<AppProvider>
<ProfileForm />
{/* 其他需要全局状态的组件 */}
</AppProvider>,
document.getElementById('root')
);
总结
至此简单介绍了React中 useState、useReducer和useContext 三大常用Hooks的使用场景与优缺点。
useState适合管理简单、独立的局部状态,但在复杂或多状态场景下会导致代码混乱、难以维护。
useReducer 通过集中管理状态和变更逻辑,适合处理复杂状态,提升了代码的可维护性。
useContext 则解决了多组件间数据共享和“props传递地狱”的问题。
详细请参考
- React 官方文档