图文并茂教你快速入门React系列04-状态管理

news2024/11/29 1:55:39

在React中,什么是状态?

响应式

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

真实举例给你看

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}>
        <textarea
          value={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() !== 'lima'
      if (shouldError) {
        reject(new Error('猜的不错,但答案不对。再试试看吧!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

仔细观察这段代码:

if (status === 'success') {
    return <h1>答对了!</h1>
  }

会发现真的不一样哦~~把代码放进页面里,测试一下看看~当你答案写对的时候,你会发现页面直接渲染了这部分

after:

如何管理状态?

状态不应包含冗余或重复的信息

举个栗子

const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

观察上面的代码,其实我们用到的fullName 是另外两个变量组合起来的,但是我们声明了三个useState
优化如下:

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;

组件间共享状态

通常我们会如何处理组件状态共享呢?

方法:将状态从这两个组件移除,移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”。

保留和重置状态

什么场景会需要我们考虑保留和重置状态呢?

当重新渲染一个组件, 需要决定哪些部分保留和更新,以及丢弃或重新创建。默认情况下,React 会保留树中与先前渲染的组件树“匹配”的部分。
但是某些情况下,我们需要一些特定的处理,比如下面的这个例子

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>
      <ContactList
        contacts={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' }
];


我们选择了收件人,然后写邮件内容,点击发送以后,再切换收件人,发现邮件内容并么有改变,我们希望它可以清空信息,此时这就是我们的一些特定更新需求,那么如果需要实现这个需求,普通的情况下,我们的做法是会写一个onChange事件,放在左侧切换按钮上面,来清除右侧的文本值,但是使用react我们可以有不同的办法去解决这个问题:

机智的办法来了:React 允许你覆盖默认行为,可通过向组件传递一个唯一 key(如 Chat key={email} 来 强制 重置其状态。
上代码:

<div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.email} contact={to} /> 重点就是这句话
    </div>

提取状态逻辑到 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>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={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 }
];

如何进行深层 props 传递–使用 Context

Context 允许父组件将一些信息提供给它下层的任何组件,不管该组件多深层也无需通过 props 逐层透传。
使用方法如下:

我们使用四个文件,组合成下面的样式


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>;
    default:
      throw Error('未知级别:' + level);
  }
}

LevelContext.js

import { createContext } from 'react';

export const LevelContext = createContext(0);

如何使用Reducer 和 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>HAPPY的一天</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

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.Provider
        value={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 }
];

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 (
    <>
      <input
        placeholder="添加任务"
        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 = (
      <>
        <input
          value={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>
      <input
        type="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>
  );
}

小伙伴们,先写到这里啦,我们明天再见啦~~

大家要天天开心哦

欢迎大家指出文章需要改正之处~
学无止境,合作共赢

在这里插入图片描述

欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

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

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

相关文章

chatgpt赋能python:Python另存为在哪:一篇SEO指南

Python另存为在哪&#xff1a;一篇SEO指南 简介 Python是目前最流行的编程语言之一&#xff0c;它的高效性和易用性使得越来越多的人选择使用它来编写软件和web应用程序。然而&#xff0c;Python在保存文件时可能会有些棘手&#xff0c;尤其是在需要另存为不同格式或目录时。…

chatgpt赋能python:Python只取数字:你需要知道的一切

Python只取数字&#xff1a;你需要知道的一切 在当今数字化的时代&#xff0c;数字信息已经成为全球交流和经济活动的主要组成部分。因此&#xff0c;在处理数据时&#xff0c;我们经常需要从文件中提取数字信息。Python是一种广泛应用于数据处理和分析的编程语言&#xff0c;…

Java面向对象程序开发——基础

文章目录 前言类和对象类对象 构造方法匿名对象变量作用域this关键字总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; Java 是一种以面向对象编程为基础的编程语言 在 Java 编程中&#xff0c;一切皆为对象 每个对象都有其自己的属性和方法 对象可以…

图论试题2021

25 A&#xff1a;最大度是7&#xff0c;大于了顶点数6&#xff0c;故不是简单图的度序列。 C&#xff1a;树的度序列至少要有两个度为1的顶点 D&#xff1a;只要度数为奇数的个数有偶数个&#xff0c;就是度序列。 A&#xff1a;每棵树的中心由一个点或两个相邻点组成 B&…

碳中和城市建筑能源系统(1):能源篇(龙惟定)2022

碳中和城市建筑能源系统(1):能源篇 碳中和城市建筑能源系统&#xff08;1&#xff09;&#xff1a;能源篇&#xff08;龙惟定&#xff09;2022 碳中和城市建筑能源系统&#xff08;2&#xff09;&#xff1a;网络篇&#xff08;龙惟定&#xff09;2022 碳中和城市建筑能源系统&…

chatgpt赋能python:Python可以用i++吗?——探讨Python自增自减运算符

Python可以用i吗&#xff1f;——探讨Python自增自减运算符 Python作为一门高级编程语言&#xff0c;一直以来都备受开发者们的喜爱。Python以代码简洁、语言简单易懂、易于学习等特点而赢得了广大开发者的信赖。然而&#xff0c;在Python中并没有i这样的自增自减运算符&#…

计算机网络第一章——计算机系统结构(下)

提示&#xff1a;总角之宴&#xff0c;言笑晏晏。信誓旦旦&#xff0c;不思其反。反是不思&#xff0c;亦已焉哉。 文章目录 1.2.1 分层结构&#xff0c;协议&#xff0c;接口和服务为什么要有分层&#xff1f;怎么分层正式认识分层结构概念总结 1.2.2 OSI 参考模型ISO参考模型…

软考A计划-电子商务设计师-电子商务系统分析与设计

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

【JUC基础】14. ThreadLocal

目录 1、前言 2、什么是ThreadLocal 3、ThreadLocal作用 4、ThradLocal基本使用 4.1、创建和初始化 4.2、存储和获取线程变量 4.3、清理和释放线程变量 4.4、小结 4.5、示例代码 5、ThreadLocal原理 5.1、set() 5.2、get() 5.3、变量清理 5.4、ThreadLocalMap 6、…

硬链接与符号链接

硬链接与符号链接(Hard Link vs Symbolic Link) 两者对于 Linux 操作系统的异同、优缺点。 什么是链接&#xff1f; 在最一般的意义上&#xff0c;链接是两个对象之间的连接。更具体地说是在计算方面&#xff1b;根据牛津定义&#xff0c;链接是与代码或指令的连接&#xff0c…

【读书笔记】《平凡的世界》- 路遥

他又进一步想&#xff0c;郝红梅抛开他而和顾养民相好&#xff0c;也完全是正常的啊&#xff01;他自己在哪方面都无法和顾养民比较。男女相好&#xff0c;这是两厢情愿的事&#xff0c;而怎能像乡俗话说的“剃头担子一头热”呢&#xff1f; 青春激流打起的第一个浪头在内心渐渐…

Eclipse不用删除之前的项目也可以多次导入相同的项目,操作十分简单!!

问题引入 当我们在学习时&#xff0c;常常需要多次导入网上的同一个案例进行查看效果或者导入自己的项目、用于进行代码测试&#xff0c;原来的项目要继续保留&#xff0c;作为备份&#xff0c;防止代码测试对代码修改过火&#xff0c;一去不返。但当我们导入在Eclipse项目管理…

JavaScript蓝桥杯------学海无涯

目录 一、介绍二、准备三、目标四、代码五、完成 一、介绍 小蓝最近一直在云课平台学习&#xff0c;为了更好的督促自己&#xff0c;于是将每天的学习时间都记录了下来&#xff0c;但是如何更加直观的显示学习时间让小蓝很是苦恼。本题需要你使用 ECharts 帮助小蓝实现统计学习…

【C/C++】基础知识之动态申请内存空间new-delete

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

对Java远程热部署实践学习和分析

目录 一、热部署现状和必要性分析 &#xff08;一&#xff09;热部署定义和现状分析 &#xff08;二&#xff09;技术实现难度分析 &#xff08;三&#xff09;其必要性分析 二、走进美团Java远程热部署实践 &#xff08;一&#xff09;Sonic分析 &#xff08;二&#x…

Delphi11的多线程ⓞ,附送图片处理代码

Delphi11的多线程ⓞ OLD Coder , 习惯使用Pascal 接下来准备启用多线程&#xff0c;毕竟硬件多核&#xff0c;Timer不太爽了&#xff08;曾经的桌面&#xff0c;都是Timer——理解为“片”&#xff09; 突然想写写&#xff0c;不知道还有多少D兄弟们在。 从源码开始 用D11之…

第1节:vue cesium 概述(含网站地址+视频)

在开始介绍vue cesium之前&#xff0c;我们先聊聊cesiumjs&#xff0c;如果你对这块内容比较熟悉&#xff0c;可以直接跳过这节内容。 cesiumJS 简介 官方网址&#xff1a;https://cesium.com/platform/cesiumjs/ CesiumJS 是一个开源 JavaScript 库&#xff0c;主要用于基于…

Linux之理解文件系统——文件的管理

文章目录 前言一、磁盘1.磁盘的物理结构2.磁盘的存储结构3.磁盘的逻辑结构 二、文件系统与inode1.文件在磁盘中是如何存储的&#xff1f;2.对文件进行操作 三、软硬链接1.软链接创建软链接&#xff1a;inode删除软链接&#xff1a;软链接的作用&#xff1a; 2.硬链接创建硬链接…

chatgpt赋能python:Python另存为对话框:如何在Python中创建一个另存为对话框

Python 另存为对话框&#xff1a;如何在Python中创建一个另存为对话框 如果你是一名 Python 开发者&#xff0c;你可能会常常需要为你的应用程序添加一个另存为对话框。这个对话框使用户可以将他们的数据保存为一个新的文件&#xff0c;而不是覆盖原始文件。然而&#xff0c;很…

【JavaEE】Tomcat-Servelet第一个helloworld程序

Tomcat & Servelet第一个程序helloworld&#xff01; 文章目录 JavaEE & Tomcat & 第一个Servelet程序1. HTTP服务器 - Tomcat1.1 Tomcat的目录结构&#xff1a;1.2 启动Tomcat1.3 Tomcat的优点 2. Servelet框架2.1 创建Maven项目2.2 引入依赖2.3 创建目录2.4 写代…