状态管理-Reacting to Input with State

387 阅读3分钟

前言:react使用一种声明式的方法去控制UI,言下之意便是我们看到的UI其实是一个个的状态,我们通过控制状态的方式去操作UI,而不是直接去操纵。 后续例子实现的一个类似下图的输出提交表单Form

image.png

按照声明式UI的方式去思考

为了像react一样去思考,需要通过下面5个步骤去帮助我们:

  • 确定组件中的视觉状态
  • 确定使状态发生改变的条件
  • 使用useState去声明state
  • 将所有的非必要变量移除
  • 使用事件控制函数去更改state

1. 确定组件中的视觉状态

计算机科学有“状态机”的概念,设计师又有“视觉状态”的概念,react将二者进行了有机结合。 首先我们需要构造出用户可见的所有state:

  • Form的submit按钮不可用 空值
  • Form的submit按钮可用 键入中
  • Form被完全禁止,出现了Spinner 提交ing
  • 提交成功时的UI success
  • 键入中,但是会有错误文案 error 在编码之前,我们梳理好所有的状态逻辑,下列代码mock的便是表单的视觉部分,由一个status的props控制,其默认值为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的值时,可以看到页面UI发生改变

export default function Form({
  // Try 'submitting', 'error', 'success':
  status = 'success'
}) {
  if (status === 'success') {
    return <h1>That's right!</h1>
 }

image.png

2.确定使状态发生改变的条件

有两种输入方式去触发state更新:1.手动控制,如点击按钮、文本框键入、链接导航;2.计算机输入,如网络请求响应、图片加载等。 在上述两种方法,我们都要设置状态变量去更新UI,在本例中,我们需要根据输入内容的不同去更新UI:

  • -在键入内容时,需要将按按钮从不可用切换为可用,判断条件则是输入框内容是否为空
  • -点击按钮后,需要将状态切换为提交ing
  • -提交成功的回调函数会将状态置为success并展示相应内容
  • -提交失败的回调函数会将状态置为error并展示相应内容

3.使用useState去声明state

首先去思考那些是我们必须的状态,answer存储输入的结果,error去捕获失败的结果。

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

然后就是去思考上述引起UI改变的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);

4.剔除掉非必要state

通过追踪必要变量的方式,可以让我们的代码变得更加简洁,让组件更加易于理解,也会减少一些出乎意料的bug。我们的目标就是让state可以去表征用户所见的UI。为了简化组件state,可以从以下几个角度思考:

    • 状态之间是否矛盾。比如isEmptyisSubmitting不可同时为true。
    • 同一UI是否被不同state去重复定义。isEmptyisTyping不能同时为true
    • 是否可以通过另一变量的相关值得到相同的结果。 如 isError的结果和error !== null 通过上述原则,剔除部分非必要状态后,代码如下
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // typing submitting sucess

5.使用事件处理函数去更改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);
  });
}