一文带你搞懂React中的useReducer

 更新时间:2023年06月16日 09:44:49   作者:何遇er  
useReducer 是除useState之外另一个与状态管理相关的 hook,这篇文章主要为大家介绍了useReducer应用的相关知识,感兴趣的小伙伴可以跟随小编一起学习一下

useReducer 是除useState之外另一个与状态管理相关的 hook,对于熟悉 Redux 的工程师而言,理解 useReducer 将变得简单,在 React 内部,useState 由 useReducer 实现。

useReducer 的类型定义

useReducer 的类型定义如下:

function useReducer<R extends ReducerWithoutAction<any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
function useReducer<R extends ReducerWithoutAction<any>>(
    reducer: R,
    initializerArg: ReducerStateWithoutAction<R>,
    initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I & ReducerState<R>,
    initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

useReducer 的类型定义很复杂,一共有 5 个重载,总体而言,它最多接受3个参数,第 1 个参数是一个用于更新状态的函数,之后将它称为 reducer。第 3 个参数非必填,如果不存在第 3 个参数,那么第 2 个参数将作为状态的初始值;如果存在第 3 个参数,那么它必须是函数,此时第 2 个参数被传递给该函数用于计算状态的初始值,该函数只在组件初始渲染时执行一次。useReducer 的返回值是一个长度为 2 的数组,数组的第 1 个位置是状态值,第 2 个位置是一个用于触发状态更新的函数,将它记为dispatch,调用 dispatch 将导致 reducer 被调用。接下来通过计数器 demo 对比 useState 和useReducer 用法上的差异。

用 useState 实现计数器

用 useState 实现计数器,代码如下:

function UseStateCounterDemo() {
    const [value, setValue] = useState<number>(0)
    const [step, setStep] = useState<number>(1)
    const onChangeStep = (ev: React.ChangeEvent<HTMLInputElement>) => {
        setStep(Number(ev.target.value))
    }
    return (
        <div>
            <h2>用useState实现计数器</h2>
            count: {count}; 
            step: <input type='number' value={step} onChange={onChangeStep}/>
            <button onClick={() => setValue(value + step)}>加</button>
            <button onClick={() => setValue(value - step)}>减</button>
            <button onClick={() => {setValue(0); setStep(1)}}>重置</button>
        </div>
    )
}

上述代码很简单,它使用 useState 定义了两个状态,分别表示计数器的值和加减步数,在过去的文章里已对 useState 做过详细的介绍,这里不再赘述,下面详细介绍用 useReducer 实现计数器。

用 useReducer 实现计数器

定义 reducer

使用 useReducer hook 离不开reducer,它是一个函数,用于更新 useReducer 返回的状态,代码如下:

const initArg: Counter = {value: 0, step: 1}
// 它用于更新state
function reducer(prevState: Counter, action: Action): Counter  {
    switch (action.type) {
        case 'increment':
            return {
                ...prevState,
                value: prevState.value + prevState.step
            }
        case 'decrement':
            return {
                ...prevState,
                value: prevState.value - prevState.step
            }
        case 'reset':
            return initArg
        case 'changeStep':
            return {
                ...prevState,
                step: action.value || initArg.value
            }
        default:
            throw new Error();
    }
}

reducer 用于更新状态,计数器 demo 有两个状态,分别是计数器的当前值和它的加减步数,这两个状态密切相关,这里用一个 TS 接口去描述它,代码如下:

interface Counter {
   // 计数器的当前值
    value: number
   // 计数器的加减步数
    step: number
}

计数器有 3 种操作,分别是加、减、重置和修改步数,这里用 TS 接口描述这些行为,代码如下:

interface Action {
    type: 'increment' | 'decrement' | 'reset'|'changeStep',
    value?: number
}

在组件中使用 useReducer

// 计数器的初始值
const initArg: Counter = {value: 0, step: 1}
function UseReducerCounterDemo() {
    // 使用上一步定义 reducer
    const [counter, dispatch] = useReducer(reducer, initArg)
    const onChangeStep = (ev: React.ChangeEvent<HTMLInputElement>) => {
        dispatch({type: 'changeStep',value: Number(ev.target.value)})
    }
    return (
        <div>
            <h2>用useReducer实现计数器</h2>
            count: {counter.value}; 
            step: <input type='number' value={counter.step} onChange={onChangeStep}/>
            <button onClick={() => dispatch({type: 'increment'})}>加</button>
            <button onClick={() => dispatch({type: 'decrement'})}>减</button>
            <button onClick={() => dispatch({type: 'reset'})}>重置</button>
        </div>
    )
}

只考虑代码量,读者应该都会认为useState比useReducer更简洁。仔细观察可以发现,上述计数器除了有value还有step,step对value有影响,UseStateCounterDemo组件将它们零散地保存在不同的状态中,UseStateCounterDemo组件将它们关联在同一个状态中,内聚性更高。useState与useReducer没有优劣之外,它们有各自适用的场景,这里有如下建议:

1.当状态是一个拥有很多属性的复杂对象,并且状态更新涉及复杂的逻辑时,推荐使用useReducer。

2.当某个状态的更新受另一个状态影响时,推荐使用 useReducer将它们放在一起。

3.当状态只是单独的基本数据类型时,推荐使用 useState。

介绍 useEffect时曾强调,为了在 effect 中拿到状态最新的值,必须给 effect 设置正确地依赖项。在 useEffect 中使用 useReducer 返回的 dispatch 能让 effect 自给自足,减少依赖项。示例代码如下:

function DispatchDemo(props: {step: number}) {
    function reducer(value: number) {
	// 始终能访问到最新的 step 
        return props.step + value
    }
    const [value, dispatch] = useReducer(reducer, 0)
    useEffect(() => {
        document.body.addEventListener('click', dispatch)
        return () => {
            document.body.removeEventListener('click', dispatch)
        }
    }, [])
    return // todo
}

上述代码 useEffect 的第二个参数为空数组,这意味着 effect 只在组件初始渲染时执行。由于React 会让 dispatch 在组件的每次渲染中保持唯一的引用,所以 dispatch 不必出现在effect的依赖中,此特性与 ref 类似。虽然 dispatch 的引用保持不变,但它能调用组件本次渲染时的reducer,在 reducer 作用域将得到最新的 state 和 props。

推荐阅读:

我搞懂了 React 的 useState 和 useEffect

到此这篇关于一文带你搞懂React中的useReducer的文章就介绍到这了,更多相关React useReducer内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文详解ReactNative状态管理rematch使用

    一文详解ReactNative状态管理rematch使用

    这篇文章主要为大家介绍了ReactNative状态管理rematch使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • React类组件更新的底层逻辑案例详解

    React类组件更新的底层逻辑案例详解

    这篇文章主要介绍了React类组件的更新过程,包括组件初始化、更新和卸载的生命周期方法,以及如何使用setState和forceUpdate来控制视图的更新,感兴趣的朋友一起看看吧
    2025-01-01
  • React组件中按钮的loading状态失效问题的解决方案

    React组件中按钮的loading状态失效问题的解决方案

    在React开发过程中,我遇到这样的情况:页面按钮的loading属性失效,尽管通过useEffect打印发现loading状态(确实在true和false之间切换,但按钮却没有表现出预期的加载效果,所以本文给大家介绍了失效的解决方案,需要的朋友可以参考下
    2025-07-07
  • react 页面加载完成后自动执行标签的点击事件的两种操作方法

    react 页面加载完成后自动执行标签的点击事件的两种操作方法

    这篇文章主要介绍了react 页面加载完成后自动执行标签的点击事件,本文给大家分享两种操作方法结合示例代码给大家讲解的非常详细,需要的朋友可以参考下
    2022-12-12
  • React Antd Select组件输入搜索时调用接口方式

    React Antd Select组件输入搜索时调用接口方式

    为优化Select组件中文输入时的接口调用,使用lodash.debounce实现防抖,避免每输入一个字母即触发请求,待用户点击空格完成输入后,再调用接口获取数据,提升性能与用户体验
    2025-08-08
  • React利用路由实现登录界面的跳转

    React利用路由实现登录界面的跳转

    这篇文章主要介绍了React利用路由实现登录界面的跳转,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • ReactJs设置css样式的方法

    ReactJs设置css样式的方法

    本篇文章主要介绍了ReactJs设置css样式的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • React+redux项目搭建流程步骤分析

    React+redux项目搭建流程步骤分析

    本文介绍了如何搭建一个React项目,包括创建项目、去除无用文件夹、配置项目、安装craco扩展webpack配置、配置代码格式化、创建路由、集成Redux等步骤,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • 简单分析React中的EffectList

    简单分析React中的EffectList

    这篇文章主要简单分析了React中的EffectList,帮助大家更好的理解和学习使用React进行前端开发,感兴趣的朋友可以了解下
    2021-04-04
  • React 在非组件环境切换路由的方法

    React 在非组件环境切换路由的方法

    这篇文章主要介绍了React 在非组件环境切换路由的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-10-10

最新评论