当前位置: 首页 > 科技观察

React状态管理——useState-useReducer+useContext实现全局状态管理

时间:2023-03-12 18:55:45 科技观察

useReducer是useState的替代品,用于处理复杂的状态或逻辑。当与其他Hooks(useContext)结合使用时,在不引入一些第三方状态管理库的情况下(例如Redux和Mobx)有时是一个不错的选择。目标在本文结束时,您将了解:使用ContextAPI。在哪些场景下可以使用Context代替Redux等第三方状态管理库。如何使用useState+useContext实现暗模式切换。如何使用useReducer+useContext实现待办事项。什么是语境?Context解决了跨组件之间的通信。也是官方默认的解决方案。不需要引入第三方库。适用于存储对组件树来说是全局的、不需要频繁更新数据的数据状态。例如:主题、当前经过身份验证的用户、首选语言。使用React.createContext方法创建上下文,该方法接受一个参数作为其默认值并返回MyContext.Provider、MyContext.ConsumerReact组件。constMyContext=React.createContext(defaultValue);MyContext.Provider组件接收传递给子组件(MyContext.Consumer使用的组件)的值属性,无论嵌套有多深。{children}将我们的内容包裹在MyContext.Consumer组件中,用于订阅上下文变化,一般写在类组件中。{value=>{value}}}上面引入的不必要的代码嵌套也增加了代码的复杂度。ReactHooks提供的useContext让访问Context状态变得更简单。constApp=()=>{constvalue=useContext(newContext);控制台日志(值);//thiswillreturn{color:'black'}return

}上面我们做了一个Context,为了简单的理解,可以参考官网的Context和useContext的文档说明。下面我们通过两个例子来学习如何使用useContext来管理全局状态。useState+useContext主题切换本节中的第一个示例是使用Reacthooks的useState和useContextAPI的深色主题切换。实现上下文提供者在ThemeContext组件中,我们将主题定义为浅色和深色。定义ThemeProvider在context中维护两个属性:当前选择的主题theme,以及切换主题的函数toggleTheme()。ThemeProvider维护的这两个属性可以通过useContexthook在其他组件中获取。在使用useContext的时候,需要保证传入的是React.createContext创建的对象。这里我们可以自定义一个钩子useTheme,直接在其他组件中使用。代码位置:src/contexts/ThemeContext.js。importReact,{useState,useContext}来自"react";exportconstthemes={light:{type:'light',background:'#ffffff',color:'#000000',},dark:{type:'dark',背景:'#000000',颜色:'#ffffff',},};constThemeContext=React.createContext({theme:themes.dark,toggleTheme:()=>{},});exportconstThemeProvider=({children})=>{const[theme,setTheme]=useState(themes.dark);constcontext={theme,toggleTheme:()=>setTheme(theme===themes.dark?themes.light:themes.dark)}返回{children}}exportconstuseTheme=()=>{constcontext=useContext(ThemeContext);returncontext;};创建一个AppProviders,用来组创建多个上下文。代码位置:src/contexts/index.js。import{ThemeProvider}from'./ThemeContext';constAppProviders=({children})=>{return{children}}exportdefaultAppProviders;在App.js文件中实现ToggleTheme组件,添加AppProviders组件作为根组件放在最上面,这样被包裹的组件就可以使用AppProviders组件提供的属性。代码位置:src/App.js。从'./contexts'导入AppProviders;从'./components/ToggleTheme'导入ToggleTheme;导入'./App.css';constApp=()=>();导出默认应用程序;在ToggleTheme组件中,我们使用自定义的useTheme钩子来访问主题对象和toggleTheme函数。下面创建一个简单的主题开关来设置背景颜色和文本颜色。代码位置:src/components/ToggleTheme.jsx。import{useTheme}from'../contexts/ThemeContext'constToggleTheme=()=>{const{theme,toggleTheme}=useTheme();返回切换浅色/深色主题在React中使用useState和useContext切换浅色/深色主题

切换到{theme.type}模式
}exportdefaultToggleTheme;Demo演示视频示例代码地址:https://github.com/qufei1993/react-state-example/tree/usestate-usecontext-theme。useReducer+useContext实现Todos使用useReducer和useContext来完成一个Todos。这个例子非常简单,可以帮助我们学习如何实现一个简单的状态管理工具,比如Redux,它可以跨组件共享数据状态。reducer在src/reducers目录下实现了reducer需要的逻辑。定义的initialState变量和reducer函数都是为useReducerHook准备的。在这个地方,都需要导出。reducer函数是一个纯函数。了解Redux小细节的小伙伴应该对这个概念不陌生。//src/reducers/todos-reducer.jsxexportconstTODO_LIST_ADD='TODO_LIST_ADD';exportconstTODO_LIST_EDIT='TODO_LIST_EDIT';exportconstTODO_LIST_REMOVE='TODO_LIST_REMOVE';constrandomID=()=>Math.floor(Math.random()*10000);exportconstinitialState={todos:[{id:randomID(),content:'todolist'}],};constreducer=(state,action)=>{switch(action.type){案例TODO_LIST_ADD:{constnewTodo={id:randomID(),content:action.payload.content};返回{todos:[...state.todos,newTodo],}}caseTODO_LIST_EDIT:{return{todos:state.todos.map(item=>{constnewTodo={...item};if(item.id===action.payload.id){newTodo.content=action.payload.content;}returnnewTodo;})}}caseTODO_LIST_REMOVE:{返回{todos:state.todos.filter(item=>item.id!==action.payload.id),}}默认值:返回状态;}}exportdefaultreducer;Context跨组件数据共享定义TodoContextexportstate,dispatch,结合useContext自定义一个useTodohook获取信息//src/contexts/TodoContext.jsimportReact,{useReducer,useContext}from"react";importreducer,{initialState}from"../reducers/todos-reducer";constTodoContext=React.createContext(null);导出constTodoProvider=({children})=>{const[state,dispatch]=useReducer(reducer,initialState);constcontext={state,dispatch}return{children}}exportconstuseTodo=()=>{constcontext=useContext(TodoContext);}returncontext;};//src/contexts/index.jsimport{TodoProvider}来自'./TodoContext';constAppProviders=({children})=>{return{children}}exportdefaultAppProviders;现实Todos组在TodoAdd、Todo、Todos三个组内分别都可以通过useTodo()hook获取到状态、调度。从“反应”导入{useState};从“../../contexts/TodoContext”导入{useTodo};从“../../reducers/todos-reducer”导入{TODO_LIST_ADD,TODO_LIST_EDIT,TODO_LIST_REMOVE};constTodoAdd=()=>{console.log('TodoAddrender');const[内容,setContent]=useState('');const{dispatch}=useTodo();返回setContent(e.target.value)}/>{dispatch({type:TODO_LIST_ADD,payload:{content}})}}>添加
};constTodo=({todo})=>{console.log('待办渲染');const{dispatch}=useTodo();const[isEdit,setIsEdit]=useState(false);const[content,setContent]=useState(todo.content);返回{!isEdit?<>{todo.content}
setIsEdit(true)}>编辑dispatch({type:TODO_LIST_REMOVE,payload:{id:todo.id}})}>删除:<>setContent(e.target.value)}/>
{setIsEdit(false);dispatch({type:TODO_LIST_EDIT,payload:{id:todo.id,content}})}}>更新setIsEdit(false)}>取消}}constTodos=()=>{console.log('Todosrender');const{状态}=useTodo();return待办事项应用</h2>useReducer+useContent实现todos

{state.todos.map(todo=>)}}导出默认待办事项;Demo演示上面的代码满足要求没有问题,但是存在性能问题。一个熟悉的变化,所有依赖于Context的组件也会被重新渲染,看下面的视频演示:视频示例代码地址:https://github.com/qufei1993/react-state-example/tree/usereducer-usecontext-todosContext概要useState/useReducer管理组件的状态。如果子组件想要获取根组件的状态,一种简单的方式是通过Props逐层传递,另一种是将需要传递的数据封装到Context的Provider中,子组件获取通过useContext实现全局状态共享。在构建小型应用时,Context比Redux更容易实现,不需要依赖第三方库。同时,还要看适用场景。官网上也有说明,适用于存储对组件树来说是全局的数据状态,没有频繁的数据更新(例如:主题,当前认证用户,首选语言)。下面是你在使用Context时会遇到的几个问题:一旦Context中的某个属性发生变化,所有依赖于Context的组件也会被重新渲染,即使React.memo()或shouldComponentUpdate()被优化为组件,它仍然会触发强制更新。如何维护太多上下文?因为子组件需要被Context.Provider包裹才能获取上下文的值,太多的Context,比如……是不是有点像之前的“回调回调地狱”。这里的一种解决方案是创建一个存储容器,请参阅结合容器以使其成为“全局”状态的最佳实践,Appswithmanycontainers。提供者父组件的重新渲染可能会导致消费者组件出现意外的渲染问题,请参考上下文注意事项。在我们实际的React项目中,没有一个Hook或API可以解决我们所有的问题,根据应用程序的大小和架构选择适合您的方法是最重要的。介绍完React官方提供的状态管理工具,下一节介绍社区状态管理领域的“老大哥Redux”。阅读文末原文查看文中两个示例代码!参考https://blog.logrocket.com/guide-to-react-usereducer-hook/https://zh-hans.reactjs.org/docs/context.html