使用useReducer + useContext 代替 react-redux

news2024/12/27 10:49:16

一. 概述

在 React16.8推出之前,我们使用react-redux并配合一些中间件,来对一些大型项目进行状态管理,React16.8推出后,我们普遍使用函数组件来构建我们的项目,React提供了两种Hook来为函数组件提供状态支持,一种是我们常用的useState,另一种就是useReducer, 其实从代码底层来看useState实际上执行的也是一个useReducer,这意味着useReducer是更原生的,你能在任何使用useState的地方都替换成使用useReducer.

Reducer的概念是伴随着Redux的出现逐渐在JavaScript中流行起来的,useReducer从字面上理解这个是reducer的一个Hook,那么能否使用useReducer配合useContext 来代替react-redux来对我们的项目进行状态管理呢?答案是肯定的。

二. useReducer 与 useContext

1. useReducer

在介绍useReducer这个Hook之前,我们先来回顾一下Reducer,简单来说 Reducer是一个函数(state, action) => newState:它接收两个参数,分别是当前应用的state和触发的动作action,它经过计算后返回一个新的state.来看一个todoList的例子:

export interface ITodo {id: numbercontent: stringcomplete: boolean
}

export interface IStore {todoList: ITodo[],
}

export interface IAction {type: string,payload: any
}

export enum ACTION_TYPE {ADD_TODO = 'addTodo',REMOVE_TODO = 'removeTodo',UPDATE_TODO = 'updateTodo',
}

import { ACTION_TYPE, IAction, IStore, ITodo } from "./type";

const todoReducer = (state: IStore, action: IAction): IStore => {const { type, payload } = actionswitch (type) {case ACTION_TYPE.ADD_TODO: //增加if (payload.length > 0) {const isExit = state.todoList.find(todo => todo.content === payload)if (isExit) {alert('存在这个了值了')return state}const item = {id: new Date().getTime(),complete: false,content: payload}return {...state,todoList: [...state.todoList, item as ITodo]}}return statecase ACTION_TYPE.REMOVE_TODO:// 删除 return {...state,todoList: state.todoList.filter(todo => todo.id !== payload)}case ACTION_TYPE.UPDATE_TODO: // 更新return {...state,todoList: state.todoList.map(todo => {return todo.id === payload ? {...todo,complete: !todo.complete} : {...todo}})}default:return state}
}
export default todoReducer 

上面是个todoList的例子,其中reducer可以根据传入的action类型(ACTION_TYPE.ADD_TODO、ACTION_TYPE.REMOVE_TODO、UPDATE_TODO)来计算并返回一个新的state。reducer本质是一个纯函数,没有任何UI和副作用。接下来看下useReducer:

 const [state, dispatch] = useReducer(reducer, initState); 

useReducer 接受两个参数:第一个是上面我们介绍的reducer,第二个参数是初始化的state,返回的是个数组,数组第一项是当前最新的state,第二项是dispatch函数,它主要是用来dispatch不同的Action,从而触发reducer计算得到对应的state.

利用上面创建的reducer,看下如何使用useReducer这个Hook:

const initState: IStore = {todoList: [],themeColor: 'black',themeFontSize: 16
}

const ReducerExamplePage: React.FC = (): ReactElement => {const [state, dispatch] = useReducer(todoReducer, initState)const inputRef = useRef<HTMLInputElement>(null);const addTodo = () => {const val = inputRef.current!.value.trim()dispatch({ type: ACTION_TYPE.ADD_TODO, payload: val })inputRef.current!.value = ''}const removeTodo = useCallback((id: number) => {dispatch({ type: ACTION_TYPE.REMOVE_TODO, payload: id })}, [])const updateTodo = useCallback((id: number) => {dispatch({ type: ACTION_TYPE.UPDATE_TODO, payload: id })}, [])return (<div className="example" style={{ color: state.themeColor, fontSize: state.themeFontSize }}>ReducerExamplePage<div><input type="text" ref={inputRef}></input><button onClick={addTodo}>增加</button><div className="example-list">{state.todoList && state.todoList.map((todo: ITodo) => {return (<ListItem key={todo.id} todo={todo} removeTodo={removeTodo} updateTodo={updateTodo} />)})}</div></div></div>)
}
export default ReducerExamplePage 

ListItem.tsx

import React, { ReactElement } from 'react';
import { ITodo } from '../typings';

interface IProps {todo:ITodo,removeTodo: (id:number) => void,updateTodo: (id: number) => void
}
constListItem:React.FC<IProps> = ({todo,updateTodo,removeTodo
}) : ReactElement => {const {id, content, complete} = todoreturn (<div> {/* 不能使用onClick,会被认为是只读的 */}<input type="checkbox" checked={complete} onChange = {() => updateTodo(id)}></input><span style={{textDecoration:complete?'line-through' : 'none'}}>{content}</span><button onClick={()=>removeTodo(id)}>删除</button></div>);
}
export default ListItem; 

useReducer利用上面创建的todoReducer与初始状态initState完成了初始化。用户触发增加、删除、更新操作后,通过dispatch派发不类型的Actionreducer根据接收到的不同Action,调用各自逻辑,完成对state的处理后返回新的state。

可以看到useReducer的使用逻辑,几乎跟react-redux的使用方式相同,只不过react-redux中需要我们利用actionCreator来进行action的创建,以便利用Redux中间键(如redux-thunk)来处理一些异步调用。

那是不是可以使用useReducer来代替react-redux了呢?我们知道react-redux可以利用connect函数,并且使用Provider来对<App />进行了包裹,可以使任意组件访问store的状态。

 <Provider store={store}> <App />
 </Provider> 

如果想要useReducer到达类似效果,我们需要用到useContext这个Hook。

2. useContext

useContext顾名思义,它是以Hook的方式使用React Context。先简单介绍 ContextContext设计目的是为了共享那些对于一个组件树而言是**“全局”**的数据,它提供了一种在组件之间共享值的方式,而不用显式地通过组件树逐层的传递props

const value = useContext(MyContext); 

useContext:接收一个context对象(React.createContext 的返回值)并返回该context的当前值,当前的 context值由上层组件中距离当前组件最近的<MyContext.Provider>value prop 决定。来看官方给的例子:

const themes = {light: {foreground: "#000000",background: "#eeeeee"},dark: {foreground: "#ffffff",background: "#222222"}
};

const ThemeContext = React.createContext(themes.light);

function App() {return (<ThemeContext.Provider value={themes.dark}><Toolbar /></ThemeContext.Provider>);
}

function Toolbar(props) {return (<div><ThemedButton /></div>);
}

function ThemedButton() {const theme = useContext(ThemeContext);return (<button style={{ background: theme.background, color: theme.foreground }}>I am styled by theme context!</button>);
} 

上面的例子,首先利用React.createContext创建了context,然后用ThemeContext.Provider标签包裹需要进行状态共享的组件树,在子组件中使用useContext获取到value值进行使用。

利用useReduceruseContext这两个Hook就可以实现对react-redux的替换了。

三. 代替方案

通过一个例子看下如何利用useReducer+useContext代替react-redux,实现下面的效果:

react-redux实现

这里假设你已经熟悉了react-redux的使用,如果对它不了解可以去 查看.使用它来实现上面的需求:

  • 首先项目中导入我们所需类库后,创建Store``Store/index.tsx````import { createStore,compose,applyMiddleware } from 'redux';import reducer from './reducer';import thunk from 'redux-thunk';// 配置 redux-thunkconst composeEnhancers = compose;const store = createStore(reducer,composeEnhancers(applyMiddleware(thunk)// 配置 redux-thunk));export type RootState = ReturnType<typeof store.getState>export default store; ```* 创建reduceractionCreator``reducer.tsximport { ACTION_TYPE, IAction, IStore, ITodo } from "../../ReducerExample/type";const defaultState:IStore = {todoList:[],themeColor: '',themeFontSize: 14};const todoReducer = (state: IStore = defaultState, action: IAction): IStore => {const { type, payload } = actionswitch (type) {case ACTION_TYPE.ADD_TODO: // 新增if (payload.length > 0) {const isExit = state.todoList.find(todo => todo.content === payload)if (isExit) {alert('存在这个了值了')return state}const item = {id: new Date().getTime(),complete: false,content: payload}return {...state,todoList: [...state.todoList, item as ITodo]}}return statecase ACTION_TYPE.REMOVE_TODO:// 删除 return {...state,todoList: state.todoList.filter(todo => todo.id !== payload)}case ACTION_TYPE.UPDATE_TODO: // 更新return {...state,todoList: state.todoList.map(todo => {return todo.id === payload ? {...todo,complete: !todo.complete} : {...todo}})}case ACTION_TYPE.CHANGE_COLOR:return {...state,themeColor: payload}case ACTION_TYPE.CHANGE_FONT_SIZE:return {...state,themeFontSize: payload}default:return state}}export default todoReducer actionCreator.tsx````import {ACTION_TYPE, IAction } from "…/…/ReducerExample/type"import { Dispatch } from “redux”;export const addCount = (val: string):IAction => ({ type: ACTION_TYPE.ADD_TODO, payload:val})export const removeCount = (id: number):IAction => ({ type: ACTION_TYPE.REMOVE_TODO, payload:id})export const upDateCount = (id: number):IAction => ({ type: ACTION_TYPE.UPDATE_TODO, payload:id})export const changeThemeColor = (color: string):IAction => ({ type: ACTION_TYPE.CHANGE_COLOR, payload:color})export const changeThemeFontSize = (fontSize: number):IAction => ({ type: ACTION_TYPE.CHANGE_FONT_SIZE, payload:fontSize})export const asyncAddCount = (val: string) => { console.log(‘val======’,val); return (dispatch:Dispatch) => { Promise.resolve().then(() => { setTimeout(() => { dispatch(addCount(val))}, 2000); }) }} ```最后我们在组件中通过useSelector,useDispatch这两个Hook来分别获取state以及派发action
const todoList = useSelector((state: RootState) => state.newTodo.todoList)
const dispatch = useDispatch()
...... 
useReducer + useContext 实现

为了实现修改颜色与字号的需求,在最开始的useReducer我们再添加两种action类型,完成后的reducer:

const todoReducer = (state: IStore, action: IAction): IStore => {const { type, payload } = actionswitch (type) {...case ACTION_TYPE.CHANGE_COLOR: // 修改颜色return {...state,themeColor: payload}case ACTION_TYPE.CHANGE_FONT_SIZE: // 修改字号return {...state,themeFontSize: payload}default:return state}
}
export default todoReducer 

在父组件中创建Context,并将需要与子组件共享的数据传递给Context.ProviderValue prop

const initState: IStore = {todoList: [],themeColor: 'black',themeFontSize: 14
}
// 创建 context
export const ThemeContext = React.createContext(initState);

const ReducerExamplePage: React.FC = (): ReactElement => {...const changeColor = () => {dispatch({ type: ACTION_TYPE.CHANGE_COLOR, payload: getColor() })}const changeFontSize = () => {dispatch({ type: ACTION_TYPE.CHANGE_FONT_SIZE, payload: 20 })}const getColor = (): string => {const x = Math.round(Math.random() * 255);const y = Math.round(Math.random() * 255);const z = Math.round(Math.random() * 255);return 'rgb(' + x + ',' + y + ',' + z + ')';}return (// 传递state值<ThemeContext.Provider value={state}><div className="example">ReducerExamplePage<div><input type="text" ref={inputRef}></input><button onClick={addTodo}>增加</button><div className="example-list">{state.todoList && state.todoList.map((todo: ITodo) => {return (<ListItem key={todo.id} todo={todo} removeTodo={removeTodo} updateTodo={updateTodo} />)})}</div><button onClick={changeColor}>改变颜色</button><button onClick={changeFontSize}>改变字号</button></div></div></ThemeContext.Provider>)
}
export default memo(ReducerExamplePage) 

然后在ListItem中使用const theme = useContext(ThemeContext); 获取传递的颜色与字号,并进行样式绑定

 // 引入创建的contextimport { ThemeContext } from '../../ReducerExample/index'...// 获取传递的数据const theme = useContext(ThemeContext); return (<div><input type="checkbox" checked={complete} onChange={() => updateTodo(id)} style={{ color: theme.themeColor, fontSize: theme.themeFontSize }}></input><span style={{ textDecoration: complete ? 'line-through' : 'none', color: theme.themeColor, fontSize: theme.themeFontSize }}>{content}</span><button onClick={() => removeTodo(id)} style={{ color: theme.themeColor, fontSize: theme.themeFontSize }}>删除</button></div>); 

可以看到在useReducer结合useContext,通过Contextstate数据给组件树中的所有组件使用 ,而不用通过props添加回调函数的方式一层层传递,达到了数据共享的目的。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/360007.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ccc-pytorch-基础操作(2)

文章目录1.类型判断isinstance2.Dimension实例3.Tensor常用操作4.索引和切片5.Tensor维度变换6.Broadcast自动扩展7.合并与分割8.基本运算9.统计属性10.高阶OP大伙都这么聪明&#xff0c;注释就只写最关键的咯1.类型判断isinstance 常见类型如下&#xff1a; a torch.randn(…

iOS开发:对苹果APNs远程推送原理的理解

本篇是对APNs推送原理的一个理解,希望看完后,能让你掌握一个知识点。 APNs是Apple Push Notification Service的缩写,也就是苹果的推送服务器。 远程通知的传递涉及几个关键组件: 您公司的服务器或第三方服务商,称为提供商服务器Apple 推送通知服务 (APNs)用户的设备您的…

Netty进阶实现自定义Rpc

项目地址&#xff1a;xz125/Rpc-msf (github.com)1 项目架构&#xff1a;RPC 框架包含三个最重要的组件&#xff0c;分别是客户端、服务端和注册中心。在一次 RPC 调用流程中&#xff0c;这三个组件是这样交互的&#xff1a;服务端(provider)在启动后会将它提供的服务列表和地址…

RocketMQ 第一章

RocketMQ 第一章 1、什么是MQ Message Queue&#xff08;消息队列&#xff09;&#xff0c;从字⾯上理解&#xff1a;⾸先它是⼀个队列。FIFO 先进先出的数据结构 —— 队列。消息队列就是所谓的存放消息的队列。 消息队列解决的不是存放消息的队列的⽬的&#xff0c;而是解…

AcWing1015.摘花生

AcWing 1015. 摘花生Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。地里每个道路的交叉点上都有种着一株花生苗&#xff0c;上面有若干颗花生&#xff0c;经过一株花生苗就能摘走该它…

《FPGA学习》->蜂鸣器播放

&#x1f34e;与其担心未来&#xff0c;不如现在好好努力。在这条路上&#xff0c;只有奋斗才能给你安全感。你若努力&#xff0c;全世界都会为你让路。蜂鸣器的发声原理由振动装置和谐振装置组成&#xff0c;而蜂鸣器又分为无源他激型与有源自激型。本实验采用无源蜂鸣器&…

嵌入物理(PINN)还是基于物理(AD)?

文章目录1. 传统"反演问题"1.1 反演问题是什么1.2 常见反演问题1.3 传统反演问题的困境2. 深度学习优势3. AD inversion 例子3.1 ADsurf3.2 ADseismic关于PINN的内容大家可以直接google PINN (Physical-informed neural network),其主要的目的是用一个神经网络拟合物…

K8S 部署 Jenkins

本文使用 bitnami 镜像部署 Jenkins 官方文档&#xff1a;https://github.com/bitnami/charts/tree/main/bitnami/jenkins 添加 bitnami 仓库 helm repo add bitnami https://charts.bitnami.com/bitnami自定义 values.yaml storageClass&#xff1a;集群的存储类&#xff…

(考研湖科大教书匠计算机网络)第五章传输层-第八节1:TCP连接管理理论部分(三次握手与四次挥手)

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航此部分内容借鉴博主【小林coding】 &#xff0c;其对计算机网络内容的图解可以说是深入浅出&#xff0c;尤其是三次握手和四次挥手这一部分&#xff0c;堪称全网最佳。所这…

OpenEuler安装软件方法

在树莓派上烧录好OpenEuler后上面是什么软件都没有的&#xff0c;像一些gcc的环境都需要自己进行配置。官方提供的安装命令是yum&#xff0c;但是执行yum是找不到命令的&#xff1a;   这个其实是因为OpenEuler中默认的安装软件使用了dnf而不是yum&#xff0c;所以软件的安装…

智能小车红外跟随原理

红外跟随电路红外跟随电路由电位器R17&#xff0c;R28&#xff1b;发光二极管D8&#xff0c;D9&#xff1b;红外发射管 D2&#xff0c;D4和红外接收管D3&#xff0c;D5和芯片LM324等组成,LM234用于信号的比较&#xff0c;并产生比较结果输出给单片机进行处理。智能小车红外跟随…

OpenGL学习日志之纹理

引言 为了使我们渲染的模型拥有更多细节&#xff0c;我们可以添加足够多的顶点&#xff0c;然后给每一个顶点都添加顶点颜色。但是这样就会产生很多额外的开销&#xff0c;因此就出现了纹理映射技术&#xff0c;我们通过纹理采样为物体的表面添加更多的细节。 纹理定义 通俗…

超25亿全球月活,字节依然没有流量

&#xff08;图片来源于网络&#xff0c;侵删&#xff09; 文|螳螂观察 作者| 搁浅虎鲸 注意看&#xff0c;这个男人叫梁汝波&#xff0c;是字节跳动的联合创始人&#xff0c;也是接棒张一鸣的新任CEO。 在字节跳动十周年之际&#xff0c;他发表了激情昂扬的演讲。“激发创…

【Datawhale图机器学习】图嵌入表示学习

图嵌入表示学习 学习视频&#xff1a;https://www.bilibili.com/video/BV1AP4y1r7Pz/ 如何把节点映射成D维向量&#xff1f; 人工特征工程&#xff1a;节点重要度、集群系数、Graphlet图表示学习&#xff1a;通过随机游走构造自监督学习任务。DeepWalk、Node2Vec矩阵分解深度…

win10字体模糊怎么办?看下面4种宝藏解决方法

最近很多用户反映电脑安装了Win10系统后出现字体发虚&#xff0c;模糊不清的问题&#xff0c;这看起来让人非常难受。win10字体模糊怎么办&#xff1f;来看下面4种宝藏解决方法&#xff01;下面的方法适用于各类台式电脑以及笔记本电脑哦&#xff01; 操作环境&#xff1a; 演示…

ESP开发环境搭建

一、windows中搭建 esp-idf tool(可选),下载连接如下:https://dl.espressif.com/dl/esp-idf/?idf4.4 下载安装tools后进入vscode进行插件安装&#xff08;未离线下载idf工具也可以通过第二步通过插件下载安装&#xff09; 1. vscode安装编译环境 ESP-IDF 需要安装一些必备工…

高并发系统设计之负载均衡

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 文章目录DNS负载均衡Nginx负载均衡负载均衡算法负载均衡配置超时配置被动健康检查与主动健康检查LVS/F5Nginx当我们的应用单实例不能支撑用户请求时&#xff0c;此时就需要扩容&#xff0c;从一台服务器扩容到…

【matplotlib】可视化解决方案——如何设置轴标签的透明度和大小

概述 Axes 标签对于读者理解图表非常重要&#xff0c;它描述了图表中展现的数据内容。通过向 axes 对象添加标签&#xff0c;可以有效理解图表所表达的内容。首先来了解一下 matplotlib 是如何组织图表的。最上层是一个 Figure 实例&#xff0c;包含绘图中所有可见和不可见的内…

北斗导航 | 2023 PTTI会议论文 2023 ITM会议论文 2022 ION GNSS+ 会议论文下载:ION 美国导航学会

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 2023 PTTI会议论文 2023 ITM会议论文 2022 ION GNSS+ 论文下载百度云链…

Teradata当年数据仓库的“一哥”为何突然退出中国市场:苦撑了3年,员工早有预料

2月15日&#xff0c;Teradata天睿公司官宣即将撤离中国市场。 又是一个艰难的决定&#xff0c;听着似乎很熟悉。Teradata为什么突然宣布结束在中国的直营&#xff1f;其实&#xff0c;回顾Teradata在中国市场的发展状况&#xff0c;一点也不突然。 多年前&#xff0c;我曾经与…