前言:react使用一种声明式的方法去控制UI,言下之意便是我们看到的UI其实是一个个的状态,我们通过控制状态的方式去操作UI,而不是直接去操纵。 后续例子实现的一个类似下图的输出提交表单Form
按照声明式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>
}
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,可以从以下几个角度思考:
-
- 状态之间是否矛盾。比如
isEmpty和isSubmitting不可同时为true。
- 状态之间是否矛盾。比如
-
- 同一UI是否被不同state去重复定义。
isEmpty和isTyping不能同时为true
- 同一UI是否被不同state去重复定义。
-
- 是否可以通过另一变量的相关值得到相同的结果。 如
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);
});
}