redux的学习实现第三课:纯函数在redux学习中的使用
首先,回顾一下上一节的内容,会发现很多性能上的问题,我们在代码中加入一些console.log进行调试:
function renderTitle(title) {
console.log('render title');
const titleDOM = document.getElementById('title');
titleDOM.innerHTML = title.text;
titleDOM.style.color = title.color;
}
function renderContent(content) {
console.log('render content');
const contentDOM = document.getElementById('content');
contentDOM.innerHTML = content.text;
contentDOM.style.color = content.color;
}
function renderApp(state) {
console.log('render App');
renderTitle(state.title);
renderContent(state.content);
}
再运行代码:
第一个红框表示首次渲染,第二个红框对应于第一次修改store.dispatch({ type: 'CHANGE_TITLE_COLOR', color: 'pink' });
,第三个红框对应于第二次修改store.dispatch({ type: 'CHANGE_CONTENT', text: 'content第二次被修改' });
。此时就有一个疑问了,为什么单单只是修改了content却带上了title的二次渲染,而单单修改了title的时候却带上了content的二次渲染,如何去避免这样重复无意义的修改呢?此时就考虑到我们可以去对比一下两次修改前后的state是否真的不同。
function renderTitle(newTitle, oldTitle = {}) {
if (newTitle === oldTitle) return;
console.log('render title');
const titleDOM = document.getElementById('title');
titleDOM.innerHTML = newTitle.text;
titleDOM.style.color = newTitle.color;
}
function renderContent(newContent, oldContent = {}) {
if (newContent === oldContent) return;
console.log('render content');
const contentDOM = document.getElementById('content');
contentDOM.innerHTML = newContent.text;
contentDOM.style.color = newContent.color;
}
function renderApp(newState, oldState = {}) {
if(newState === oldState) return;
console.log('render App');
renderTitle(newState.title, oldState.title);
renderContent(newState.content, oldState.content);
}
const store = createStore(appState, stateChanger);
// 保存旧state
let oldState = store.getState();
console.log('oldState', oldState);
// 订阅一次renderApp方法
store.subscribe(() => {
// 获取新的state,此时state有可能会变化
let newState = store.getState();
console.log('newState', newState);
renderApp(store.getState(), oldState);
oldState = newState;
});
// 首次渲染页面
renderApp(store.getState());
setTimeout(() => {
store.dispatch({ type: 'CHANGE_TITLE_COLOR', color: 'pink' });
store.dispatch({ type: 'CHANGE_CONTENT', text: 'content第二次被修改' });
}, 2000);
上述代码执行之后,结果如下:
虽然state已经被修改了,但是界面却没有变更,那我们接下来就一步一步查找为什么state更新了但是界面仍然不变。 首先,我们可以先看看我们到底是怎么去更新state的。在stateChanger中我们传入并直接修改了state,回想一下js中直接修改对象会导致什么结果 示例:
let a = { b: 12, c: 13 };
let a01 = a;
a01.b = 17;
a01 === a;
运行结果:
emmmm,这下就知道我们调用dispatch修改了两次appState之后,界面始终不改变的原因了。再次检验,我们在renderTitle里打个断点:
function renderTitle(newState, oldState = {}) {
console.log('title变化了吗?');
if(newState === oldState) return;
console.log('title变化了');
const titleDOM = document.getElementById('title');
titleDOM.innerHTML = newState.title.text;
titleDOM.style.color = newState.title.color;
}
运行结果:
我将运行结果划分为三次,第一次初始化时我们并不传入oldState,必然state !== {},此外第二次和第三次都对应着后来的两次dispatch修改,但是呢,每一次都完美通过newState === oldState的监测,这说明了我们的猜想是正确的。那究竟要怎么做才能让两次的state来自不同的引用呢? es6给出了答案
let obj1 = { a: 12, b: 13 };
let obj2 = { ...obj1, b: 14 };
obj1 === obj2;
此时就满足了我们所需要的两次更改之后,oldState和newState分别指向了不同的引用。
一个小思考: 如果我们不用es6的话,又应该怎么去做呢?这就涉及到了深浅拷贝以及对象引用的问题了,放个链接我的Object常用方法的学习
此时再看修改过的stateChanger:
function stateChanger(state, action) {
switch (action.type) {
case 'CHANGE_TITLE':
return {
...state,
title: {
...state.title,
text: action.text
}
};
case 'CHANGE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
};
case 'CHANGE_CONTENT':
return {
...state,
content: {
...state.content,
text: action.text
}
};
case 'CHANGE_CONTENT_COLOR':
return {
...state,
content: {
...state.content,
color: action.color
}
};
default:
return state
}
}
利用es6的展开符号,我们成功得到了新的对象引用。优化结果如下:
到此我们也完成了对于重复更新的优化。 本节学习的完整代码如下:
let appState = {
title: {
text: '我是标题',
color: 'red'
},
content: {
text: '我是内容',
color: 'blue'
}
};
function renderTitle(newTitle, oldTitle = {}) {
if (newTitle === oldTitle) return;
console.log('render title');
const titleDOM = document.getElementById('title');
titleDOM.innerHTML = newTitle.text;
titleDOM.style.color = newTitle.color;
}
function renderContent(newContent, oldContent = {}) {
if (newContent === oldContent) return;
console.log('render content');
const contentDOM = document.getElementById('content');
contentDOM.innerHTML = newContent.text;
contentDOM.style.color = newContent.color;
}
function renderApp(newState, oldState = {}) {
if(newState === oldState) return;
console.log('render App');
renderTitle(newState.title, oldState.title);
renderContent(newState.content, oldState.content);
}
// createStore
function createStore(state, stateChanger) {
let listeners = [];
const getState = () => state;//初始化/获取state的函数
// 订阅事件
const subscribe = (listener) => {
listeners.push(listener);
};
const dispatch = (action) => {
state = stateChanger(state, action);
//每一次在执行完stateChanger之后就将事件队列里的监听事件执行处罚
listeners.forEach((listener) => listener());
};
return { getState, dispatch, subscribe };
}
function stateChanger(state, action) {
switch (action.type) {
case 'CHANGE_TITLE':
return {
...state,
title: {
...state.title,
text: action.text
}
};
case 'CHANGE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
};
case 'CHANGE_CONTENT':
return {
...state,
content: {
...state.content,
text: action.text
}
};
case 'CHANGE_CONTENT_COLOR':
return {
...state,
content: {
...state.content,
color: action.color
}
};
default:
return state
}
}
const store = createStore(appState, stateChanger);
// 保存旧state
let oldState = store.getState();
// 订阅一次renderApp方法
store.subscribe(() => {
// 获取新的state,此时state有可能会变化
let newState = store.getState();
renderApp(store.getState(), oldState);
oldState = newState;
});
// 首次渲染页面
renderApp(store.getState());
setTimeout(() => {
store.dispatch({ type: 'CHANGE_TITLE_COLOR', color: 'pink' });
store.dispatch({ type: 'CHANGE_CONTENT', text: 'content第二次被修改' });
}, 2000);