在上一节的代码中,其实存在一个很严重的性能问题。
1 | function renderTitle(title) { |
其他代码保持不变,依旧执行一次初始化渲染,和两次更新,然后打开控制台看下log
前三个是第一次渲染打印出来的。中间三个是第一次 store.dispatch 的结果,最后三个是第二次 store.dispatch 的结果。问题就是后两次的更新都没有改动 content 对象,只是修改了 title 对象。 renderContent 是不需要执行的,这里的操作需要优化。
可以通过在每个渲染函数执行渲染操作之前先做个判断,判断传入的新数据和旧的数据是不是相同,相同的话就不渲染了。
1 | function renderTitle(newTitle, oldTile = {}) { |
然后我们用一个 oldState 变量保存旧的应用状态,在需要重新渲染的时候把新旧数据传进入去:
1 | // 生成 store |
我们的代码现在变成了这样:
1 |
|
打开页面,会发现只执行了第一次渲染,而后面的两次更新根本就不执行了,why???????
我们知道在 JavaScript 函数中,所有的参数都是值传递,参数为基本类型时传递的直接就是值,参数为对象时,参数的值就是对象所在的内存地址空间,看下我们修改 state 的地方:
1 | function stateChanger(state, action) { |
通过上面的代码可以看到,我们只是修改了 state.title
中的 text 和 color 属性,而 title 对象的内存地址依然原来的,state 的内存地址也依然是原来,所以 newState 和 oldState 都是指向同一个内存地址,所以 newAppState === oldAppState
为 true
,所以也就不会触发新的渲染。
怎么办??????
是不是可以通过浅复制生成一个新的对象,然后将修改的部分覆盖到这个新的对象上,这样既可以保证没有被修改的对象内存地址保持不变,而被修改的对象又可以获得新的地址,继而触发渲染。
每次修改某些数据的时候,不去改变原来的数据,而是把需要修改数据对象都 copy 一个出来,然后再去修改新生成的数据。如上图所示,content 对象就可以在不同的阶段进行共享。
根据这个思路,来修改 stateChanger
:
1 | function stateChanger(state, action) { |
每次需要修改的时候都会产生新的对象,并且返回。而如果没有修改(在 default 语句中)则返回原来的 state 对象。
因为 stateChanger 不会修改原来对象了,而是返回对象,所以我们需要修改一下 createStore。让它用每次 stateChanger(state, action) 的调用结果覆盖原来的 state:
1 | function createStore(state, stateChanger) { |
现在的完整代码如下:
1 |
|
另外,并不需要担心每次修改都新建共享结构对象会有性能、内存问题,因为构建对象的成本非常低,而且我们最多保存两个对象引用(oldState 和 newState),其余旧的对象都会被垃圾回收掉。