React UI 中的声明式思维
声明式的考虑UI
声明式UI意味着不必直接启用、关闭、显示或隐藏组件,而是只需要 声明你想要显示的内容, React 就会通过计算得出该如何去更新 UI。在 React 中,可以通过如下步骤思考如何实现UI效果:
- 定位组件中不同的视图状态
- 确定是什么触发了这些
state
的改变 - 表示内存中的
state
(需要使用useState
) - 删除任何不必要的
state
变量 - 连接事件处理函数去设置
state
定位组件中不同的视图状态
可视化 UI 界面中用户可能看到的所有不同的“状态”,例如:
- 无数据:表单有一个不可用状态的“提交”按钮。
- 输入中:表单有一个可用状态的“提交”按钮。
- 提交中:表单完全处于不可用状态,加载动画出现。
- 成功时:显示“成功”的消息而非表单。
- 错误时:与输入状态类似,但会多错误的消息。
在添加逻辑之前去“模拟”不同的状态或创建“模拟状态”,这个过程可在书写逻辑前快速迭代 UI。
官网示例代码如下:
1 | export default function Form({ |
确定是什么触发了这些状态的改变
触发 state
的更新来响应两种输入:
- 人为输入。比如点击按钮、在表单中输入内容,或导航到链接。
- 计算机输入。比如网络请求得到反馈、定时器被触发,或加载一张图片。
以上两种情况中,必须设置 state
变量 去更新 UI。对于正在开发中的表单来说,需要改变 state 以响应几个不同的输入:
- 改变输入框中的文本时(人为)应该根据输入框的内容是否是空值,从而决定将表单的状态从空值状态切换到输入中或切换回原状态。
- 点击提交按钮时(人为)应该将表单的状态切换到提交中的状态。
- 网络请求成功后(计算机)应该将表单的状态切换到成功的状态。
- 网络请求失败后(计算机)应该将表单的状态切换到失败的状态,与此同时,显示错误信息。
可通过如下表示方法,对状态变化进行可视化:
通过 useState
表示内存中的 state
接下来需要在内存中通过 useState
表示组件中的视图状态。诀窍很简单:state 的每个部分都是“处于变化中的”,并且让“变化的部分”尽可能的少。
根据上一部分的状态图,需要存储输入的 answer
以及用于存储最后一个错误的 error
,这是必须的两个状态。除此之外,一开始可以添加足够多的 state
开始,确保所有可能的视图状态都囊括其中:
1 | // 必须状态 |
删除任何不必要的 state
变量
为避免 state
内容中的重复,从而只需要关注那些必要的部分,可以对前一步中的状态进行重构。这可以让组件更容易被理解,减少重复并且避免歧义,同时防止出现在内存中的 state 不代表任何你希望用户看到的有效 UI 的情况。
可以通过如下一些问题促进对状态的思考:
- 这个
state
是否会导致矛盾?例如,isTyping
与isSubmitting
的状态不能同时为true
。矛盾的产生通常说明了这个 state 没有足够的约束条件。两个布尔值有四种可能的组合,但是只有三种对应有效的状态。为了将“不可能”的状态移除,可以将'typing'
、'submitting'
以及'success'
这三个中的其中一个与status
结合。 - 相同的信息是否已经在另一个
state
变量中存在?另一个矛盾:isEmpty
和isTyping
不能同时为true
。通过使它们成为独立的 state 变量,可能会导致它们不同步并导致 bug。幸运的是,可以移除isEmpty
转而用message.length === 0
。 - 是否可以通过另一个
state
变量的相反值得到相同的信息?isError
是多余的,因为可以检查error !== null
。
重构完成以后,剩下三个必要的状态:
1 | const [answer, setAnswer] = useState(''); |
连接事件处理函数以设置 state
最后,创建事件处理函数以设置 state
变量。下面是绑定好事件的最终表单:
1 | import { useState } from 'react'; |
总结
总的来说,在 React 中,可从 UI 整体出发,为每个视图状态先声明 UI。在组件开发过程中,列出所有状态及其转换关系,通过 useState
将状态放置于组件内,在移除冗余状态后,利用设值函数及事件处理函数,根据事件结果调整状态变化。