React的UseReducer Hook踩坑

112 阅读2分钟

背景

前阵子开发一个项目,前端使用了React,并且使用React Route DOM包来搭建前端路由系统,其中一个功能是要允许编辑用户个人信息,编辑功能由一个单独的组件实现,组件初始状态由父组件提供,子组件由react-router-dom包的Route组件渲染,结构如下所示

<Switch>
        <Route exact path="/users/:id/edit" render={({match}) => <EditUser user={state.users.find(d => d.id == match.params.id)} />} />
</Switch>

EditUser组件如下所示

const formFields = ['name', 'address1', 'address2', 'city'];
export default function EditUser({ user }) {
    const { id } = useParams();
    const [ state, dispatch ] = useReducer(reducer, {
        user: user,
        form: formFields.reduce((pv, cv) => Object.assign(pv, {[cv]: {value: user[cv]? user[cv] : '', error: ''}}), {}),
    });
}

由于在Route内部使用了render方法,所以即使传递给子组件EditUser的数据发生变化,它也只会被装载(mount)一次,假设现在我们的url是‘/users/1/edit’,子组件正常显示在页面中,现在我们刷新父组件的值,即state.users值改变后,父组件找不到id为1的用户数据会导致返回的值是undefined,显然,如果我们已经装载了子组件(使用useReducer实现),id为1的用户的数据也已经存在了子组件里面,奇怪的事情来了,浏览器一直给我报错,意思是‘can not find index 'name' of undefined’,导致state.form变量初始化失败,父组件改变了的新值即使传递到子组件不该被渲染啊,因为我使用useReducer,按理说子组件的状态是自己维持的,奇怪了,然后我修改了一下代码,既然你说user undefine,那好,简单粗暴加个值判断,修改后如下

const formFields = ['name', 'address1', 'address2', 'city'];
export default function EditLocation({ user }) {
    const { id } = useParams();
    const [ state, dispatch ] = useReducer(reducer, {
        user: user,
        form: user? formFields.reduce((pv, cv) => Object.assign(pv, {[cv]: {value: user[cv]? user[cv] : '', error: ''}}), {}) : {},
    });
}

果然,不报错了,应用也正常跑起来了,奇了个怪了,而且就算是父组件的值改变了,子组件值也没有变,因为已经被初始化了。所以,结论是,useReducer会接受新的值,但是,并不会改变子组件的本来的值,那干啥还管父组件传进来的值是啥呢,太坑了。。。

结论

useReducer在任何阶段都会接受父组件的值,重新计算state,但是只有在mount阶段会真的将值传递给子组件,后来的阶段有重新计算的过程,但是不会将子组件的值刷新,算着玩的意思?