关于 useState 的一些须知
1:功能更新程序
2:惰性初始化器
3:更新救援
4. 便利性过载
5:实现细节
结论
笔记:
我的博客上有一些可互动的示例,因此您在那里阅读可能会有更好的体验:
React.useState使用起来非常简单。一个值、一个 setter 函数和一个初始状态。那么,它背后还有什么隐藏的宝藏值得我们去了解呢?好吧,以下 5 件你可能不知道却能让你日常受益的事情:
1:功能更新程序
React 类组件中的旧版setState有这个功能, useState也有:函数式更新器!除了将useState中获取的新值传递给 setter 之外,我们还可以传递一个函数。React 会调用该函数并返回previousValue,这样我们就可以根据它计算出新的结果:
const [count, setCount] = React.useState(0)
// 🚨 depends on the current count value to calculate the next value
<button onClick={() => setCount(count + 1)}>Increment</button>
// ✅ uses previousCount to calculate next value
<button onClick={() => setCount(previousCount => previousCount + 1)}>Increment</button>
这可能完全不相关,但在某些情况下也可能会引入细微的错误:
多次调用同一个 setter
例子:
function App() {
const [count, setCount] = React.useState(0)
return (
<button
onClick={() => {
setCount(count + 1)
setCount(count + 1)
}}
>
🚨 This will not work as expected, count is: {count}
</button>
)
}
每次点击只会增加一次计数,因为两次调用setCount闭包时都使用了同一个值 ( count )。需要注意的是,setCount不会立即设置计数。 useState 更新器只会安排一次更新。它的作用是告诉 React:
请将此值设置为新值。
在我们的例子中,我们两次告诉 React 同一件事:
请将计数设置为二
请将计数设置为二
React 确实做到了这一点,但这可能不是我们想要表达的意思。我们想表达的是:
请增加当前值
请(再次)增加当前值
功能更新程序形式确保了这一点:
function App() {
const [count, setCount] = React.useState(0)
return (
<button
onClick={() => {
setCount((previousCount) => previousCount + 1)
setCount((previousCount) => previousCount + 1)
}}
>
✅ Increment by 2, count is: {count}
</button>
)
}
当涉及异步操作时
Kent C. Dodds就此问题写了一篇长文,结论如下:
任何时候我需要根据先前的状态计算新状态,我都会使用函数更新。
— 肯特·C·多兹
我赞同这个结论并鼓励你仔细阅读该文章。
奖励:避免依赖
函数式更新器形式还可以帮助你避免对useEffect、useMemo或useCallback的依赖。假设你想将一个 increment 函数传递给一个带 memoized 的子组件。我们可以使用useCallback来确保该函数不会频繁更改,但如果我们对count进行闭包,则每次 count 更改时,我们仍然会创建一个新的引用。函数式更新器完全避免了这个问题:
function Counter({ incrementBy = 1 }) {
const [count, setCount] = React.useState(0)
// 🚨 will create a new function whenever count changes because we closure over it
const increment = React.useCallback(() => setCount(count + incrementBy), [
incrementBy,
count,
])
// ✅ avoids this problem by not using count at all
const increment = React.useCallback(
() => setCount((previousCount) => previousCount + incrementBy),
[incrementBy]
)
}
Bonus2:使用 useReducer 切换状态
切换布尔状态值可能你之前做过一两次。根据上面的规则,它变得有点样板化:
const [value, setValue] = React.useState(true)
// 🚨 toggle with useState
<button onClick={() => setValue(perviousValue => !previousValue)}>Toggle</button>
如果您唯一想做的事情是切换状态值,甚至可能在一个组件中多次切换,那么useReducer可能是更好的选择,因为它:
- 将切换逻辑从 setter 调用转移到钩子调用
- 允许您命名切换函数,因为它不仅仅是一个设置器
- 如果多次使用切换功能,可以减少重复的样板
// ✅ toggle with useReducer
const [value, toggleValue] = React.useReducer(previousValue => !previousValue, true)
<button onClick={toggleValue}>Toggle</button>
我认为这很好地表明了 Reducer 不仅适用于处理“复杂”状态,而且您不需要不惜一切代价用它来调度事件。
2:惰性初始化器
当我们将初始值传递给useState时,初始变量始终会被创建,但 React 只会在第一次渲染时使用它。这对于大多数用例来说完全无关紧要,例如,当你传递一个字符串作为初始值时。在极少数情况下,我们需要进行复杂的计算来初始化状态。对于这些情况,我们可以将一个函数作为初始值传递给useState。React 只会在真正需要结果时(即组件挂载时)调用此函数:
// 🚨 will unnecessarily be computed on every render
const [value, setValue] = React.useState(calculateExpensiveInitialValue(props))
// ✅ looks like a small difference, but the function is only called once
const [value, setValue] = React.useState(() => calculateExpensiveInitialValue(props))
3:更新救援
当你调用 updater 函数时,React 并不总是会重新渲染你的组件。如果你尝试将更新值设置为与 state 当前值相同的值,React 会放弃渲染。React 使用Object.is来判断值是否不同。你可以参考以下示例自行查看:
function App() {
const [name, setName] = React.useState('Elias')
// 🤯 clicking this button will not re-render the component
return (
<button onClick={() => setName('Elias')}>
Name is: {name}, Date is: {new Date().getTime()}
</button>
)
}
4. 便利性过载
这一条是写给所有 TypeScript 用户的。useState 的类型推断通常很有效,但如果你想用undefined或null来初始化你的值,你需要显式指定泛型参数,否则 TypeScript 将无法获得足够的信息:
// 🚨 age will be inferred to `undefined` which is kinda useless
const [age, setAge] = React.useState(undefined)
// 🆗 but a bit lengthy
const [age, setAge] = React.useState<number | null>(null)
幸运的是, useState有一个便捷的重载方法,如果我们完全省略初始值,它会将undefined添加到我们传递的类型中。它在运行时也将是undefined,因为完全不传递参数相当于显式传递undefined:
// ✅ age will be `number | undefined`
const [age, setAge] = React.useState<number>()
当然,如果您必须用null进行初始化,那么您需要冗长的版本。
5:实现细节
useState 的底层实现(某种程度上)是通过useReducer实现的。你可以在这里的源代码中看到这一点。Kent C. Dodds 还写了一篇很棒的文章,介绍了如何使用 useReducer 实现 useState。
结论
这五件事中的前三件实际上在我一开始链接的 React 官方文档的 Hooks API 参考中直接提到了 😉。如果你之前不知道这些,现在你知道了!
这些要点你知道多少?请在下方留言⬇️
鏂囩珷鏉ユ簮锛�https://dev.to/tkdodo/things-to-know-about-usestate-5agl