保留或者重设状态
对于以下的组件
import { useState } from 'react';
export default function Chat({ contact }) {
const [text, setText] = useState('');
return (
<section>
<textarea
value={text}
placeholder={`Chat to ${contact.name}`}
onChange={e => setText(e.target.value)}
/>
</section>
);
}
如果重置了 contact ,组件内部的 <textarea />
的值不会被重置。如果需要重置整个 <textarea />
,当然可以通过 useEffect
实现。不过如 React 的文档所示,React 提供了方法来覆盖这种默认行为,强制组件重置。即给组件传递 key
属性。例如 <Chat key={email} />
。这样会告知 React,如果 key 的值变动,可以认为是一个完全不同的 Chat
组件。Chat
会被重新从数据上从零创建。这样如果 key 变动了, textarea
内的值就会被重置。这种方法对于某些场景下,强制动画等行为很有帮助。
将 state 的相关逻辑抽离成 reducer
当 state 过多时,将它抽离成 reducer 应该会较有帮助。可以保持较好的代码聚合性。
const initialTasks = [];
function tasksReducer(tasks, action) {
switch(action.type) {
case ’add‘: {
return [
...tasks, {
id: action.id,
text: action.text,
done: false
}
];
}
}
}
export default function TaskApp() {
const [tasks, dispatch] = useReducer(
tasksReducer,
initialTasks
);
// some component
return <button onClick={() => dispatch({ type: 'add', id: 1, text: 'Task' })}></button>;
}
可以把 context
和 reducer
结合来较好的管理复杂状态
声名式 UI
- 识别组件的不同的状态
- 决定状态的变化是如何变迁的
- 在内存中使用
useState
表述状态 - 去除不重要的状态变量
选择状态结构
好的状态结构可以让修改、debug组件变得容易。不好的状态结构是 bug 的来源。虽然次优的状态结构也能工作,不过还是有一些状态结构的原则。
- 组合相关状态。如果两个状态总是同时更新,考虑把他们合并成一个状态变量。
- 避免状态矛盾 (contradiction)。
- 避免冗余状态,如果有的状态可以通过其他状态计算得来,就不应该存储在状态树中。
- 避免深度嵌套的状态。过于结构化的状态难以管理,如果可能,应当选择扁平的状态结构。
参考 数据库范式 Database normalization description
- 如果数据存储在多个地方,就要保证同时更新他们,并且更新的时候要保证相同的逻辑。维护起来就会很困难。
- 不一致的依赖: 例如把一个员工的工资存储在他关联的客户表里,可能导致数据获取数据的路径遗矢或断裂。
- 数据库范式 一般有三种。在前端工程中,难有相关的约束。大量的数据被重复以方便显示。
组合状态
例如有状态跟踪鼠标位置,应该把鼠标的 x, y 坐标组合存储在一对象里,而不是存储在两个变量 x, y 中。
// 这样的代码难以维护
function MouseTrace() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
}
// 将状态进行组合,会让代码容易维护
function MousePositionTrace() {
const [position, setPosition] = useState({ x: 0, y: 0 });
}
避免状态矛盾
例如下面的代码跟踪数据是否发送
export function FeedbackForm() {
const [val, setVal] = useState('');
// 数据发送中
const [isSending, setIsSending] = useState(false);
// 数据已经发送
const [isSent, setIsSent] = useState(false);
}
虽然上述的代码可以工作,不过却留下了 不可能 状态的可能,就是 isSending=true
, isSent=true
, 让组件处在矛盾的状态中。正确的用法是:
export function FeedbackForm() {
const [val, setVal] = useState('');
const [status, setStatus] = useState('typing');
const isSending = status === 'sending';
const isSent = status === 'sent';
}
isSending
, isSent
可以从状态中派生,不再是组件状态,从而保证他们不会不一致。
避免重复的状态
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(items[0]);
// 修改 items,可能会导致 selectedItem 与 items 不一致。
}
正确的方法是 item
只存储在 items
中,选中的状态中只有一个 id。
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);
// 选中的数据应该只使用 id
const selectedItem = items.find(item => item.id == selectedId);
}
避免深入嵌套的状态
在树形结构中,使用 id 引用子数据,全局数据中存储 KV 结构。例如
const tree = {
0: {
id: 0,
name: 'root',
children: [1, 2 ,3]
},
1: {
id: 1,
name: 'child_1',
children: []
},
2: {
id: 2,
name: 'child_2',
children: []
},
3: {
id: 3,
name: 'child_3',
children: []
}
};
方便数据的组织和更新。
后记
在实际项目中,笔者似乎很少有这些思考。这些状态组织的原则,应该会对代码的组织有一些指导作用。值得在实际项目中实践。
网友评论