当我在做 downshift的时候,我遇到了这样一种情况:我的用户(包括我自己)需要随时将我们正在构建的下拉菜单重置为初始状态:没有输入值、没有高亮显示、没有选择、关闭。但我也有用户希望 "初始状态 "有一些默认的输入、默认的选择,或者保持开放。所以我想出了状态初始化器模式来支持所有这些用例。
状态初始化器模式允许你向用户公开一个API,以便能够将你的组件重置为原始状态,而不必完全卸载和重新安装组件。
实际上,这种模式在某些方面类似于HTML中的defaultValue 。有时候,你的钩子或组件的消费者想要初始化你的状态值。状态初始化器模式允许你这样做。
以此为例:
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
const reset = () => setCount(0)
return (
{count}
Reset
)
}
所以我们的组件有一种方法来初始化它的状态(到0 ),它也支持一种方法来重置状态到那个初始值。
所以这个模式的作用是允许你的组件的外部用户控制这个初始状态值。比如说。如果有人想把计数开始为1 ,他们可能想这样做。
一些实现这种模式的库使用前缀default ,而不是initial ,以匹配来自input 元素的defaultValue 。虽然这是有道理的,但我还是更喜欢前缀initial ,因为我觉得它更清楚地表达了目的和使用情况。
为了支持initialCount ,我们需要做的就是这些:
function Counter({initialCount = 0}: {initialCount?: number}) {
// ^^^ accept the prop with a default value so it's optional
const [count, setCount] = React.useState(initialCount) // <-- pass it to your state
const increment = () => setCount(c => c + 1)
const reset = () => setCount(initialCount) // <-- pass that initialCount value to the reset function
return (
{count}
Reset
)
}
而这里的初始计数为8 。
这是该模式的核心部分。但它缺少一个重要的边缘案例。
如果你的组件的用户在你的组件被安装后改变了initialCount 的值,会发生什么?这不就违背了我们道具名称中 "初始 "部分的目的吗?这里有一个例子,我们计数的消费者在初始挂载后每隔500ms就会改变initialCount 。
点击上面的 "重置 "会将我们的组件重置到与初始状态不同的状态,这可能是一个错误,所以我们希望这是不可能的。多次点击它,每次都会被重置为完全不同的东西。现在,我绝对同意你的观点。这是一个有人错误使用API的例子。但是,如果这不是一个很大的工作,我们也可以让它变得不可能,对吗?
那么,我们怎样才能抓住实际的初始值并忽略对该道具的任何改变呢?我给你一个提示。这并不像useEffect ,用一个isMounted 布尔值或其他东西那么复杂。它实际上是非常简单的。而且我们有几种方法可以做到这一点:
const {current: initialState} = React.useRef({count: initialCount})
const [initialState] = React.useState({count: initialCount})
const [initialState] = React.useReducer(s => s, {count: initialCount})
// actual initial count is: initialState.count
在这些选项中,我更喜欢useRef ,但你做你的,我的朋友。让我们开始吧!这里是将initialCount 设置为2 。
而且,即使有人随机地改变了initialCount 的值,我们的组件也不会在意。
如果你还没有读过《理解React的关键道具》,我建议你现在就快速阅读一下。你读完了吗?很好,让我们继续
我想说的另一件事是,你实际上可以在没有任何API的情况下非常容易地重置一个组件。这是所有组件的一个内置的React API:key prop。只要提供一个key ,并在你想重新初始化组件时将key prop设置为一个新值。这将解除组件的挂载并重新挂载全新的组件。以下是相关代码:
function KeyPropReset() {
const [key, setKey] = React.useState(0)
const resetCounter = () => setKey(k => k + 1)
return
}
function KeyPropResetCounter({reset}) {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return
}
这里是渲染后的结果。
你会注意到,为了支持这个,我们不得不对代码进行了一些调整。在某些情况下,这可能是不可能/不愿意的。
此外,卸载和重新挂载一个组件会有更多的影响(这就是对key 道具的改变所要做的)。例如,在我的高级React模式工作坊中,当状态发生变化时,我们有一个动画。看看key 方法对它的影响。
用按键重置的方法(注意没有动画)
用状态初始化器模式(注意有动画)
另外,卸载和重新挂载组件也会调用useEffect 清理和回调。这可能是你想要的,但也可能不是。
状态初始化器的模式是非常简单的。事实上,在很长一段时间里,我把它从我的高级React模式工作坊中删除了,因为我认为它不值得花时间。但是,在几次没有它的研讨会上,人们开始问我它所解决的问题,所以我又把它加了回来。希望这篇文章对你的工作有所帮助。祝您好运!

