做者:Dmitri Pavlutin翻译:疯狂的技术宅javascript
原文:https://dmitripavlutin.com/re...前端
未经容许严禁转载java
React 组件内部的状态是在渲染过程之间保持不变的封装数据。 useState()
是 React hook,负责管理功能组件内部的状态。react
我喜欢 useState()
,它确实使状态处理变得很是容易。可是我常常遇到相似的问题:程序员
useState()
的用法是如此简单,那么何时须要 useReducer()
?本文介绍了 3 条简单的规则,能够回答上述问题,并帮助你设计组件的状态。面试
有效状态管理的第一个规则是:segmentfault
使状态变量负责一个问题。
使状态变量负责一个问题使其符合单一责任原则。服务器
让咱们来看一个复合状态的示例,即一种包含多个状态值的状态。微信
const [state, setState] = useState({ on: true, count: 0 }); state.on // => true state.count // => 0
状态由一个普通的 JavaScript 对象组成,该对象具备 on
和 count
属性。多线程
第一个属性 state.on
包含一个布尔值,表示开关。一样,`state.count
包含一个表示计数器的数字,例如,用户单击按钮的次数。
而后,假设你要将计数器加1:
// Updating compound state setUser({ ...state, count: state.count + 1 });
你必须将整个状态放在一块儿,才能仅更新 count
。这是为了简单地增长一个计数器而调用的一个大结构:这都是由于状态变量负责两个方面:开关和计数器。
解决方案是将复合状态分为两个原子状态 on
和 count
:
const [on, setOnOff] = useState(true); const [count, setCount] = useState(0);
状态变量 on
仅负责存储开关状态。一样,count
变量仅负责计数器。
如今,让咱们尝试更新计数器:
setCount(count + 1); // or using a callback setCount(count => count + 1);
count
状态仅负责计数,很容易推断,也很容易更新和读取。
没必要担忧调用多个 useState()
为每一个关注点建立状态变量。
可是请注意,若是你使用过多的 useState()
变量,则你的组件颇有可能就违反了“单一职责原则”。只需将此类组件拆分为较小的组件便可。
将复杂的状态逻辑提取到自定义 hook 中。
在组件内保留复杂的状态操做是否有意义?
答案来自基本面(一般会发生这种状况)。
建立 React hook 是为了将组件与复杂状态管理和反作用隔离开。所以,因为组件只应关注要渲染的元素和要附加的某些事件侦听器,因此应该把复杂的状态逻辑提取到自定义 hook 中。
考虑一个管理产品列表的组件。用户能够添加新的产品名称。约束是产品名称必须是惟一的。
第一次尝试是将产品名称列表的设置程序直接保留在组件内部:
function ProductsList() { const [names, setNames] = useState([]); const [newName, setNewName] = useState(''); const map = name => <div>{name}</div>; const handleChange = event => setNewName(event.target.value); const handleAdd = () => { const s = new Set([...names, newName]); setNames([...s]); }; return ( <div className="products"> {names.map(map)} <input type="text" onChange={handleChange} /> <button onClick={handleAdd}>Add</button> </div> ); }
names
状态变量保存产品名称。单击 Add 按钮时,将调用 addNewProduct()
事件处理程序。
在 addNewProduct()
内部,用 Set
对象来保持产品名称惟一。组件是否应该关注这个实现细节?不须要。
最好将复杂的状态设置器逻辑隔离到一个自定义 hook 中。开始作吧。
新的自定义钩子 useUnique()
可以使每一个项目保持惟一性:
// useUnique.js export function useUnique(initial) { const [items, setItems] = useState(initial); const add = newItem => { const uniqueItems = [...new Set([...items, newItem])]; setItems(uniqueItems); }; return [items, add]; };
将自定义状态管理提取到一个 hook 中后,ProductsList
组件将变得更加轻巧:
import { useUnique } from './useUnique'; function ProductsList() { const [names, add] = useUnique([]); const [newName, setNewName] = useState(''); const map = name => <div>{name}</div>; const handleChange = event => setNewName(e.target.value); const handleAdd = () => add(newName); return ( <div className="products"> {names.map(map)} <input type="text" onChange={handleChange} /> <button onClick={handleAdd}>Add</button> </div> ); }
const [names, addName] = useUnique([])
启用自定义 hook。该组件再也不被复杂的状态管理所困扰。
若是你想在列表中添加新名称,则只需调用 add('New Product Name')
便可。
最重要的是,将复杂的状态管理提取到自定义 hooks 中的好处是:
将多个状态操做提取到化简器中。
继续用 ProductsList
的例子,让咱们引入“delete”操做,该操做将从列表中删除产品名称。
如今,你必须为 2 个操做编码:添加和删除产品。处理这些操做,就能够建立一个简化器并使组件摆脱状态管理逻辑。
一样,此方法符合 hook 的思路:从组件中提取复杂的状态管理。
如下是添加和删除产品的 reducer 的一种实现:
function uniqueReducer(state, action) { switch (action.type) { case 'add': return [...new Set([...state, action.name])]; case 'delete': return state.filter(name => name === action.name); default: throw new Error(); } }
而后,能够经过调用 React 的 useReducer()
hook 在产品列表中使用 uniqueReducer()
:
function ProductsList() { const [names, dispatch] = useReducer(uniqueReducer, []); const [newName, setNewName] = useState(''); const handleChange = event => setNewName(event.target.value); const handleAdd = () => dispatch({ type: 'add', name: newName }); const map = name => { const delete = () => dispatch({ type: 'delete', name }); return ( <div> {name} <button onClick={delete}>Delete</button> </div> ); } return ( <div className="products"> {names.map(map)} <input type="text" onChange={handleChange} /> <button onClick={handleAdd}>Add</button> </div> ); }
const [names, dispatch] = useReducer(uniqueReducer, [])
启用 uniqueReducer
。 names
是保存产品名称的状态变量,而 dispatch
是使用操做对象调用的函数。
当单击 Add 按钮时,处理程序将调用 dispatch({ type: 'add', name: newName })
。调度一个 add
动做使 reducer uniqueReducer
向状态添加一个新的产品名称。
以一样的方式,当单击 Delete 按钮时,处理程序将调用 dispatch({ type: 'delete', name })
。remove
操做将产品名称从名称状态中删除。
有趣的是,reducer 是命令模式的特例。
状态变量应只关注一个点。
若是状态具备复杂的更新逻辑,则将该逻辑从组件提取到自定义 hook 中。
一样,若是状态须要多个操做,请用 reducer 合并这些操做。
不管你使用什么规则,状态都应该尽量地简单和分离。组件不该被状态更新的细节所困扰:它们应该是自定义 hook 或化简器的一部分。
这 3 个简单的规则可以使你的状态逻辑易于理解、维护和测试。