据我了解,reducer 除了成为 useState 的语法糖之外,在 React 中不会做任何事情。但实际上它的工作原理并没有什么不同。例如,假设我正在尝试创建一个自定义挂钩:
const update = (state, action) => {
switch (action) {
case 'increment':
return state + 1
break
case 'decrement':
return state - 1
break
default:
break
}
}
const initState = 0
const useWithReducer = () => {
const [state, dispatch] = useReducer(update, initState)
return [state, dispatch]
}
const useWithState = () => {
const [state, setState] = useState(initState)
const dispatch = (action) => setState(update(state, action))
return [state, dispatch]
}
这两件事是一样的。
据我所知,无论
initState
或 update()
有多复杂,reducer 和 state 版本应该始终工作相同。如果我将 initState
更新为 {nestedState: 0}
并相应更新 update ,它仍然可以工作。换句话说,useWithReducer 和 useWithState 在技术上是相同的/做相同的事情。这是正确的吗?对于这些项目在幕后的工作原理,我是否缺少一些东西?我唯一能想到的为什么 useState
可能会更好/不同的是:
const useWithState = () => {
const [state, setState] = useState(initState)
const dispatch = (action) => setState((currentState) => update(currentState, action))
return [state, dispatch]
}
是的,你是对的,你展示的两段代码非常相似。如果我们看一下实现 useState 的源代码,它实际上是 useReducer 代码的包装器。
在第一次渲染时,
useState
创建一个调度函数,它作为索引1返回(然后通常由用户代码分配一个像setFoo
这样的名称):
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any);
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
然后在后续渲染中,useState 仅运行 useReducer 的代码:
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, initialState);
}
我在您的代码中看到的与 React 代码的主要区别在于,您在每个渲染上创建一个新的调度函数,而 React 只执行一次。这可能会意外地破坏记忆。正如您在上一个示例中指出的那样,您还需要确保在最新的状态下进行操作,而 React 是开箱即用的。