redux学习理解03

redux-study

Posted by EWL on December 13, 2018

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);
}

再运行代码: image.png

第一个红框表示首次渲染,第二个红框对应于第一次修改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);

上述代码执行之后,结果如下:

image.png

虽然state已经被修改了,但是界面却没有变更,那我们接下来就一步一步查找为什么state更新了但是界面仍然不变。 首先,我们可以先看看我们到底是怎么去更新state的。在stateChanger中我们传入并直接修改了state,回想一下js中直接修改对象会导致什么结果 示例:

let a = { b: 12, c: 13 };

let a01 = a;
a01.b = 17;

a01 === a;

运行结果:

image.png

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;
}

运行结果: image.png

我将运行结果划分为三次,第一次初始化时我们并不传入oldState,必然state !== {},此外第二次和第三次都对应着后来的两次dispatch修改,但是呢,每一次都完美通过newState === oldState的监测,这说明了我们的猜想是正确的。那究竟要怎么做才能让两次的state来自不同的引用呢? es6给出了答案

let obj1 = { a: 12, b: 13 };

let obj2 = { ...obj1, b: 14 };

obj1 === obj2;

image.png

此时就满足了我们所需要的两次更改之后,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的展开符号,我们成功得到了新的对象引用。优化结果如下: image.png

到此我们也完成了对于重复更新的优化。 本节学习的完整代码如下:

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);