如何用useReducer实现useState(附代码)

357 阅读4分钟

在 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钩子暴露了两种状态管理的机制:useStateuseReducer 。有趣的是,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

所以我们新的基于useReduceruseState 将需要支持所有这些参数变化。

useState的返回值

当你调用useState ,它会返回状态和一个更新该状态的机制(通常称为 "状态更新函数")。那个函数可以用新的状态来调用,也可以用一个接受先前状态并返回新状态的函数来调用。所以我们新的基于useReduceruseState 将需要支持这两种变化。

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 的所有功能。

基于useReduceruseState 实现

这里是我们的起点:

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上玩玩这个。

Edit useState implemented by useReducer

祝你好运