React useReducer中Symbol作为action type无法匹配switch/case的原因
React useReducer 使用Symbol作为action type报错原因解析
这个简单的计数器按钮可以正常工作,初始值设为0,每次点击都会递增按钮上的数值:
function Counter() { const INCREMENT = 'INC'; const [state, dispatch] = React.useReducer(reducer, { counter: 0 }); function reducer(state, action) { switch (action.type) { case INCREMENT: return { counter: state.counter + 1 }; default: throw new Error("Unknown action!"); } } return <button onClick={() => dispatch({ type: INCREMENT })}>{state.counter}</button>; }
但如果将INCREMENT常量改为Symbol类型,每次点击按钮都会抛出“Unknown action!”错误:
function Counter() { const INCREMENT = Symbol(); const [state, dispatch] = React.useReducer(reducer, { counter: 0 }); function reducer(state, action) { switch (action.type) { case INCREMENT: return { counter: state.counter + 1 }; default: throw new Error("Unknown action!"); } } return <button onClick={() => dispatch({ type: INCREMENT })}>{state.counter}</button>; }
原因解析
核心原因是React在处理useReducer的dispatch时,会对action对象进行序列化/克隆处理,结合Symbol的特性导致匹配失败:
- Symbol的核心特性是唯一性:每次调用
Symbol()都会生成一个全新的、独一无二的Symbol实例,哪怕是描述符相同的Symbol也不相等。 - 当
INCREMENT定义在组件内部时,组件每次重新渲染(比如点击按钮触发状态更新),都会重新创建这个Symbol实例。 - React在dispatch action时,会对action对象进行内部处理(比如并发模式下的克隆、序列化),导致reducer接收到的
action.type和当前组件内的INCREMENT已经不是同一个Symbol实例。 - 由于Symbol的相等判断基于实例本身,switch语句中的
case INCREMENT永远无法匹配,最终进入default分支抛出错误。
而使用字符串类型时,字符串是值类型,每次渲染时INCREMENT = 'INC'都是同一个值,dispatch的action.type和reducer中的判断值完全相等,所以匹配正常。
解决办法
将Symbol定义在组件外部,确保组件每次渲染时复用同一个Symbol实例:
const INCREMENT = Symbol(); // 定义在组件作用域外部 function Counter() { const [state, dispatch] = React.useReducer(reducer, { counter: 0 }); function reducer(state, action) { switch (action.type) { case INCREMENT: return { counter: state.counter + 1 }; default: throw new Error("Unknown action!"); } } return <button onClick={() => dispatch({ type: INCREMENT })}>{state.counter}</button>; }
内容的提问来源于stack exchange,提问作者Gilles Vanderstraeten
相关产品推荐
相关产品推荐

