面试官叫我手写 Redux - 2

方:目前,我们需要整理一下我们的代码,把 appContext.js、connect.js 和 createNewState.js 的代码全整合到 redux.js 里来:

如此一来,App.js 就只用到了两个 API 了:

import { appContext, connect } from "./redux";

学生:接下来做什么呢?

方:优化一下 appContext,来看看它是被如何使用的:

可以看到,appContext 主要是用来初始化 appState 和 setAppState 的。目前我们使用 App 的 state 当做全局 state,其实是有很大的性能问题的

学生:什么问题?

方:我给你演示一下这个 bug,首先,我在三个儿子组件的第一行都添加一个 log

然后,你注意看控制台里的 log:

发现什么问题没有?

学生:有什么问题吗?

方:你没发现有多余的 render 吗?我在「二儿子」里的 UserModifier 调用 dispatch,理论上应该只需要执行「大儿子」函数和「二儿子」函数即可(因为这两个组件的子组件都读取了 user),为什么要执行「幺儿子」函数呢?这属于多余的 render 对吧?

学生:哦,对哦……

方:你知道这意味着什么吗?这意味着我们每次对 state 的微小改动,都会渲染这个 App

学生:因为你调用了 App 的 setAppState 函数对吗?

方:是的,只要调用这个函数(并传给它了一个新的 appState),就必定会触发 App 重新执行,也就必定会导致 App 的所有子组件重新执行,除非给子组件加缓存:

显然,大部分人不会给每个后代组件都加缓存。

学生:那怎么办?

方:问题出在我们「使用 App 的 state 当做全局 state」,可以考虑将 appState 独立出来,于是我在 redux.js 里声明了一个 store,并将其导出:

然后将 store 传给 context 的 value:

如此一来,就消除了对 App 的 state 的依赖了。

学生:那么所有用到 appState 的地方,就都要改成 store.state 了

方:没错,由于我们上节课让 User 和 UserModifier 组件都从 connect 那里获取 state 和 dispatch,所以我们只需要修改 connect 就行了:

学生:这样就可以了吗?

方:完全不行,运行页面你会发现「无法修改 user.name」。因为 store.state 的变化并不能触发任何一个组件更新,React 里想要触发组件更新一般需要调用 setState。

学生:这不有回到原点了吗?我们一开始就是用的 setState

方:我们一开始用的是 App 的 setState,这次我们可以使用 Wrapper 的 setState 呀

学生:可是 Wrapper 怎么知道 store.state 变化了呢?

方:Wrapper 可以订阅 store.state 的变化,我们只需要给 store 添加一个 subscribe 接口即可:

然后,在 setState 运行完毕后,遍历调用所有 fn 即可:

学生:你就用这么几行代码就实现了一个发布订阅?

方:没错,只不过实现得比较简陋而已。接下来我们需要在 Wrapper 里面监听 store.state 的变化:

学生:state 变化了就重新渲染 Wrapper?

方:对,但问题是如何重新渲染 Wrapper?函数组件并没有提供 forceUpdate 接口哦。这里我们要使用一个小技巧:

26 行的 update({}) 就相当于 forceUpdate()

学生:你利用了 {} !== {} 这一特性!

方:聪明,用 symbol 也能达到相同的效果。接下来看看我们的优化成果:

看到没,修改 user.name 时,三个儿子组件全都没有重新执行,只有 User 和 UserModifier 重新执行了。

学生:那也就是说,每次修改 state,只有被 connect 过的组件会重新渲染?

方:对。

学生:诶?那我又有一个问题了,如果一个组件被 connect 过,但是它没有使用 user.name 也会在 user.name 被更新时重新渲染吗?

方:目前是

学生:能优化吗?

方:这还不简单吗?每个 Wrapper 对比一下自己依赖的值是否改变了(用 ref 来存储上一次的值),没变就不要 update({}) 呗……

学生:那每个 Wrapper 怎么知道自己依赖的值是 state.user 还是 state.group 呢?

让 connect 接受一个 selector 即可,如果依赖 user,就给 connect 传一个 state => ({user: state.user});如果依赖 group,就传 state => ({group: state.group})

学生:原来 redux 的 connect 的第一个参数 mapStateToProps是干这个的啊

方:嗯,目前的代码先给你看看 damp-wave-1ufwd - CodeSandbox ,具体怎么实现 selector 下节课再讲

学生:好的!

待续……

饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com