React 18 用 State 响应输入

news2024/11/18 15:31:15

参考文章

用 State 响应输入

React 控制 UI 的方式是声明式的。不必直接控制 UI 的各个部分,只需要声明组件可以处于的不同状态,并根据用户的输入在它们之间切换。这与设计师对 UI 的思考方式很相似。

声明式 UI 与命令式 UI 的比较

当设计 UI 交互时,可能会去思考 UI 如何根据用户的操作而响应变化。想象一个允许用户提交一个答案的表单:

  • 当向表单输入数据时,“提交”按钮会随之变成可用状态
  • 当点击“提交”后,表单和提交按钮都会随之变成不可用状态,并且加载动画会随之出现
  • 如果网络请求成功,表单会随之隐藏,同时“提交成功”的信息会随之出现
  • 如果网络请求失败,错误信息会随之出现,同时表单又变为可用状态

命令式编程 中,以上的过程直接告诉如何去实现交互。必须去根据要发生的事情写一些明确的命令去操作 UI。

这种告诉计算机如何去更新 UI 的编程方式被称为命令式编程

在这个命令式 UI 编程的例子中,表单没有使用 React 生成,而是使用原生的 DOM:

// index.js
async function handleFormSubmit(e) {
  e.preventDefault();
  disable(textarea);
  disable(button);
  show(loadingMessage);
  hide(errorMessage);
  try {
    await submitForm(textarea.value);
    show(successMessage);
    hide(form);
  } catch (err) {
    show(errorMessage);
    errorMessage.textContent = err.message;
  } finally {
    hide(loadingMessage);
    enable(textarea);
    enable(button);
  }
}

function handleTextareaChange() {
  if (textarea.value.length === 0) {
    disable(button);
  } else {
    enable(button);
  }
}

function hide(el) {
  el.style.display = 'none';
}

function show(el) {
  el.style.display = '';
}

function enable(el) {
  el.disabled = false;
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (answer.toLowerCase() == 'istanbul') {
        resolve();
      } else {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      }
    }, 1500);
  });
}

let form = document.getElementById('form');
let textarea = document.getElementById('textarea');
let button = document.getElementById('button');
let loadingMessage = document.getElementById('loading');
let errorMessage = document.getElementById('error');
let successMessage = document.getElementById('success');
form.onsubmit = handleFormSubmit;
textarea.oninput = handleTextareaChange;
<!--index.html-->
<form id="form">
  <h2>City quiz</h2>
  <p>
    What city is located on two continents?
  </p>
  <textarea id="textarea"></textarea>
  <br />
  <button id="button" disabled>Submit</button>
  <p id="loading" style="display: none">Loading...</p>
  <p id="error" style="display: none; color: red;"></p>
</form>
<h1 id="success" style="display: none">That's right!</h1>

<style>
* { box-sizing: border-box; }
body { font-family: sans-serif; margin: 20px; padding: 0; }
</style>

对于独立系统来说,命令式地控制用户界面的效果也不错,但是当处于更加复杂的系统中时,这会造成管理的困难程度指数级地增长。如同示例一样,想象一下,当想更新这样一个包含着不同表单的页面时,想要添加一个新 UI 元素或一个新的交互,为了保证不会因此产生新的 bug(例如忘记去显示或隐藏一些东西),必须十分小心地去检查所有已经写好的代码。

React 正是为了解决这样的问题而诞生的。

在 React 中,不必直接去操作 UI —— 不必直接启用、关闭、显示或隐藏组件。相反,只需要 声明想要显示的内容, React 就会通过计算得出该如何去更新 UI。

声明式地考虑 UI

已经从上面的例子看到如何去实现一个表单了,为了更好地理解如何在 React 中思考,接下来将会学到如何用 React 重新实现这个 UI:

  1. 定位组件中不同的视图状态
  2. 确定是什么触发了这些 state 的改变
  3. 表示内存中的 state(需要使用 useState
  4. 删除任何不必要的 state 变量
  5. 连接事件处理函数去设置 state

步骤 1:定位组件中不同的视图状态

在计算机科学中,或许听过可处于多种“状态”之一的 “状态机”。如果有与设计师一起工作,那么可能已经见过不同“视图状态”的模拟图。正因为 React 站在设计与计算机科学的交点上,因此这两种思想都是灵感的来源。

首先,需要去可视化 UI 界面中用户可能看到的所有不同的“状态”:

  • 无数据:表单有一个不可用状态的“提交”按钮。
  • 输入中:表单有一个可用状态的“提交”按钮。
  • 提交中:表单完全处于不可用状态,加载动画出现。
  • 成功时:显示“成功”的消息而非表单。
  • 错误时:与输入状态类似,但会多错误的消息。

像一个设计师一样,会想要在添加逻辑之前去“模拟”不同的状态或创建“模拟状态”。例如下面的例子,这是一个对表单可视部分的模拟。这个模拟被一个 status 的属性控制,并且这个属性的默认值为 empty

export default function Form({
  status = 'empty'
}) {
  if (status === 'success') {
    return <h1>That's right!</h1>
  }
  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form>
        <textarea />
        <br />
        <button>
          Submit
        </button>
      </form>
    </>
  )
}

可以随意命名这个属性,名字并不重要。试着将 status = 'empty' 改为 status = 'success',然后就会看到成功的信息出现。模拟可以在书写逻辑前快速迭代 UI。这是同一组件的一个更加充实的原型,仍然由 status 属性“控制”:

export default function Form({
  // Try 'submitting', 'error', 'success':
  status = 'empty'
}) {
  if (status === 'success') {
    return <h1>That's right!</h1>
  }
  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form>
        <textarea disabled={
          status === 'submitting'
        } />
        <br />
        <button disabled={
          status === 'empty' ||
          status === 'submitting'
        }>
          Submit
        </button>
        {status === 'error' &&
          <p className="Error">
            Good guess but a wrong answer. Try again!
          </p>
        }
      </form>
      </>
  );
}

步骤 2:确定是什么触发了这些状态的改变

可以触发 state 的更新来响应两种输入:

  • 人为输入。比如点击按钮、在表单中输入内容,或导航到链接。
  • 计算机输入。比如网络请求得到反馈、定时器被触发,或加载一张图片。

以上两种情况中,必须设置 state 变量 去更新 UI。对于正在开发中的表单来说,需要改变 state 以响应几个不同的输入:

  • 改变输入框中的文本时(人为)应该根据输入框的内容是否是空值,从而决定将表单的状态从空值状态切换到输入中或切换回原状态。
  • 点击提交按钮时(人为)应该将表单的状态切换到提交中的状态。
  • 网络请求成功后(计算机)应该将表单的状态切换到成功的状态。
  • 网络请求失败后(计算机)应该将表单的状态切换到失败的状态,与此同时,显示错误信息。

注意:人为输入通常需要 事件处理函数!

为了可视化这个流程,请尝试在纸上画出圆形标签以表示每个状态,两个状态之间的改变用箭头表示。可以像这样画出很多流程并且在写代码前解决许多 bug。

在这里插入图片描述

步骤 3:通过 useState 表示内存中的 state

接下来会需要在内存中通过 useState 表示组件中的视图状态。诀窍很简单:state 的每个部分都是“处于变化中的”,并且需要让“变化的部分”尽可能的少。更复杂的程序会产生更多 bug!

先从绝对必须存在的状态开始。例如,需要存储输入的 answer 以及用于存储最后一个错误的 error (如果存在的话):

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);

接下来,需要一个状态变量来代表想要显示的那个可视状态。通常有多种方式在内存中表示它,因此需要进行实验。

如果很难立即想出最好的办法,那就先从添加足够多的 state 开始,确保所有可能的视图状态都囊括其中:

const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

最初的想法或许不是最好的,但是没关系,重构 state 也是步骤中的一部分!

步骤 4:删除任何不必要的 state 变量

会想要避免 state 内容中的重复,从而只需要关注那些必要的部分。花一点时间来重构 state 结构,会让组件更容易被理解,减少重复并且避免歧义。目的是防止出现在内存中的 state 不代表任何希望用户看到的有效 UI 的情况。(比如绝对不会想要在展示错误信息的同时禁用掉输入框,导致用户无法纠正错误!)

这有一些可以问自己的, 关于 state 变量的问题:

  • 这个 state 是否会导致矛盾?例如,isTypingisSubmitting 的状态不能同时为 true。矛盾的产生通常说明了这个 state 没有足够的约束条件。两个布尔值有四种可能的组合,但是只有三种对应有效的状态。为了将“不可能”的状态移除,可以将 'typing''submitting' 以及 'success' 这三个中的其中一个与 status 结合。
  • 相同的信息是否已经在另一个 state 变量中存在?另一个矛盾:isEmptyisTyping 不能同时为 true。通过使它们成为独立的 state 变量,可能会导致它们不同步并导致 bug。幸运的是,可以移除 isEmpty 转而用 message.length === 0
  • 是否可以通过另一个 state 变量的相反值得到相同的信息isError 是多余的,因为可以检查 error !== null

在清理之后,只剩下 3 个(从原本的 7 个!)必要的 state 变量:

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

正是因为不能在不破坏功能的情况下删除其中任何一个状态变量,因此可以确定这些都是必要的。

步骤 5:连接事件处理函数以设置 state

最后,创建事件处理函数去设置 state 变量。下面是绑定好事件的最终表单:

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>That's right!</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>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

尽管这些代码相对与最初的命令式的例子来说更长,但是却更加健壮。将所有的交互变为 state 的改变,可以让你避免之后引入新的视图状态后导致现有 state 被破坏。同时也使你在不必改变交互逻辑的情况下,更改每个状态对应的 UI。

摘要

  • 声明式编程意味着为每个视图状态声明 UI 而非细致地控制 UI(命令式)。
  • 当开发一个组件时:
    1. 写出组件中所有的视图状态。
    2. 确定是什么触发了这些 state 的改变。
    3. 通过 useState 模块化内存中的 state。
    4. 删除任何不必要的 state 变量。
    5. 连接事件处理函数去设置 state。

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

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

相关文章

Semantic Visual SLAM 简述

语义slam解决的问题&#xff1a;在动态和复杂环境下的定位问题 语义slam的关键&#xff1a;语义信息的提取与联系。 本文仅讨论视觉slam。 参考文献&#xff1a; [1] https://arxiv.org/abs/2209.06428 [2] slambook 一、简介 slam的目的在于定位和建图。 视觉slam由于其…

电子器件系列57:三防漆

三防漆是一种特殊配方的涂料&#xff0c;用于保护线路板及其相关设备免受环境的侵蚀。三防漆具有良好的耐高低温性能&#xff1b;其固化后成一层透明保护膜&#xff0c;具有优越的绝缘、防潮、防漏电、防震、防尘、防腐蚀、防老化、耐电晕等性能。 三防漆根据每个地区和每个厂家…

【Kubernetes】Rancher管理集群

目录 1、安装 rancher 2、登录 Rancher 平台 3、Rancher 管理已存在的 k8s 集群 4、Rancher 部署监控系统 5、使用 Rancher 仪表盘管理 k8s 集群 以创建 nginx 服务为例 创建名称空间 namespace 创建 Deployment 资源 创建 service 1、安装 rancher 在 所有 node 节点下…

org.xml.sax.SAXParseException: 元素类型 “select“ 必须后跟属性规范 “>“ 或 “/>

项目场景&#xff1a; springBoot项目build编译成功&#xff0c;maven依赖都没问题&#xff0c;项目启动run服务的时候报异常&#xff1a; org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name demoUserController: Unsatisfie…

燃尽图、甘特图、鱼骨图

燃尽图、甘特图、鱼骨图 1. 燃尽图 燃尽图&#xff08;burn down chart&#xff09;是在项目完成之前&#xff0c;对需要完成的工作的一种可视化表示。燃尽图有一个Y轴&#xff08;工作&#xff09;和X轴&#xff08;时间&#xff09;。理想情况下&#xff0c;该图表是一个向下…

运动控制-CodeSys编程书籍

陆国君 <<PLC综合开发利器-CodeSys基础编程及应用指南 网上流传的陆国君PDF书籍 <<PLC综合开发利器-CodeSys基础编程及应用指南>> 很不错, 这本书网上有两个版本 556页是新的版本, 423页是老的版本, 不过内容差异不大. 423页是老的版本下载: url80.ctfile.co…

FL Studio21.1最新中文版本新功能有哪些?详细下载教程

FL Studio 21.1通过钢琴卷中的音阶吸附和基于 Python 的脚本&#xff0c;提供了更快捷、更有创意的音符编辑功能。更新版还带来了针对更好的管理采样的彩色波形&#xff0c;Hyper Chorus 极致合唱插件、Edison 中的 AI Declipper&#xff08;人工智能去削波器&#xff09;等。 …

Google Play P12文件

一、P12文件 给到后台用于订单消费/订单查询等功能&#xff0c;这个就是谷歌的凭证&#xff0c;没有这个你不配查&#xff0c;要的就是安全性。 1.应用要用到Google Pay&#xff1b; 2.自己后台去谷歌后台做订单消费/订单查询&#xff0c;如果所以操作由移动端去做&#xff0…

(7)(7.6) 恢复任务回放

文章目录 前言 7.6.1 配置 7.6.2 工作原理 7.6.3 局限性 前言 本页介绍了什么是"任务继续时后退"功能以及如何使用该功能。 &#xff01;Note 从 4.1 版起&#xff0c;Plane、Copter 和 Rover 均可使用此功能。 在某些应用或运行区域&#xff0c;为了消除冲突&…

Java自学到什么程度就可以去找工作了?

引言 Java作为一门广泛应用于软件开发领域的编程语言&#xff0c;对于初学者来说&#xff0c;了解到什么程度才能开始寻找实习和入职机会是一个常见的问题。 本文将从实习和入职这两个方面&#xff0c;分点详细介绍Java学习到什么程度才能够开始进入职场。并在文章末尾给大家安…

Docker的数据管理及端口映射与容器互联(使用centos镜像)

目录 Docker数据管理 1&#xff0e;数据卷 2&#xff0e;数据卷容器 Docker端口映射 Docker容器互联 Docker数据管理 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&…

一些异常情况

对象注入失败 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name hotelServiceImpl: Unsatisfied dependency expressed through field baseMapper; nested exception is org.springframework.beans.factory.NoSuchBeanDefini…

(自记录)LINUX部署docker+opencv的标注软件CVAT

开源代码地址 https://github.com/opencv/cvat 主要参考官方用户手册 https://opencv.github.io/cvat/docs/administration/basics/installation/#ubuntu-1804-x86_64amd64 其中涉及换源&#xff0c;不然太慢了&#xff0c;有个坑&#xff0c;官方的换源地址第四个是错的&#…

arduino使用FreeRTOS实时操作系统

How to use FreeRTOS with Arduino – Real time operating system 如何让Arduino运行FreeRTOS实时操作系统 在本文中&#xff0c;您将学习如何使用带Arduino的Freertos操作系统来执行特定的任务。实时操作系统又称RTOS&#xff0c;是一种旨在满足实时应用要求的操作系统。它…

将字符串的大小写字母进行转换大写字母转换为小写字母小写字母转换为大写字母str.swapcase()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将字符串的大小写字母进行转换 大写字母转换为小写字母 小写字母转换为大写字母 str.swapcase() 选择题 以下程序的运行结果是? str"I Like Python!" print("【显示】str:&quo…

可自定义实时监控系统HertzBeat

什么是 HertzBeat &#xff1f; HertzBeat是一个拥有强大自定义监控能力&#xff0c;无需 Agent 的开源实时监控告警系统。集 监控告警通知 为一体&#xff0c;支持对应用服务&#xff0c;数据库&#xff0c;操作系统&#xff0c;中间件&#xff0c;云原生&#xff0c;网络等监…

算法:双指针解决数组划分和数组分块问题

文章目录 实现原理实现思路典型例题移动0复写0快乐数盛最多水的容器有效三角形的个数三数之和四数之和 总结 在快速排序或者是其他和数组有关的题目中&#xff0c;有很经典的一类题目是关于数组划分的&#xff0c;数组划分就是把数组按照一定的规则划分为不同的区间&#xff0c…

jupyter notebook出现ERR_SSL_VERSION_OR_CIPHER_MISMATCH解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

SQL阶段性优化

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;微信小程序、页面跳转、移动端、前端☀️每日 一言&#xff1a;我们要把懦弱扼杀在摇篮中。 一、前言 我们在做系统的过程中&#xff0c;难免会遇到页面查询速度慢&#xff0c;性能差的问题&…