学习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.xdnf.cn/news/1486488.html

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

相关文章

SQL 简单查询

目录 一、投影查询 1、指定特定列查询 2、修改返回列名查询 3、计算值查询 二、选择查询 1、使用关系表达式 2、使用逻辑表达式 3、使用 BETWEEN关键字 4、使用 IN关键字 5、使用 LIKE关键字 6、使用 IS NULL/ NOT NULL关键字 7、符合条件查询 三、聚合函数查询 一…

vuepress搭建个人文档

vuepress搭建个人文档 文章目录 vuepress搭建个人文档前言一、VuePress了解二、vuepress-reco主题个人博客搭建三、vuepress博客部署四、vuepress后续补充 总结 vuepress搭建个人文档 所属目录&#xff1a;项目研究创建时间&#xff1a;2024/7/23作者&#xff1a;星云<Xing…

Nuxt 使用指南:掌握 useNuxtApp 和运行时上下文

title: Nuxt 使用指南&#xff1a;掌握 useNuxtApp 和运行时上下文 date: 2024/7/21 updated: 2024/7/21 author: cmdragon excerpt: 摘要&#xff1a;“Nuxt 使用指南&#xff1a;掌握 useNuxtApp 和运行时上下文”介绍了Nuxt 3中useNuxtApp的使用&#xff0c;包括访问Vue实…

[Spring] Spring日志

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

python实现责任链模式

把多个处理方法串成一个list。下一个list的节点是上一个list的属性。 每个节点都有判断是否能处理当前数据的方法。能处理&#xff0c;则直接处理&#xff0c;不能处理则调用下一个节点&#xff08;也就是当前节点的属性&#xff09;来进行处理。 Python 实现责任链模式&#…

在浏览器中测试JavaScript代码方法简要介绍

在浏览器中测试JavaScript代码方法简要介绍 在浏览器中测试JavaScript代码是前端开发中的一个重要技能。方法如下&#xff1a; 1. 浏览器控制台 最简单和直接的方法是使用浏览器的开发者工具中的控制台&#xff08;Console&#xff09;。 步骤&#xff1a; 在大多数浏览器…

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 目录 Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 一、简单介绍 二、共享纹理 1、共享纹理的原理 2、共享纹理涉及到的关键知识点 3、什么可以实现共享 不能实现共享…

有关于链表带环的两道OJ题目

目录 1.判断链表是否带环 1.1快指针的速度为慢指针的2倍 1.2快指针的速度为慢指针的3倍 2.找出带环链表开始入环的第一个节点 2.1将快慢指针相遇的节点与后面分开&#xff0c;构造交叉链表 2.2记录快慢指针相遇节点&#xff0c;与头结点一起向后走&#xff0c;相遇点为入…

远程开启空调,享受即刻凉爽

随着夏季的热浪逐渐侵袭&#xff0c;我们都渴望回到家中那一刻&#xff0c;能感受到一丝丝的凉意。但是&#xff0c;有时候&#xff0c;即使我们提前开窗通风&#xff0c;房间里的温度依然像烤箱一样闷热难耐。那么&#xff0c;有没有一种方法&#xff0c;能让我们在外头酷暑难…

升级Nvidia CUDA 遇到 sub-process /usr/bin/dpkg returned an error code (1)

1.问题描述 在自己Ubuntu22.04的服务器环境上存在cuda版本为11.5&#xff0c;按照官网教程升级为12.1运行安装命令 sudo apt-get -y install cuda 报错&#xff1a;sub-process /usr/bin/dpkg returned an error code (1) 官网教程&#xff1a; https://developer.nvidia…

PCIE软件基础知识

什么是PCIE PCIe&#xff0c;全称 Peripheral Component Interconnect Express&#xff0c;是一种高速串行计算机扩展总线标准&#xff0c;用于连接计算机内部的硬件组件&#xff0c;如显卡、存储设备、网络适配器等。PCIe是一种点对点的双向通信标准&#xff0c;这意味着它在发…

微信小程序canvas 使用案例(一)

一、cavans 对象获取、上线文创建 1.wxml <!-- canvas.wxml --><canvas type"2d" id"myCanvas"></canvas> 2.js /*** 生命周期函数--监听页面加载*/onLoad(options) {const query wx.createSelectorQuery()query.select(#myCanvas).f…

分离式网络变压器的集成化设计替代传统网络变压器(网络隔离滤波器)尝试

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;今天分享的是应用了分离式网络变压器设计的的新型网络变压器&#xff08;网络隔离变压器&#xff09; 今天我们一起来看这款新型网络变压器&#xff0c;它就是应用分离式网络变压器集成到电路板上&#xff0c;加上外…

CAS乐观锁原理

1、什么是CAS&#xff1f; compare and swap也就是比较和交换&#xff0c;他是一条CPU的并发原语。 他在替换内存的某个位置的值时&#xff0c;首先查看内存中的值与预期值是否一致&#xff0c;如果一致&#xff0c;执行替换操作。 这个操作是一个原子性操作。 Java中基于Un…

昇思学习打卡-21-生成式/Diffusion扩散模型

文章目录 Diffusion扩散模型介绍模型推理结果 Diffusion扩散模型介绍 关于扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;除了本文介绍的离散时间视角外&#xff0c;还有连续时间视角、概率分布转换视角、马尔可夫链视角、能量函数视角、数据增强…

虚拟机迁移报错:虚拟机版本与主机“x.x.x.x”的版本不兼容

1.虚拟机在VCenter上从一个ESXi迁移到另一个ESXi上时报错&#xff1a;虚拟机版本与主机“x.x.x.x”的版本不兼容。 2.例如从10.0.128.13的ESXi上迁移到10.0.128.11的ESXi上。点击10.0.128.10上的任意一台虚拟机&#xff0c;查看虚拟机版本。 3.确认要迁移的虚拟机磁盘所在位…

操作系统---死锁相关

目录 一. 基础概念死锁的定义死锁与饥饿死锁产生原因死锁产生的必要条件资源分配圈&#xff1a;循环等待 VS 死锁 死锁处理策略 二. 死锁预防破坏互斥条件破坏不可剥夺条件破坏请求并保持条件破坏循环等待条件 三. 死锁的避免系统安全状态银行家算法 四. 死锁检测和解除死锁检测…

Mysql注意事项(一)

Mysql注意事项&#xff08;一&#xff09; 最近回顾了一下MySQL&#xff0c;发现了一些MySQL需要注意的事项&#xff0c;同时也作为学习笔记&#xff0c;记录下来。–2020年05月13日 1、通配符* 检索所有的列。 不建议使用 通常&#xff0c;除非你确定需要表中的每个列&am…

微软研发致胜策略 06:学无止境

这是一本老书&#xff0c;作者 Steve Maguire 在微软工作期间写了这本书&#xff0c;英文版于 1994 年发布。我们看到的标题是中译版名字&#xff0c;英文版的名字是《Debugging the Development Process》&#xff0c;这本书详细阐述了软件开发过程中的常见问题及其解决方案&a…

【运维】软件运维方案(2024word完整版)

1. 文档介绍 2. 人员与责任 3. 运维过程内容 4. 运维资源 5. 运维服务规划保障 6. 事件处置 7. 质量改进 8. 运维边界及内容 获取方式&#xff1a; 本文末个人名片直接获取。