高端网站建设有哪些,机电工程东莞网站建设技术支持,专业旅游网站开发系统,北京展柜设计制作公司多更新的组件状态
在组件状态中#xff0c;我们了解到了React中组件的状态及其用法。组件状态的主要作用就是由状态设置触发组件的局部UI渲染#xff0c;状态用法也很简单。
有时候有些组件对于状态的更新操作很多#xff0c;这就让我们很难短时间理清组件更新逻辑。示例如…多更新的组件状态在组件状态中我们了解到了React中组件的状态及其用法。组件状态的主要作用就是由状态设置触发组件的局部UI渲染状态用法也很简单。有时候有些组件对于状态的更新操作很多这就让我们很难短时间理清组件更新逻辑。示例如下获取初始数据后用户可以对其进行添加、删除和修改操作。import{useState}fromreact;functionAddTask({onAddTask}){const[text,setText]useState();return(input placeholderAdd taskvalue{text}onChange{esetText(e.target.value)}/button onClick{(){setText();onAddTask(text);}}Add/button/);}functionTaskList({tasks,onChangeTask,onDeleteTask}){return(ul{tasks.map(task(li key{task.id}Task task{task}onChange{onChangeTask}onDelete{onDeleteTask}//li))}/ul);}functionTask({task,onChange,onDelete}){const[isEditing,setIsEditing]useState(false);lettaskContent;if(isEditing){taskContent(input value{task.text}onChange{e{onChange({...task,text:e.target.value})}}/button onClick{()setIsEditing(false)}Save/button/);}else{taskContent({task.text}button onClick{()setIsEditing(true)}Edit/button/);}return(labelinput typecheckboxchecked{task.done}onChange{e{onChange({...task,done:e.target.checked});}}/{taskContent}button onClick{(){onDelete(task.id)}}Delete/button/label);}exportdefaultfunctionTaskApp(){const[tasks,setTasks]useState(initialTasks);functionhandleAddTask(text){setTasks([...tasks,{id:nextId,text:text,done:false,},]);}functionhandleChangeTask(task){setTasks(tasks.map((t){if(t.idtask.id){returntask;}else{returnt;}}));}functionhandleDeleteTask(taskId){setTasks(tasks.filter((t)t.id!taskId));}return(h1布拉格的行程安排/h1AddTask onAddTask{handleAddTask}/TaskList tasks{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}//);}letnextId3;constinitialTasks[{id:0,text:参观卡夫卡博物馆,done:true},{id:1,text:看木偶戏,done:false},{id:2,text:打卡列侬墙,done:false},];上面的代码中有三个不同的事件处理程序来实现状态数据的添加、删除和修改。这个组件的每个事件处理程序都通过setTasks来更新状态。随着这个组件的不断迭代其状态逻辑可能也会越来越多随着代码也会越变越复杂。为了降低这种复杂度并让所有逻辑都可以存放在一个易于理解的地方你可以将这些状态逻辑移到组件之外的一个称为reducer的函数中。reducer状态管理的另一种方式reducer是React中处理状态的另一种方式使用一个useReducer的Hook来实现主要用于管理复杂的状态变化逻辑。功能上与state类似但是代码形式上有些不同。两种方式各有优劣简单状态下使用useState管理组件状态即可对于复杂状态下可以尝试将useState转换为useReducer。至于State的优劣和取舍在咱们进行一次useSate到useReducer的转换后就能总结出来。转换过程可以分为三步:将设置状态的逻辑修改dispatch的一个action编写一个reducer函数在组件中使用reducer。Step1: 将设置状态的逻辑修改成dispatch的一个action这一步咱们先移除所有的状态设置逻辑只留下三个事件处理函数:handleAddTask(text): 在用户点击“添加”时被调用。handleChangeTask(task): 在用户切换任务或点击“保存”时被调用。handleDeleteTask(taskId): 在用户点击“删除”时被调用使用reducer管理状态与直接设置状态略有不同。它不是通过设置状态来告诉React“要做什么”而是通过事件处理程序dispatch一个“action” 来指明 “用户刚刚做了什么”。而状态更新逻辑则保存在其他地方因此我们不再通过事件处理器直接 “设置task”而是dispatch一个 “添加/修改/删除任务” 的action。这更加符合用户的思维。functionhandleAddTask(text){dispatch({type:added,id:nextId,text:text,});}functionhandleChangeTask(task){dispatch({type:changed,task:task,});}functionhandleDeleteTask(taskId){dispatch({type:deleted,id:taskId,});}传递给dispatch函数的就叫action这是一个普通的JavaScript对象。它的结构是由咱们自主定义的里面可以存储一些有用的参数通常情况下它应该至少包含可以表明“发生了什么事情”的信息(上述代码中的type字段)这个信息将指导dispatch的函数去处理状态变化。Step2: 编写一个reducer函数reducer函数就是你放置状态逻辑的地方。它接受两个参数分别为当前state和action对象并且返回的是更新后的state。functiontasksReducer(tasks,action){switch(action.type){caseadded:{return[...tasks,{id:action.id,text:action.text,done:false,},];}casechanged:{returntasks.map((t){if(t.idaction.task.id){returnaction.task;}else{returnt;}});}casedeleted:{returntasks.filter((t)t.id!action.id);}default:{throwError(未知 action: action.type);}}}由于reducer函数接受state作为参数因此你可以 在组件之外声明它。Step3: 在组件中使用reducer最后咱们需要将上一步实现的tasksReducer导入到组件中。// 1. 首先从React中导入useReducer// import { useState } from react;import{useReducer}fromreact;// 2. 组件中使用useReducer替换掉useSate// const [tasks, setTasks] useState(initialTasks);const[tasks,dispatch]useReducer(tasksReducer,initialTasks);与useState类似咱们必须给useReducer传递一个初始状态它会返回一个状态值(tasks)和一个设置状态的函数(dispatch)。但是useReducer还需要传入一个用于处理各种action的函数(tasksReducer)。最后得到的代码是这样的:import{useReducer}fromreact;importAddTaskfrom./AddTask.js;importTaskListfrom./TaskList.js;exportdefaultfunctionTaskApp(){const[tasks,dispatch]useReducer(tasksReducer,initialTasks);functionhandleAddTask(text){dispatch({type:added,id:nextId,text:text,});}functionhandleChangeTask(task){dispatch({type:changed,task:task,});}functionhandleDeleteTask(taskId){dispatch({type:deleted,id:taskId,});}return(h1布拉格的行程安排/h1AddTask onAddTask{handleAddTask}/TaskList tasks{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}//);}functiontasksReducer(tasks,action){switch(action.type){caseadded:{return[...tasks,{id:action.id,text:action.text,done:false,},];}casechanged:{returntasks.map((t){if(t.idaction.task.id){returnaction.task;}else{returnt;}});}casedeleted:{returntasks.filter((t)t.id!action.id);}default:{throwError(未知 action: action.type);}}}letnextId3;constinitialTasks[{id:0,text:参观卡夫卡博物馆,done:true},{id:1,text:看木偶戏,done:false},{id:2,text:打卡列侬墙,done:false}];现在事件处理函数只通过派发action来指定发生了什么而reducer函数通过响应actions来决定状态如何更新。这样一来状态修改的全部内容就集中到了tasksReducer函数中了我们也能一眼看出状态经历了哪些改变。对比useState和useReducerReducer并非没有缺点以下是比较它们的几个方面:代码体积: 通常在使用useState时一开始只需要编写少量代码。而useReducer必须提前编写reducer函数和需要调度的actioins。但是当多个事件处理程序以相似的方式修改state时useReducer可以减少代码量。可读性: 当状态更新逻辑足够简单时useState的可读性还行。但是一旦逻辑变得复杂起来它们会使组件变得臃肿且难以阅读。在这种情况下useReducer允许你将状态更新逻辑与事件处理程序分离开来。可调试性: 当使用useState出现问题时, 你很难发现具体原因。而使用useReducer时你可以在reducer函数中通过打印日志的方式来观察每个状态的更新以及为什么要更新来自哪个action。 如果所有action都没问题你就知道问题出在了reducer本身的逻辑中。 然而与使用useState相比你必须单步执行更多的代码。可测试性:reducer是一个不依赖于组件的纯函数。这就意味着你可以单独对它进行测试。一般来说我们最好是在真实环境中测试组件但对于复杂的状态更新逻辑针对特定的初始状态和action断言reducer返回的特定状态会很有帮助。如果你在修改某些组件状态时经常出现问题或者想给组件添加更多逻辑时建议你还是使用reducer。当然也不必整个项目都用reducer这是可以自由搭配的。编写一个好的reducer编写reducer时最好牢记以下两点:reducer必须是纯粹的。 这一点和状态更新函数是相似的reducer是在渲染时运行的(actions会排队直到下一次渲染)。这就意味着reducer必须纯净即当输入相同时输出也是相同的。它们不应该包含异步请求、定时器或者任何副作用(对组件外部有影响的操作)。它们应该以不可变值的方式去更新对象和数组。每个action都描述了一个单一的用户交互即使它会引发数据的多个变化。举个例子如果用户在一个由reducer管理的表单(包含五个表单项)中点击了重置按钮那么dispatch一个reset_form的action比dispatch五个单独的set_field的action更加合理。如果你在一个reducer中打印了所有的action日志那么这个日志应该是很清晰的它能让你以某种步骤复现已发生的交互或响应。这对代码调试很有帮助使用Context深层传递参数通常来说咱们会通过props将信息从父组件传递到子组件。但是如果必须通过许多中间组件向下传递props或是在应用中的许多组件需要相同的信息传递props会变得十分冗长和不便。Context允许父组件向其下层无论多深的任何组件提供信息而无需通过props显式传递。Context翻译过来时上下文它可以为父组件下面的整个组件树提供数据咱们可以将其看成父组件下的一个全局变量。这是一个来自官方手册的例子要实现的是Heading组件接收一个level参数来决定它标题尺寸functionHeading({level,children}){switch(level){case1:returnh1{children}/h1;case2:returnh2{children}/h2;case3:returnh3{children}/h3;case4:returnh4{children}/h4;case5:returnh5{children}/h5;case6:returnh6{children}/h6;default:throwError(未知的 levellevel);}}functionSection({children}){return(section classNamesection{children}/section);}exportdefaultfunctionPage(){return(SectionHeading level{1}主标题/HeadingHeading level{2}副标题/HeadingHeading level{3}子标题/HeadingHeading level{4}子子标题/HeadingHeading level{5}子子子标题/HeadingHeading level{6}子子子子标题/Heading/Section);}假设先要让相同Section中的多个Heading具有相同的尺寸对于这样一个复杂的结构Page组件可能就会写成这样:exportdefaultfunctionPage(){return(SectionHeading level{1}主标题/HeadingSectionHeading level{2}副标题/HeadingHeading level{2}副标题/HeadingHeading level{2}副标题/HeadingSectionHeading level{3}子标题/HeadingHeading level{3}子标题/HeadingHeading level{3}子标题/HeadingSectionHeading level{4}子子标题/HeadingHeading level{4}子子标题/HeadingHeading level{4}子子标题/Heading/Section/Section/Section/Section);}目前你将level参数分别传递给每个Heading将level参数传递给Section组件而不是传给Heading组件看起来更好一些。这样的话你可以强制使同一个section中的所有标题都有相同的尺寸Section level{3}Heading关于/HeadingHeading照片/HeadingHeading视频/Heading/Section但是Heading组件是如何知道离它最近的Section的level的呢这需要子组件可以通过某种方式“访问”到组件树中某处在其上层的数据。这个功能不能只通过props来实现它。这就是context大显身手的地方。你可以通过以下三个步骤来实现它创建一个context。你可以将其命名为LevelContext, 因为它表示的是标题级别。在需要数据的组件内使用刚刚创建的context。Heading将会使用LevelContext。在指定数据的组件中提供这个context。Section将会提供LevelContext。Context可以让父节点甚至是很远的父节点都可以为其内部的整个组件树提供数据。Step 1: 创建contextContext在调用前需要在组件外创建甚至可以在单独的文件中创建它如果你需要在单独文件中创建并将其从一个文件中导出可以采用如下的代码:import{createContext}fromreact;exportconstLevelContextcreateContext(1);createContext只需默认值这么一个参数。在这里,1表示最大的标题级别但是也可以传递任何类型的值(甚至可以传入一个对象)。Step 2: 使用Context从React中引入useContext Hook以及你刚刚创建的context:import{useContext}fromreact;import{LevelContext}from./LevelContext.js;删掉level参数并从你刚刚引入的LevelContext中读取值functionHeading({children}){constleveluseContext(LevelContext);switch(level){case1:returnh1{children}/h1;case2:returnh2{children}/h2;case3:returnh3{children}/h3;case4:returnh4{children}/h4;case5:returnh5{children}/h5;case6:returnh6{children}/h6;default:throwError(未知的 levellevel);}}useContext是一个Hook。和useState以及useReducer一样你只能在React组件中(不是循环或者条件里)立即调用Hook。useContext告诉React Heading组件想要读取LevelContext。现在Heading组件没有level参数你不需要再像这样在你的JSX中将level参数传递给Heading修改一下JSX让Section组件代替Heading组件接收level参数:import{createContext}fromreact;import{useContext}fromreact;constLevelContextcreateContext(1);functionHeading({children}){constleveluseContext(LevelContext);switch(level){case1:returnh1{children}/h1;case2:returnh2{children}/h2;case3:returnh3{children}/h3;case4:returnh4{children}/h4;case5:returnh5{children}/h5;case6:returnh6{children}/h6;default:throwError(未知的 levellevel);}}functionSection({children}){return(section classNamesection{children}/section);}exportdefaultfunctionPage(){return(Section level{1}Heading主标题/HeadingSection level{2}Heading副标题/HeadingHeading副标题/HeadingHeading副标题/HeadingSection level{3}Heading子标题/HeadingHeading子标题/HeadingHeading子标题/HeadingSection level{4}Heading子子标题/HeadingHeading子子标题/HeadingHeading子子标题/Heading/Section/Section/Section/Section);}注意这个示例运行起来还达不到咱们的预期。所有Headings的尺寸都一样因为 即使你正在使用context但是你还没有提供它。如果不提供contextReact会使用在上一步指定的默认值。在这个例子中为createContext传入了1这个参数所以useContext(LevelContext)会返回1把所有的标题都设置为h1。我们通过让每个Section提供它自己的context来修复这个问题。Step 3:提供 contextsection组件目前渲染传入它的子组件把它们用context provider包裹起来 以提供LevelContext给它们functionSection({level,children}){return(section classNamesectionLevelContext value{level}{children}/LevelContext/section);}这告诉React“如果在Section组件中的任何子组件请求LevelContext给他们这个level。”组件会使用UI树中在它上层最近的那个LevelContext传递过来的值。这与原始代码的运行结果相同但是你不需要向每个Heading组件传递level参数了取而代之的是它通过访问上层最近的Section来“断定”它的标题级别你将一个level参数传递给Section。Section把它的子元素包在LevelContext value{level}里面。Heading使用useContext(LevelContext)访问上层最近的LevelContext提供的值。在相同的组件中使用并提供context:目前你仍需要手动指定每个section的level。由于context让你可以从上层的组件读取信息每个Section都会从上层的Section读取level并自动向下层传递level 1。你可以像下面这样做:import{useContext}fromreact;functionSection({children}){constleveluseContext(LevelContext);return(section classNamesectionLevelContext value{level1}{children}/LevelContext/section);}这样修改之后你不用将level参数传给Section或者是Heading了import{createContext}fromreact;import{useContext}fromreact;constLevelContextcreateContext(1);functionHeading({children}){constleveluseContext(LevelContext);switch(level){case1:returnh1{children}/h1;case2:returnh2{children}/h2;case3:returnh3{children}/h3;case4:returnh4{children}/h4;case5:returnh5{children}/h5;case6:returnh6{children}/h6;default:throwError(未知的 levellevel);}}functionSection({children}){constleveluseContext(LevelContext);return(section classNamesectionLevelContext value{level1}{children}/LevelContext/section);}exportdefaultfunctionPage(){return(SectionHeading主标题/HeadingSectionHeading副标题/HeadingHeading副标题/HeadingHeading副标题/HeadingSectionHeading子标题/HeadingHeading子标题/HeadingHeading子标题/HeadingSectionHeading子子标题/HeadingHeading子子标题/HeadingHeading子子标题/Heading/Section/Section/Section/Section);}现在Heading和Section都通过读取LevelContext来判断它们的深度。而且Section把它的子组件都包在LevelContext中来指定其中的任何内容都处于一个“更深”的级别。Context 会穿过中间层级的组件可以在提供context的组件和使用它的组件之间的层级插入任意数量的组件。这包括像div这样的内置组件和自己创建的组件。Context让你可以编写“适应周围环境”的组件并且根据在哪或者说在哪个context中来渲染它们不同的样子。Context的工作方式可能会让人想起CSS属性继承。在CSS中你可以为一个div手动指定color: blue并且其中的任何DOM节点无论多深都会继承那个颜色除非中间的其他DOM节点用color: green来覆盖它。类似地在React中覆盖来自上层的某些context的唯一方法是将子组件包裹到一个提供不同值的context provider中。在CSS中诸如color和background-color之类的不同属性不会覆盖彼此。你可以设置所有div的color为红色而不会影响background-color。类似地不同的React context不会覆盖彼此。你通过createContext()创建的每个context都和其他context完全分离只有使用和提供 那个特定的context的组件才会联系在一起。一个组件可以轻松地使用或者提供许多不同的context。但是context对于许多其他的场景也很有用。你可以用它来传递整个子树需要的任何信息当前的颜色主题、当前登录的用户等。使用Context之前使用Context看起来非常方便诱人这也意味着它也太容易被过度使用了。如果咱们只想把一些props传递到多个层级中这并不意味着你需要把这些信息放到context里。在使用context之前可以考虑以下几种替代方案从传递 props开始。 如果组件看起来不起眼那么通过十几个组件向下传递一堆props并不罕见。这有点像是在埋头苦干但是这样做可以让哪些组件用了哪些数据变得十分清晰维护你代码的人会很高兴你用props让数据流变得更加清晰。抽象组件并将 JSX 作为 children 传递给它们。 如果通过很多层不使用该数据的中间组件并且只会向下传递来传递数据这通常意味着在此过程中忘记了抽象组件。举个例子可能在想传递一些像posts的数据props到不会直接使用这个参数的组件类似Layout posts{posts} /。取而代之的是让Layout把children当做一个参数然后渲染LayoutPosts posts{posts} //Layout。这样就减少了定义数据的组件和使用数据的组件之间的层级。如果这两种方法都不适合你再考虑使用context。Context的使用场景主题如果你的应用允许用户更改其外观例如暗夜模式可以在应用顶层放一个context provider并在需要调整其外观的组件中使用该context。当前账户许多组件可能需要知道当前登录的用户信息。将它放到context中可以方便地在树中的任何位置读取它。某些应用还允许你同时操作多个账户例如以不同用户的身份发表评论。在这些情况下将UI的一部分包裹到具有不同账户数据的provider中会很方便。路由大多数路由解决方案在其内部使用context来保存当前路由。这就是每个链接“知道”它是否处于活动状态的方式。如果你创建自己的路由库你可能也会这么做。状态管理 随着你的应用的增长最终在靠近应用顶部的位置可能会有很多state。许多遥远的下层组件可能想要修改它们。通常 将reducer与context搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦。Context不局限于静态值。如果你在下一次渲染时传递不同的值React将会更新读取它的所有下层组件这就是context经常和state结合使用的原因。使用Reducer和Context扩展你的应用Reducer可以整合组件的状态更新逻辑。Context可以将信息深入传递给其他组件。你可以组合使用它们来共同管理一个复杂页面的状态。结合使用reducer和context在reducer介绍的例子里面状态被reducer所管理。reducer函数包含了所有的状态更新逻辑并在此文件的底部声明Reducer有助于保持事件处理程序的简短明了。但随着应用规模越来越庞大你就可能会遇到别的困难。目前tasks状态和dispatch函数仅在顶级TaskApp组件中可用。要让其他组件读取任务列表或更改它你必须显式传递当前状态和事件处理程序将其作为props。例如TaskApp将一系列task和事件处理程序传递给TaskListTaskList task{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}/TaskList将事件处理程序传递给TaskTask task{task}onChange{onChangeTask}onDelete{onDeleteTask}/在像这样的小示例里这样做没什么问题但是如果你有成百上千个中间组件传递所有状态和函数可能会非常麻烦刚刚学过Context之后咱们有了解决方案可以直接把tasks状态和dispatch函数都 放入context。这样所有的在TaskApp组件树之下的组件都不必一直往下传props而可以直接读取tasks和dispatch函数。下面将介绍如何结合使用reducer和context创建context将state和dispatch放入context。在组件树的任何地方使用context。Step 1: 创建contextuseReducer返回当前的tasks和dispatch函数来让你更新它们:const[tasks,dispatch]useReducer(tasksReducer,initialTasks);为了将它们从组件树往下传你将创建两个不同的context:TasksContext提供当前的tasks列表。TasksDispatchContext提供了一个函数可以让组件分发动作。import{createContext}fromreact;constTasksContextcreateContext(null);constTasksDispatchContextcreateContext(null);在这里先把null作为默认值传递给两个context。实际值是由TaskApp组件提供的。Step 2: 将state和dispatch函数放入context现在你可以将所有的context导入TaskApp组件。获取useReducer()返回的tasks和dispatch并将它们提供给整个组件树import{createContext}fromreact;constTasksContextcreateContext(null);constTasksDispatchContextcreateContext(null);exportdefaultfunctionTaskApp(){const[tasks,dispatch]useReducer(tasksReducer,initialTasks);// ...return(TasksContext value{tasks}TasksDispatchContext value{dispatch}// .../TasksDispatchContext/TasksContext);}现在你可以同时通过props和context传递信息Step 3: 在组件树中的任何地方使用context现在你不需要将tasks和事件处理程序在组件树中传递相反任何需要tasks的组件都可以从TasksContext中读取它任何组件都可以从context中读取dispatch函数并调用它从而更新任务列表。TaskApp组件不会向下传递任何事件处理程序TaskList也不会。每个组件都会读取它需要的context:import{createContext}fromreact;import{useState,useContext}fromreact;import{useReducer}fromreact;constTasksContextcreateContext(null);constTasksDispatchContextcreateContext(null);functionTaskList(){consttasksuseContext(TasksContext);return(ul{tasks.map(task(li key{task.id}Task task{task}//li))}/ul);}functionTask({task}){const[isEditing,setIsEditing]useState(false);constdispatchuseContext(TasksDispatchContext);lettaskContent;if(isEditing){taskContent(input value{task.text}onChange{e{dispatch({type:changed,task:{...task,text:e.target.value}});}}/button onClick{()setIsEditing(false)}Save/button/);}else{taskContent({task.text}button onClick{()setIsEditing(true)}Edit/button/);}return(labelinput typecheckboxchecked{task.done}onChange{e{dispatch({type:changed,task:{...task,done:e.target.checked}});}}/{taskContent}button onClick{(){dispatch({type:deleted,id:task.id});}}Delete/button/label);}functionAddTask(){const[text,setText]useState();constdispatchuseContext(TasksDispatchContext);return(input placeholderAdd taskvalue{text}onChange{esetText(e.target.value)}/button onClick{(){setText();dispatch({type:added,id:nextId,text:text,});}}Add/button/);}letnextId3;exportdefaultfunctionTaskApp(){const[tasks,dispatch]useReducer(tasksReducer,initialTasks);return(TasksContext value{tasks}TasksDispatchContext value{dispatch}h1Day offinKyoto/h1AddTask/TaskList//TasksDispatchContext/TasksContext);}functiontasksReducer(tasks,action){switch(action.type){caseadded:{return[...tasks,{id:action.id,text:action.text,done:false}];}casechanged:{returntasks.map(t{if(t.idaction.task.id){returnaction.task;}else{returnt;}});}casedeleted:{returntasks.filter(tt.id!action.id);}default:{throwError(Unknown action: action.type);}}}constinitialTasks[{id:0,text:Philosopher’s Path,done:true},{id:1,text:Visit the temple,done:false},{id:2,text:Drink matcha,done:false}];state仍然 “存在于” 顶层Task组件中由useReducer进行管理。不过组件树里的组件只要导入这些context之后就可以获取tasks和dispatch。将相关逻辑迁移到一个文档当中这不是必须的但你可以通过将reducer和context移动到单个文件中来进一步整理组件。新建一个“TasksContext.js” 文件包含两个context声明来给这个文件添加更多代码将reducer移动到此文件中然后声明一个新的TaskProvider组件。此组件将所有部分连接在一起:它将管理reducer的状态它将提供现有的context给组件树它将把children作为prop所以你可以传递JSX。import{createContext,useReducer}fromreact;exportconstTasksContextcreateContext(null);exportconstTasksDispatchContextcreateContext(null);exportfunctionTasksProvider({children}){const[tasks,dispatch]useReducer(tasksReducer,initialTasks);return(TasksContext value{tasks}TasksDispatchContext value{dispatch}{children}/TasksDispatchContext/TasksContext);}functiontasksReducer(tasks,action){switch(action.type){caseadded:{return[...tasks,{id:action.id,text:action.text,done:false}];}casechanged:{returntasks.map(t{if(t.idaction.task.id){returnaction.task;}else{returnt;}});}casedeleted:{returntasks.filter(tt.id!action.id);}default:{throwError(Unknown action: action.type);}}}constinitialTasks[{id:0,text:Philosopher’s Path,done:true},{id:1,text:Visit the temple,done:false},{id:2,text:Drink matcha,done:false}];你也可以从TasksContext.js中导出使用context的函数exportfunctionuseTasks(){returnuseContext(TasksContext);}exportfunctionuseTasksDispatch(){returnuseContext(TasksDispatchContext);}组件可以通过以下函数读取contextconsttasksuseTasks();constdispatchuseTasksDispatch();这不会改变任何行为但它会允许你之后进一步分割这些context或向这些函数添加一些逻辑。现在所有的context和reducer连接部分都在TasksContext.js中。这保持了组件的干净和整洁让我们专注于它们显示的内容而不是它们从哪里获得数据你可以将TasksProvider视为页面的一部分它知道如何处理tasks。useTasks用来读取它们useTasksDispatch用来从组件树下的任何组件更新它们。像useTasks和useTasksDispatch这样的函数被称为自定义 Hook。 如果你的函数名以use开头它就被认为是一个自定义Hook。这让你可以使用其他Hook比如useContext。随着应用的增长你可能会有许多这样的context和reducer的组合。这是一种强大的拓展应用并提升状态的方式让你在组件树深处访问数据时无需进行太多工作。