学习React(状态管理)

        随着你的应用不断变大,更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动会对你很有帮助。冗余或重复的状态往往是缺陷的根源。在本节中,你将学习如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态。

本章节

  • 如何将 UI 变更视为状态变更
  • 如何组织好状态
  • 如何使用“状态提升”在组件之间共享状态
  • 如何控制状态的保留或重置
  • 如何在函数中整合复杂的状态逻辑
  • 如何避免数据通过 prop 逐级透传
  • 如何随着应用的增长去扩展状态管理

 

用 State 响应输入

        使用 React,你不用直接从代码层面修改 UI。例如,不用编写诸如“禁用按钮”、“启用按钮”、“显示成功消息”等命令。相反,你只需要描述组件在不同状态(“初始状态”、“输入状态”、“成功状态”)下希望展现的 UI,然后根据用户输入触发状态更改。这和设计师对 UI 的理解很相似。

下面是一个使用 React 编写的反馈表单。请注意看它是如何使用 status 这个状态变量来决定启用或禁用提交按钮,以及是否显示成功消息的。、

import { useState } from 'react';export default function Form() {const [answer, setAnswer] = useState('');const [error, setError] = useState(null);const [status, setStatus] = useState('typing');if (status === 'success') {return <h1>答对了!</h1>}async function handleSubmit(e) {e.preventDefault();setStatus('submitting');try {await submitForm(answer);setStatus('success');} catch (err) {setStatus('typing');setError(err);}}function handleTextareaChange(e) {setAnswer(e.target.value);}return (<><h2>城市测验</h2><p>哪个城市有把空气变成饮用水的广告牌?</p><form onSubmit={handleSubmit}><textareavalue={answer}onChange={handleTextareaChange}disabled={status === 'submitting'}/><br /><button disabled={answer.length === 0 ||status === 'submitting'}>提交</button>{error !== null &&<p className="Error">{error.message}</p>}</form></>);
}function submitForm(answer) {// 模拟接口请求return new Promise((resolve, reject) => {setTimeout(() => {let shouldError = answer.toLowerCase() !== '北京'if (shouldError) {reject(new Error('猜的不错,但答案不对。再试试看吧!'));} else {resolve();}}, 1500);});
}

选择 State 结构

        良好的状态组织,可以区分开易于修改和调试的组件与频繁出问题的组件。最重要的原则是,状态不应包含冗余或重复的信息。如果包含一些多余的状态,我们会很容易忘记去更新它,从而导致问题产生!

例如,这个表单有一个多余的 fullName 状态变量:

import { useState } from 'react';export default function Form() {const [firstName, setFirstName] = useState('');const [lastName, setLastName] = useState('');const [fullName, setFullName] = useState('');function handleFirstNameChange(e) {setFirstName(e.target.value);setFullName(e.target.value + ' ' + lastName);}function handleLastNameChange(e) {setLastName(e.target.value);setFullName(firstName + ' ' + e.target.value);}return (<><h2>让我们帮你登记</h2><label>名:{' '}<inputvalue={firstName}onChange={handleFirstNameChange}/></label><label>姓:{' '}<inputvalue={lastName}onChange={handleLastNameChange}/></label><p>你的票据将签发给:<b>{fullName}</b></p></>);
}

你可以移除它并在组件渲染时通过计算 fullName 来简化代码: 

import { useState } from 'react';export default function Form() {const [firstName, setFirstName] = useState('');const [lastName, setLastName] = useState('');const fullName = firstName + ' ' + lastName;function handleFirstNameChange(e) {setFirstName(e.target.value);}function handleLastNameChange(e) {setLastName(e.target.value);}return (<><h2>让我们帮你登记</h2><label>名:{' '}<inputvalue={firstName}onChange={handleFirstNameChange}/></label><label>姓:{' '}<inputvalue={lastName}onChange={handleLastNameChange}/></label><p>你的票将发给:<b>{fullName}</b></p></>);
}

 这看起来似乎只是一个小改动,但却可以避免很多潜在的问题。

在组件间共享状态

        有时候你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把这些状态移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。

在以下示例中,要求每次只能激活一个面板。要实现这一点,父组件将管理激活状态并为其子组件指定 prop,而不是将激活状态保留在各自的子组件中。

 

import { useState } from 'react';export default function Accordion() {const [activeIndex, setActiveIndex] = useState(0);return (<><h2>Almaty, Kazakhstan</h2><Paneltitle="关于"isActive={activeIndex === 0}onShow={() => setActiveIndex(0)}>阿拉木图人口约200万,是哈萨克斯坦最大的城市。在1929年至1997年之间,它是该国首都。</Panel><Paneltitle="词源"isActive={activeIndex === 1}onShow={() => setActiveIndex(1)}>这个名字源于哈萨克语 <span lang="kk-KZ">алма</span>,是“苹果”的意思,通常被翻译成“满是苹果”。事实上,阿拉木图周围的地区被认为是苹果的祖籍,<i lang="la">Malus sieversii</i> 被认为是目前本土苹果的祖先。</Panel></>);
}function Panel({title,children,isActive,onShow
}) {return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={onShow}>显示</button>)}</section>);
}

 

对 state 进行保留和重置

        当你重新渲染一个组件时, React 需要决定组件树中的哪些部分要保留和更新,以及丢弃或重新创建。在大多数情况下, React 的自动处理机制已经做得足够好了。默认情况下,React 会保留树中与先前渲染的组件树“匹配”的部分。

然而,有时这并不是你想要的。例如,在下面这个程序中,输入内容后再切换收件人并不会清空输入框。这可能会导致用户不小心发错消息:

App.js

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';export default function Messenger() {const [to, setTo] = useState(contacts[0]);return (<div><ContactListcontacts={contacts}selectedContact={to}onSelect={contact => setTo(contact)}/><Chat contact={to} /></div>)
}const contacts = [{ name: 'Taylor', email: 'taylor@mail.com' },{ name: 'Alice', email: 'alice@mail.com' },{ name: 'Bob', email: 'bob@mail.com' }
];

ContactList.js 

export default function ContactList({selectedContact,contacts,onSelect
}) {return (<section className="contact-list"><ul>{contacts.map(contact =><li key={contact.email}><button onClick={() => {onSelect(contact);}}>{contact.name}</button></li>)}</ul></section>);
}

 Chat.js

import { useState } from 'react';export default function Chat({ contact }) {const [text, setText] = useState('');return (<section className="chat"><textareavalue={text}placeholder={'Chat to ' + contact.name}onChange={e => setText(e.target.value)}/><br /><button>发送给 {contact.email}</button></section>);
}

        React 允许你覆盖默认行为,可通过向组件传递一个唯一 key(如 <Chat key={email}/>强制 重置其状态。这会告诉 React ,如果收件人不同,应将其作为一个 不同的 Chat 组件,需要使用新数据和 UI(比如输入框)来重新创建它。现在,在接收者之间切换时就会重置输入框——即使渲染的是同一个组件。 

App.js

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';export default function Messenger() {const [to, setTo] = useState(contacts[0]);return (<div><ContactListcontacts={contacts}selectedContact={to}onSelect={contact => setTo(contact)}/><Chat key={to.email} contact={to} /></div>)
}const contacts = [{ name: 'Taylor', email: 'taylor@mail.com' },{ name: 'Alice', email: 'alice@mail.com' },{ name: 'Bob', email: 'bob@mail.com' }
];

ContactList.js 

export default function ContactList({selectedContact,contacts,onSelect
}) {return (<section className="contact-list"><ul>{contacts.map(contact =><li key={contact.email}><button onClick={() => {onSelect(contact);}}>{contact.name}</button></li>)}</ul></section>);
}

Chat.js 

export default function ContactList({selectedContact,contacts,onSelect
}) {return (<section className="contact-list"><ul>{contacts.map(contact =><li key={contact.email}><button onClick={() => {onSelect(contact);}}>{contact.name}</button></li>)}</ul></section>);
}

迁移状态逻辑至 Reducer 中

        对于那些需要更新多个状态的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。这样,事件处理程序就会变得简洁,因为它们只需要指定用户的 “actions”。在文件的底部,reducer 函数指定状态应该如何更新以响应每个 action!

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';export default function TaskApp() {const [tasks, dispatch] = useReducer(tasksReducer,initialTasks);function handleAddTask(text) {dispatch({type: 'added',id: nextId++,text: text,});}function handleChangeTask(task) {dispatch({type: 'changed',task: task});}function handleDeleteTask(taskId) {dispatch({type: 'deleted',id: taskId});}return (<><h1>布拉格行程</h1><AddTaskonAddTask={handleAddTask}/><TaskListtasks={tasks}onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}/></>);
}function tasksReducer(tasks, action) {switch (action.type) {case 'added': {return [...tasks, {id: action.id,text: action.text,done: false}];}case 'changed': {return tasks.map(t => {if (t.id === action.task.id) {return action.task;} else {return t;}});}case 'deleted': {return tasks.filter(t => t.id !== action.id);}default: {throw Error('未知操作:' + action.type);}}
}let nextId = 3;
const initialTasks = [{ id: 0, text: '参观卡夫卡博物馆', done: true },{ id: 1, text: '看木偶戏', done: false },{ id: 2, text: '列侬墙图片', done: false }
];

 

使用 Context 深层传递参数

        通常,你会通过 props 将信息从父组件传递给子组件。但是,如果要在组件树中深入传递一些 prop,或者树里的许多组件需要使用相同的 prop,那么传递 prop 可能会变得很麻烦。Context 允许父组件将一些信息提供给它下层的任何组件,不管该组件多深层也无需通过 props 逐层透传。

        这里的 Heading 组件通过“询问”最近的 Section 来确定其标题级别。每个 Section 的级别是通过给父 Section 添加的级别来确定的。每个 Section 都向它下层的所有组件提供信息,不需要逐层传递 props,而是通过 Context 来实现。

App.js

import Heading from './Heading.js';
import Section from './Section.js';export default function Page() {return (<Section><Heading>大标题</Heading><Section><Heading>一级标题</Heading><Heading>一级标题</Heading><Heading>一级标题</Heading><Section><Heading>二级标题</Heading><Heading>二级标题</Heading><Heading>二级标题</Heading><Section><Heading>三级标题</Heading><Heading>三级标题</Heading><Heading>三级标题</Heading></Section></Section></Section></Section>);
}

Section. js

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Section({ children }) {const level = useContext(LevelContext);return (<section className="section"><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>);
}

Heading.js

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Heading({ children }) {const level = useContext(LevelContext);switch (level) {case 0:throw Error('标题必须在 Section 内!');case 1:return <h1>{children}</h1>;case 2:return <h2>{children}</h2>;case 3:return <h3>{children}</h3>;case 4:return <h4>{children}</h4>;case 5:return <h5>{children}</h5>;case 6:return <h6>{children}</h6>;default:throw Error('未知级别:' + level);}
}

LevelConText.js

import { createContext } from 'react';export const LevelContext = createContext(0);

使用 Reducer 和 Context 拓展你的应用

        Reducer 帮助你合并组件的状态更新逻辑。Context 帮助你将信息深入传递给其他组件。你可以将 reducers 和 context 组合在一起使用,以管理复杂应用的状态。

        基于这种想法,使用 reducer 来管理一个具有复杂状态的父组件。组件树中任何深度的其他组件都可以通过 context 读取其状态。还可以 dispatch 一些 action 来更新状态。

App.js

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';export default function TaskApp() {return (<TasksProvider><h1>在京都休息一天</h1><AddTask /><TaskList /></TasksProvider>);
}

AddTask.js

import { useState, useContext } from 'react';
import { useTasksDispatch } from './TasksContext.js';export default function AddTask({ onAddTask }) {const [text, setText] = useState('');const dispatch = useTasksDispatch();return (<><inputplaceholder="添加任务"value={text}onChange={e => setText(e.target.value)}/><button onClick={() => {setText('');dispatch({type: 'added',id: nextId++,text: text,});}}>添加</button></>);
}let nextId = 3;

TaskList.js

import { useState, useContext } from 'react';
import { useTasks, useTasksDispatch } from './TasksContext.js';export default function TaskList() {const tasks = useTasks();return (<ul>{tasks.map(task => (<li key={task.id}><Task task={task} /></li>))}</ul>);
}function Task({ task }) {const [isEditing, setIsEditing] = useState(false);const dispatch = useTasksDispatch();let taskContent;if (isEditing) {taskContent = (<><inputvalue={task.text}onChange={e => {dispatch({type: 'changed',task: {...task,text: e.target.value}});}} /><button onClick={() => setIsEditing(false)}>保存</button></>);} else {taskContent = (<>{task.text}<button onClick={() => setIsEditing(true)}>编辑</button></>);}return (<label><inputtype="checkbox"checked={task.done}onChange={e => {dispatch({type: 'changed',task: {...task,done: e.target.checked}});}}/>{taskContent}<button onClick={() => {dispatch({type: 'deleted',id: task.id});}}>删除</button></label>);
}

TasksContext.js

import { createContext, useContext, useReducer } from 'react';const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);export function TasksProvider({ children }) {const [tasks, dispatch] = useReducer(tasksReducer,initialTasks);return (<TasksContext.Provider value={tasks}><TasksDispatchContext.Providervalue={dispatch}>{children}</TasksDispatchContext.Provider></TasksContext.Provider>);
}export function useTasks() {return useContext(TasksContext);
}export function useTasksDispatch() {return useContext(TasksDispatchContext);
}function tasksReducer(tasks, action) {switch (action.type) {case 'added': {return [...tasks, {id: action.id,text: action.text,done: false}];}case 'changed': {return tasks.map(t => {if (t.id === action.task.id) {return action.task;} else {return t;}});}case 'deleted': {return tasks.filter(t => t.id !== action.id);}default: {throw Error('未知操作:' + action.type);}}
}const initialTasks = [{ id: 0, text: '哲学家之路', done: true },{ id: 1, text: '参观寺庙', done: false },{ id: 2, text: '喝抹茶', done: false }
];

 

 希望这篇文章对你有所帮助,并能在实际工作中为你提供参考。如果你有任何问题或建议,欢迎在评论区留言。请记得一键三连(点赞、收藏、分享)哦!

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

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

相关文章

基于自组织映射的检索增强生成

大量数据用于训练大型语言模型 (LLM)&#xff0c;该模型包含数百万和数十亿个模型参数&#xff0c;目的是生成文本&#xff0c;例如文本补全、文本摘要、语言翻译和回答问题。虽然 LLM 从训练数据源中开发知识库&#xff0c;但总有一个训练截止日期&#xff0c;在此日期之后 LL…

java jts 针对shp含洞多边形进行三角剖分切分成可行区域

前言 java jts 提供了Delaunay三角剖分的相关方法,但是该方法不考虑含洞的多边形的。虽然 jts 的 ConformingDelaunayTriangulationBuilder 类可以通过提供线段约束的方式防止切割到洞内&#xff0c;但是仅支持最多99条线段&#xff0c;虽然可以通过重写破除99条线段的约束&am…

Java——————接口(interface) <详解>

1.1 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本电脑上的USB接口&#xff0c;电源插座等。 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘...所有符合USB协议的设备 电源插座插孔上&#xff0c;可以插&#xff…

OS Copilot初体验的感受与心得

本文介绍体验操作系统智能助手OS Copilot后&#xff0c;个人的一些收获、体验等。 最近&#xff0c;抽空体验了阿里云的操作系统智能助手OS Copilot&#xff0c;在这里记录一下心得与收获。总体观之&#xff0c;从个人角度来说&#xff0c;感觉这个OS Copilot确实抓住了不少开发…

EasyMedia转码rtsp视频流flv格式,hls格式,H5页面播放flv流视频

EasyMedia转码rtsp视频流flv格式&#xff0c;hls格式 H5页面播放flv流视频 文章最后有源码地址 解决海康视频播放视频流&#xff0c;先转码后自定义页面播放flv视频流 先看效果&#xff0c;1&#xff0c;EasyMedia自带的页面&#xff0c;这个页面二次开发改动页面比较麻烦 …

张高兴的 MicroPython 入门指南:(三)使用串口通信

目录 什么是串口使用方法使用板载串口相互通信 硬件需求电路代码使用板载的 USB 串口参考 什么是串口 串口是串行接口的简称&#xff0c;这是一个非常大的概念&#xff0c;在嵌入式中串口通常指 UART(Universal Asynchronous Receiver/Transmitter&#xff0c;通用异步收发器)。…

初识c++(string和模拟实现string)

一、标准库中的string类 string类的文档介绍&#xff1a;cplusplus.com/reference/string/string/?kwstring 1、auto和范围for auto&#xff1a; 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量&#xff0c;后来这个 不重…

Cannot perform upm operation: connect ETIMEDOUT 34.36.199.114:443 [NotFound]

版本&#xff1a;Unity 2018 Windows 问题&#xff1a;打开 Package Manager&#xff0c;加载报错 尝试解决&#xff1a; 删除项目文件里的Packages下的mainfest.json文件&#xff0c;然后重新打开项目&#xff08;X&#xff09;重新登录 Unity 账号&#xff08;X&#xff09…

Kotlin 协程 — 基础

Kotlin 协程 — 基础 协程已经存在一段时间了&#xff0c;关于它的各种文章也很多。但我发现想要了解它还比较费时&#xff0c;所以我花了一段时间才真正理解了协程的基础知识以及它的工作原理。因此&#xff0c;我想分享一些我理解到的内容。 什么是协程&#xff1f; 协程代表…

vue项目实战速查记录

1.图片下载到本地 2.本地静态文件访问 3.元素大小相同,相互覆盖 1.图片下载到本地 实现原理:创建a标签,利用a标签下载属性. download(){const link document.createElement(a);link.href "图片地址";link.setAttribute(download, name);document.body.ap…

读论文《Hi-Net: Hybrid-fusion Network for Multi-modalMR Image Synthesis》

论文题目&#xff1a;Hi-Net:用于多模态磁共振图像合成的混合融合网络 论文地址&#xff1a;arxiv 项目地址&#xff1a;github 原项目可能在训练的时候汇报version的错&#xff0c;这是因为生成器和辨别器的优化有些逻辑错误&#xff0c;会改的话多加一个生成操作可以解决&…

React 学习——条件渲染、遍历循环、事件绑定

React特点&#xff1a; 声明式的设计高效&#xff0c;采用虚拟DOM来实现DOM的渲染&#xff0c;最大限度减少DOM的操作灵活&#xff0c;跟其他库灵活搭配使用JSX&#xff0c;俗称JS里面写HTML&#xff0c;JavaScript语法的扩展组件化&#xff0c;模块化&#xff0c;代码容易复用…

pdf的下载,后端返回工作流,前端进行转换

前端将后端返回的工作流进行转换 项目中接触到了pdf的下载和预览的功能&#xff0c;记录一下~ 这里pdf的下载和预览的接口&#xff0c;后端返回的数据结构与其他的接口返回的数据结构有点不同&#xff0c;是直接返回的工作流&#xff0c;在控制台接口的响应预览内容大致是这样…

初学MySQl简单sql语句(1)

目录 SQL语句介绍&#xff1a; DDL创建数据库&#xff1a; char和varchar比较 数值类型 数据库存储引擎 数据库存储引擎——InnoDB 数据库存储引擎——MyISAM 数据库存储引擎-MyISAM 和InnoDB区别 修改和删除数据库表 数据库设计三大范式 一、什么是范式 二、约束作…

css实战案例1:顶部搜索

代码样式&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><div class"search_box"><!-- 搜索框--><div class"search">搜索…

【Linux】网络基础_2

文章目录 十、网络基础2. IP地址和MAC地址3. 端口号端口号和进程ID 4. 网络字节序 未完待续 十、网络基础 2. IP地址和MAC地址 IP协议有两个版本&#xff0c;IPv4和IPv6&#xff0c; 用的比较多的都是IPv4。IP地址是在IP协议中&#xff0c;用来标识网络中不同主机的地址&…

如何发现快速发现分析生产问题SQL

Performance Schema介绍 Performance Schema提供了有关MySQL服务器内部运行的操作上的底层指标。为了解释清楚Performance Schema的工作机制&#xff0c;先介绍两个概念。 第一个概念是程序插桩&#xff08;instrument&#xff09;。程序插桩在MySQL代码中插入探测代码&#xf…

基本聚集函数和case的应用

文章目录 1.基本聚集函数(1)基本聚集函数的介绍(2)使用基本聚集函数的简单例子&#xff08;1&#xff09;查询最大年龄&#xff0c;最小年龄年龄和平均年龄<1>最大年龄<2>最小年龄<3>平均年龄 (2&#xff09;配合上where语句&#xff0c;查询女士的平均年龄(…

JAVA笔记十四

十四、集合 1.集合概述 (1)集合是存储其它对象的特殊对象&#xff0c;可以将集合当作一个容器 (2)集合的相关接口和类位于java.util包中 (3)集合中的接口和类是一个整体、一个体系 2.集合接口 接口定义了一组抽象方法&#xff0c;实现该接口的类需要实现这些抽象方法&…

Docker核心技术:Docker原理之Cgroups

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;Docker原理之Cgroups&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术&#xff1a;…