最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面试官叫我手写 Redux - 1

    正文概述 掘金(方应杭)   2021-03-21   527

    学生:方,面试官叫我手写 Redux

    方:电话里让你口述 Redux?

    学生:是线下面试,给我电脑问我能不能写。我写不出来,老尴尬了

    方:这岗位工资给多少钱啊?敢出这么硬的题

    学生:20k~30k

    方:那还算合理

    学生:唉,看来我是值不了这么多钱了

    方:别灰心,其实很简单,我跟你讲一遍你就会写了

    学生:真的?你讲讲呗

    方:你如果想要理解一个库,最好就是先自己写一个类似的库,然后把自己的代码跟它的代码做对比。

    学生:我自己写也不会啊……

    方:你应该先问自己,Redux 要解决的问题是什么

    学生:我看看 Redux 官网(10秒钟后)官网说 Redux is a predictable state container for JavaScript apps.

    方:没错,Redux 说自己是一个可预测的状态容器。

    学生:不懂……

    方:没关系,我们先从「状态」开始,我会从零开始构建一个库,并逐渐告诉你如何优化:

    面试官叫我手写 Redux - 1

    App 有三个儿子,以及一个 AppState,现在想把 appState 分享给所有「大儿子」里的 User 组件,我们很容易想到使用 Context,对吧

    面试官叫我手写 Redux - 1

    想要在大儿子里的 User 组件里显示 appState.user.name

    学生:嗯,需要先用 Provider 把整个 App 包起来就行

    方:加三行代码即可,就像这样:

    面试官叫我手写 Redux - 1

    然后到大儿子的子组件 User 里,使用 Consumer:

    面试官叫我手写 Redux - 1

    学生:是不是还可以用 useContext ?

    方:嗯,useContext 会简洁一点:

    面试官叫我手写 Redux - 1

    学生:我喜欢 useContext

    方:现在,我们似乎就已经解决了 appState 共享问题了,对吧?那怎么更新 appState 呢?

    学生:我看你把 setAppState 也放到 contextValue 里了,用它就行吧?

    方:没错,我们来试一试,我在二儿子里新增了一个 UserModifier 组件,里面有个 input 可以修改 appState.user.name

    面试官叫我手写 Redux - 1

    面试官叫我手写 Redux - 1

    学生:你这个代码应该不能修改 user.name 吧

    方:为什么呢?

    学生:因为你传给 setAppState 的还是原来那个对象,虽然你改了里面的 name,但是对象的引用没变

    方:确实,你说的是对的,那我就创建个新对象吧:

    面试官叫我手写 Redux - 1

    运行成功:

    面试官叫我手写 Redux - 1

    学生:等下,56 ~ 58 的代码有问题啊:

    const onChange = (e) => {
      const { appState, setAppState } = contextValue;
      appState.user.name = e.target.value;
      setAppState({ ...appState });
    };
    

    你其实还是修改了之前的 appState,然后为了骗过 setAppState,故意使用了 {... appState} 来创建新对象

    方:这样不行吗?你看代码还是能用的。

    学生:我也说不上有什么问题,但是这样是不推荐的。

    方:那我们得想个办法阻止这种写法,你看这样行不行:我们把创建新 state 的过程封装成一个函数 createNewState(),使用者只用传入参数就能得到新 state:

    面试官叫我手写 Redux - 1

    学生:对哦,这样开发者就算想捣乱也必须去改 createNewState 的源码才行,新手应该没有这么无聊。

    方:为了防止新人手贱,我们把 createNewState 单独放到一个文件里:

    面试官叫我手写 Redux - 1

    学生:这个函数我看着有点眼熟啊,这不就是 reducer 么?

    方:没错,我们的 createNewState 跟 reducer 就两个区别,

    1. 我没有接受 initialState
    2. 我没有把 actionType 和 actionData 合成 action 对象

    另外,这里创建新对象的代码非常繁琐,如果是我来优化的话可能会引入 Immer.js,但由于 Redux 没有做优化,所以我们这里就先不动它。

    我们先把 action 对象搞出来:

    面试官叫我手写 Redux - 1

    于是,所有调用 createNewState 的地方也要改成 {type, payload} 的形式:

    面试官叫我手写 Redux - 1

    学生:原来 reducer 和 action 的来历是这样的啊,是为了统一规范创建新 state 的流程啊

    方:对

    学生:那 dispatch 呢?

    方:别急,我们把上面的代码简化一下:

    面试官叫我手写 Redux - 1

    学生:你只是把两行代码合并成一行了

    方:你看第 59 行的 setAppState(createNewState(appState, .. 这段代码

    学生:这代码怎么了

    方:这段代码将来会被重复无数遍

    学生:什么意思?

    方:现在修改 user.name 要写

    setAppState(createNewState(appState, {
      type: "updateUser",
      payload: {
        name: e.target.value
      }
    }));
    

    下一次你修改 user.age 是不是要写

    setAppState(createNewState(appState, {
      type: "updateUser",
      payload: {
        age: e.target.value
      }
    }));
    

    再下一次你修改 group.name 是不是还要写

    setAppState(createNewState(appState, {
      type: "updateGroup",
      payload: {
        name: e.target.value
      }
    }));
    

    学生:是诶……

    方:那为什么不把 setAppState(createNewState(appState, ... 封装一下呢,就像这样:

    面试官叫我手写 Redux - 1

    学生:对哦,这样简化清爽多了。原来 dispatch 是为了简化和统一 setState 的流程啊

    方:对啥对啊,这段代码有个明显的问题

    学生:什么问题?

    方:updateState 无法读取 context,所以它也不能访问 appState 和 setAppState!

    学生:确实!那怎么办?

    方: 有两个办法:

    • 一是不要把 appState 和 setAppState 放到 Context 里;
    • 二是把 updateState 放在组件里,因为组件里可以读取 context;

    我先讲第二个方法。第一个方法改动有点大,明天再讲。

    学生:第二个办法是把 updateState 放在组件里,但怎么放

    方:语言很难描述,还是直接看代码吧。首先,我们要准备一个空组件,专门用来把 appState 和 setAppState 传给 updateState():

    面试官叫我手写 Redux - 1

    然后,这个 Wrapper 组件会把 updateState 传给 UserModifier,并渲染 UserModifier:

    面试官叫我手写 Redux - 1

    于是,UserModifier 组件就能从 props 里拿到「能访问 Context 的 updateState」了:

    面试官叫我手写 Redux - 1

    学生:那我应该使用 Wrapper 组件而不是 UserModifier 组件咯?

    方:嗯,要把二儿子里的 UserModifier 组件改成 Wrapper 组件。

    学生:让我想想,你为了让 updateState 能访问 Context,故意造了个空组件 Wrapper,然后让 Wrapper 渲染 UserModifier

    方:没错

    学生:原来还能这样。但有个问题,如此一来,我岂不是要给每个组件都套一个 Wrapper 才能拿到 「能访问 Context 的 updateState」?

    方:没错

    学生:你肯定有什么办法消除重复吧?

    方:当然,我们可以写一个 createWrapper 函数:

    面试官叫我手写 Redux - 1

    然后直接把 UserModifier 改写成 createWrapepr(原来的 UserModifier) 的形式:

    面试官叫我手写 Redux - 1

    这样一来,UserModifier 就是原来的 Wrapper 了,直接使用 UserModifier 组件即可:

    面试官叫我手写 Redux - 1

    最后,这个 createWrapper 可以单独提取到另一个文件里。

    学生: 这个 createWrapper 好像 connect 函数啊

    方:没错,它就是 connect,我们来重构一下,把它改名为 connect:

    面试官叫我手写 Redux - 1

    学生:这个 updateState 是不是也可以改名为 dispatch

    方:可以改:

    面试官叫我手写 Redux - 1

    学生:啊,越来越像 Redux 了。dispatch 这里是不是还能接受 state

    方:当然,只需要在 connect 注入 dispatch 的时候,把 appState 也注入即可

    面试官叫我手写 Redux - 1

    这样一来,User 组件也不再需要自己从 Conext 里拿 user 了,直接 connect 一下就能注入 state 了:

    面试官叫我手写 Redux - 1

    学生:connect 确实方便,这就是传说中的高阶组件吗?

    方:是,这些术语其实没什么复杂的,你从 0 开始理解就会觉得很简单

    学生:那,我看 Redux 提供的 connect 是接受两次参数:

    connect(mapState, mapDispatch)(Counter)
    

    这个怎么实现?

    方:这些都是小技巧而已,明天再讲吧,今天就到这里先。目前的代码我发给你,你可以运行看看有什么问题没有。

    学生:好的,我先自己尝试一下。

    待续……


    起源地下载网 » 面试官叫我手写 Redux - 1

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元