React 18 用 State 响应输入

参考文章

用 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}><textareavalue={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.mzph.cn/news/53470.shtml

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

相关文章

Python爬虫猿人学逆向系列——第六题

题目&#xff1a;采集全部5页的彩票数据&#xff0c;计算全部中奖的总金额&#xff08;包含一、二、三等奖&#xff09; 地址&#xff1a;https://match.yuanrenxue.cn/match/6 本题比较简单&#xff0c;只是容易踩坑。话不多说请看分析。 两个参数&#xff0c;一个m一个f&…

【宝藏系列】一文带你梳理 Linux 的五种 IO 模型

【宝藏系列】一文带你梳理 Linux 的五种 IO 模型 文章目录 【宝藏系列】一文带你梳理 Linux 的五种 IO 模型&#x1f468;‍&#x1f3eb;前言1️⃣用户态和核心态1️⃣1️⃣用户态和核心态的切换 2️⃣进程切换3️⃣进程阻塞4️⃣文件描述符(fd, File Descriptor)5️⃣缓存I/O…

【笔记】MySQL行转列函数

GROUP_CONCAT()函数 创建表person_info&#xff0c;并插入数据 CREATE TABLE person_info (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(100) DEFAULT NULL,family varchar(100) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT8 DEFAULT CHARSETutf8;…

RecyclerView面试问答

RecycleView 和 ListView对比: 使用方法上 ListView:继承重写 BaseAdapter,自定义 ViewHolder 与 converView优化。 RecyclerView: 继承重写 RecyclerView.Adapter 与 RecyclerView.ViewHolder。设置 LayoutManager 来展示不同的布局样式 ViewHolder的编写规范化,ListVie…

基于蛾群算法优化的BP神经网络(预测应用) - 附代码

基于蛾群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于蛾群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.蛾群优化BP神经网络2.1 BP神经网络参数设置2.2 蛾群算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

会声会影2023全新中文专业版下载安装教程

熟练使用会声会影视频编辑工具&#xff0c;对视频创作过程的帮助是极大的。大家可以放心大胆地去研究会声会影的视频编辑技巧&#xff0c;会声会影2023与以往版本会声会影版本最大的区别是&#xff1a;账户制管理。可以通过账户添加或移除设备&#xff0c;非常便捷。该软件一直…

量化QAT QLoRA GPTQ

模型量化的思路可以分为PTQ&#xff08;Post-Training Quantization&#xff0c;训练后量化&#xff09;和QAT&#xff08;Quantization Aware Training&#xff0c;在量化过程中进行梯度反传更新权重&#xff0c;例如QLoRA&#xff09;&#xff0c;GPTQ是一种PTQ的思路。 QAT…

【安卓】自定义View实现画板涂鸦等功能

一、实现效果 二、代码 1、MainActivity.class package com.lsl.mydrawingboarddemo;import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat;import android.os.Bundle; import android.os.Handler; import android.view.View; impo…

【C++进阶(一)】STL大法以及string的使用

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; STL标准库 1. 前言2. STL库的版本以及缺陷3. ST…

远程调试环境

一、远程调试 1.安装vscode 2.打开vscode&#xff0c;下载插件Remote-SSH,用于远程连接 3.安装php debug 4.远程连接&#xff0c;连接到远端服务器 注&#xff1a;连接远程成功后&#xff0c;在远程依然要进行安装xdebug&#xff0c;刚才只是在vscode中进行的安装。 5.配置la…

UG\NX二次开发 使用BlockUI设计对话框时,如何设置默认的开发语言?

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,C\C++,Qt-CSDN博客 简介: NX二次开发使用BlockUI设计对话框时,如何设置默认的代码语言? 效果: 方法: 依次打开“文件”->“实用工具”->“用户默认设置”->“用户界面”->“操作记录”->“…

【机器学习】python基础实现线性回归

手写梯度下降的实现ykxb的线性回归 算法步骤&#xff1a; &#xff08;1&#xff09;构造数据&#xff0c;y3*x5; &#xff08;2&#xff09;随机初始化和&#xff0c;任意数值&#xff0c;例如9,10; &#xff08;3&#xff09;计算&#xff0c;,并计算 &#xff08;4&…

只考一门数据结构,计算机学硕复录比1:1的山东双非学校考情分析

青岛理工大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1420字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 青岛理工…

Vue2向Vue3过度核心技术指令补充

目录 1 指令修饰符1.1 什么是指令修饰符&#xff1f;1.2 按键修饰符1.3 v-model修饰符1.4 事件修饰符 2 v-bind对样式控制的增强-操作class2.1 语法&#xff1a;2.2 对象语法2.3 数组语法2.4 代码练习 3 京东秒杀-tab栏切换导航高亮3.1 需求&#xff1a;3.2 准备代码:3.3 思路&…

Mac电脑系统应该用什么软件进行优化清理?

作为一枚资深的Windows系统使用者&#xff0c;小编刚刚转向Mac系统的怀抱时&#xff0c;各种不适应&#xff0c;Windows系统中普遍使用的360清理软件目前暂时没有Mac版本的&#xff0c;这就让小编很是头疼了&#xff0c;大家的Mac都是用的什么清理软件呢&#xff1f; 经过一番…

大数据:AI大模型对数据分析领域的颠覆(文末送书)

随着数字化时代的到来&#xff0c;大数据已经成为了各行各业中不可或缺的资源。然而&#xff0c;有效地分析和利用大数据仍然是一个挑战。在这个背景下&#xff0c;OpenAI推出的Code Interpreter正在对数据分析领域进行颠覆性的影响。 如何颠覆数据分析领域&#xff1f;带着这…

MySQL高级篇——MySQL架构篇2(MySQL的数据目录)

目录 1 MySQL8的主要目录结构1.1 数据库文件的存放路径1.2 相关命令目录1.3 配置文件目录 2 数据库和文件系统的关系2.1 查看默认数据库2.2 数据库在文件系统中的表示2.3.1 InnoDB存储引擎模式2.3.2 MyISAM存储引擎模式 2.4 总结2.5 视图在文件系统中的表示2.6 其他的文件 1 My…

Python如何进行基本的数学运算

Python进行基本的数学运算 Python是一门功能强大且易于学习的编程语言&#xff0c;它不仅可以用于开发应用程序&#xff0c;还可以用于执行各种数学运算。让我们一起来看看如何在Python中进行基本的数学运算。 加法、减法、乘法和除法 Python支持常见的加法、减法、乘法和除…

【剑指offer】【C语言】调整数组中奇偶数顺序_冒泡法+二分法_调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,所有偶数位于数组的后半部分

题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数&#xff0c;来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c;所有偶数位于数组的后半部分。 示例&#xff1a;{4&#xff0c;6&#xff0c;5&#xff0c;7&#xff0c;2&#xff0c;3…

PAT 1127 ZigZagging on a Tree

个人学习记录&#xff0c;代码难免不尽人意。 Suppose that all the keys in a binary tree are distinct positive integers. A unique binary tree can be determined by a given pair of postorder and inorder traversal sequences. And it is a simple standard routine t…