在 egghead.io上观看 "用useReducer实现useState"。
这里是TL;DR:
const useStateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState
const useStateInitializer = initialValue =>
typeof initialValue === 'function' ? initialValue() : initialValue
function useState(initialValue) {
return React.useReducer(useStateReducer, initialValue, useStateInitializer)
}
想深入了解吗?我们走吧。
但是Kent...为什么?
为了好玩🤓而且我认为重新实现东西是学习它们如何工作的一个好方法。
React的状态管理
React钩子暴露了两种状态管理的机制:useState 和useReducer 。有趣的是,React实际上是用构建useReducer 的相同代码来构建useState 。他们这样做是因为在一个组件中管理一个单一的状态值是非常常见的,但是用useReducer 来做需要一些模板。所以他们通过useState 暴露一个更简单的状态管理API来减少模板。
他们的好处是拥有所有的内部代码来做这件事,但我们自己也可以这样做 😄
useState API
让我们先来看看useState 向我们暴露的API。
useState函数参数。
你可以用三种不同的方式调用useState:
useState() // no initial value
useState(initialValue) // a literal initial value
useState(() => initialValue) // a lazy initial value
所以我们新的基于useReducer 的useState 将需要支持所有这些参数变化。
useState的返回值
当你调用useState ,它会返回状态和一个更新该状态的机制(通常称为 "状态更新函数")。那个函数可以用新的状态来调用,也可以用一个接受先前状态并返回新状态的函数来调用。所以我们新的基于useReducer 的useState 将需要支持这两种变化。
const [state, setState] = useState()
setState(newState)
setState(previousState => newState)
这也类似于useReducer ,只是更新状态的机制被称为 "dispatch "函数,而不是直接用来设置状态,它将实际的状态更新逻辑委托给reducer。
useReducer API
因此,这里是useReducer API:
const [state, dispatch] = React.useReducer(reducerFn, initialValue)
对于useReducer ,如果你想有懒惰的初始化,那么你提供第三个参数,这是你的初始化函数,第二个参数作为该初始化函数的参数,所以你可以把它重命名为类似initialArg:
const initializationFn = initialArg => initialArg
const [state, dispatch] = useReducer(reducerFn, initialArg, initializationFn)
并且记住,reducerFn 是对dispatch 函数所做的事情负责的。因此,如果你想控制状态如何被dispatch函数更新,你可以通过reducerFn 来实现,该函数被调用时,无论dispatch 是用什么调用的:
const reducerFn = (prevState, dispatchArg) => newState
有了这个,我们就可以实现useState 的所有功能。
基于useReducer 的useState 实现
这里是我们的起点:
const useStateReducer = () => {}
function useState() {
return React.useReducer(useStateReducer)
}
让我们开始尝试实现这个用例的状态更新功能:
const [count, setCount] = useState(0)
setCount(count + 1)
所以我们需要让dispatch 函数实际更新状态值。为了做到这一点,我们让我们的reducer获取dispatchArg ,然后返回。
const useStateReducer = (prevState, dispatchArg) => dispatchArg
function useState() {
return React.useReducer(useStateReducer)
}
有了这个,实际上调用dispatchArg newState 来代替更有意义:
const useStateReducer = (prevState, newState) => newState
function useState() {
return React.useReducer(useStateReducer)
}
很好!接下来,让我们支持useState API的函数更新版本:
const [count, setCount] = useState(0)
setCount(previousCount => previousCount + 1)
如果我们想继续支持以前的API,我们需要做一些typeof ,以确定它是否是一个函数,如果它是,我们就用以前的状态来调用它。否则,我们将直接返回它。三元组来拯救我们!
const useStateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState
function useState() {
return React.useReducer(useStateReducer)
}
很好!现在让我们继续讨论这个初始值!对于简单的useState(0),这实际上是非常简单的。
const useStateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState
function useState(initialValue) {
return React.useReducer(useStateReducer, initialValue)
}
就这样了。但如果是懒人版本呢? useState(() => 0) 这就有点棘手了,因为useReducer API在这里略有不同。 让我们先迭代到这个。这里是我们实现非懒惰的useState(0) 用例的另一种方式。
const useStateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState
const useStateInitializer = initialArg => initialArg
function useState(initialValue) {
return React.useReducer(useStateReducer, initialValue, useStateInitializer)
}
在这种情况下,我们把initialValue 作为initialArg ,我们的useStateInitializer 函数只是返回这个值。这使得我们更容易支持API的懒惰初始化版本。我们只需要确定initialArg 是否是一个函数,如果是,我们就调用它,否则我们就返回它。
const useStateReducer = (prevState, newState) =>
typeof newState === 'function' ? newState(prevState) : newState
const useStateInitializer = initialValue =>
typeof initialValue === 'function' ? initialValue() : initialValue
function useState(initialValue) {
return React.useReducer(useStateReducer, initialValue, useStateInitializer)
}
就这样了!
总结
我希望你喜欢和我一起对这些API进行更多的研究。我绝对建议你继续使用内置的useState 钩子,但我认为你会发现,看到useReducer 是多么的灵活。你不必以使用redux的方式来使用它(事实上,你也不必以传统的方式使用redux......或者根本不需要)。
只是为了好玩,如果你想,你可以在CodeSandbox上玩玩这个。
祝你好运