Open jtwang7 opened 3 years ago
参考文章:
useReducer 与 useState 作用很像,都是 React Hooks 中用于状态声明和管理的方法。 useState 通常用于一些简单的状态管理。而 useReducer 主要有以下应用场景:
const [state, dispatch] = useReducer(reducer, initState, init);
(state, action) => newState
。init(initState)
的返回值。
useReducer 提供了两种用于初始化 state 的参数(initState / init)
init(initialArg)
。
init 适用于将计算 state 的逻辑提取到 reducer 外部的场景。如果初始 state 计算比较复杂,可以单独分离一个创建 state 的函数。该函数仅会在组件初始创建的时候惰性求值。
dispatch 接收一个对象作为 action,并执行 useReducer 中相应的 reducer 函数,reducer 函数中 state 参数取自 useReducer 持有的 state 变量,action 参数取自 dispatch 接收的 action 对象。dispatch 执行结果的的返回值用于替换原有的 state 变量。
const [myState, dispatch] = useReducer(reducer, init);
// dispatch 流程
// dispatch(myAction) -> reducer(myState, myAction) & return newState -> myState = newState
React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。 这一特点在 React 性能优化中有着许多应用。例如 props 传递回调函数时,由于每次 render 都会导致回调函数重新定义,引发子组件的 rerender,传递 dispatch 可以避免该问题。此外,dispatch 还可以用于避免 useEffect 某个 state 频繁更新导致多次渲染的问题。
function LoginPage() {
// 可以看到 login 需要维护许多 state,而这些 state 又是相关的。
// 如果用 useState,则需要定义多个分散的 state,且 setState 会被分散在代码各处,不好维护。
const [name, setName] = useState(''); // 用户名
const [pwd, setPwd] = useState(''); // 密码
const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
const [error, setError] = useState(''); // 错误信息
const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录
const login = (event) => {
event.preventDefault();
setError('');
setIsLoading(true);
login({ name, pwd })
.then(() => {
setIsLoggedIn(true);
setIsLoading(false);
})
.catch((error) => {
// 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
setError(error.message);
setName('');
setPwd('');
setIsLoading(false);
});
}
return (
// 返回页面JSX Element
)
}
useState 能够将类组件中 state 对象单独分离,在一些简单的场景下,它是有优势的,因为类组件中只有一个 state,却管理了一整个组件的状态,useState 将其解耦了出来。但若存在多个 state 相关,useState 就似乎有点解耦过头了,它将一些局部相关的 state 都拆分开了。
我们可以将多个相关的 useState 都放在一处定义,这样 state 就被人为的归纳到了一处。但是我们却无法控制 setState 的书写位置,当我们需要修改 setState 时,就需要花费大量的精力去到代码中寻找。
// useReducer 的第二参数:初始化 state
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
}
// useReducer 的第一参数:定义了各个 actionType 对应的 state 修改行为
function loginReducer(state, action) {
switch(action.type) {
case 'login':
return {
...state,
isLoading: true,
error: '',
}
case 'success':
return {
...state,
isLoggedIn: true,
isLoading: false,
}
case 'error':
return {
...state,
error: action.payload.error,
name: '',
pwd: '',
isLoading: false,
}
default:
return state;
}
}
function LoginPage() {
// 这里仅用一次 useReducer 就代替了上述多个 useState 定义
const [state, dispatch] = useReducer(loginReducer, initState);
// 对象解构
const { name, pwd, isLoading, error, isLoggedIn } = state;
const login = (event) => {
event.preventDefault();
// 告知 dispatch 行为类型,就会执行事先定义好的逻辑,返回新的 state 对象。
dispatch({ type: 'login' });
login({ name, pwd })
.then(() => {
dispatch({ type: 'success' });
})
.catch((error) => {
dispatch({
type: 'error'
payload: { error: error.message }
});
});
}
return (
// 返回页面JSX Element
)
}
对比发现,useReducer 将 state 和 setState 操作都统一到了一起,在使用时,我们只需要调用 dispatch 指定对应的操作类型,不需要关心 reducer 是如何处理 state 的,我们看到的就是 state 输入的值以及 state 输出的结果。此外,useReducer 将 state 的处理逻辑都封装到了 dispatch 函数内,使得我们复用 state 的逻辑更加容易,比如在其他函数中也需要触发登录 error 状态,只需要
dispatch({ type: 'error' })
。
参考文章:
在上述两篇中,讲解了 reducer 的基础概念以及 useReducer 的基本使用。 useReducer 可以帮助我们集中式的处理复杂的state管理,此外 useReducer 另一大用处就是能够解决深层子组件的状态修改。 由于 React 是单向数据流传递,因此对于 state 在深层次的组件状态修改,往往需要通过组件树中多次传递回调函数来解决,通过 props 添加回调层层传递的方法存在以下问题:
context 作用就是对它所包含的组件树提供全局共享数据。 useContext 为函数组件提供了获取 consumer 的 Hook。
// 第一步:创建需要共享的context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// 第三步:使用共享 Context
const theme = useContext(ThemeContext);
render() {
return <Button theme={theme} />;
}
}
创建 Context 对象
const MyContext = React.createContext(defaultValue);
当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。
注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。
Context.Provider 包裹子级消费者
<MyContext.Provider value={/* 某个值 */}>消费组件</MyContext.Provider>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。
通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。
useContext 使用共享 Context
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的
调用了 useContext 的组件(消费组件)总会在 context 值变化时重新渲染。也就是说,当Context Provider 的 value 发生变化时,他的所有子级消费者都会rerender。
注意!!!若定义 Context 对象的组件和使用 Context 对象的组件位于不同的 JS 文件(通常都是这种情况),你需要将定义的 Context 对象导出,并在使用的组件中 import 导入。 组件导出使用的是
export default
,因此我们还可以用export
导出 Context 对象的引用。// App.js // export 导出 Context 对象 export const ThemeContext = React.createContext('light');
// 组件是通过 export default 导出的 export default class App extends React.Component { render() { // 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值 return (
React - Hooks (useReducer)
参考文章:
什么是 reducer?
简单来说,reducer 是一个函数,形如
(state, action) => newState
,其接收当前组件的 state 和触发的动作 action,计算并返回最新的 state。其中 action 为一个对象类型,包含 type 和 payload 两个属性。reducer 的使用重点:
immutable 在 React 中的必要性
Object.is
函数进行浅比较,若修改原对象的属性值,对象内存地址不发生改变,则不会出发组件的 rerender。因此需要整体替换 state 触发组件重新渲染。action
action 用来表示触发 state 修改的行为。 action 是一个对象类型,包含两个属性:
reducer 总结