动手实现 Redux(二):抽离 store 和监控数据变化

上一节 的咱们有了 appState 和 dispatchhtml

let appState = {
  title: {
    text: 'React.js 小书',
    color: 'red',
  },
  content: {
    text: 'React.js 小书内容',
    color: 'blue'
  }
}

function dispatch (action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      appState.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      appState.title.color = action.color
      break
    default:
      break
  }
}

如今咱们把它们集中到一个地方,给这个地方起个名字叫作 store,而后构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的集合,这样别的 App 也能够用这种模式了:设计模式

function createStore (state, stateChanger) {
  const getState = () => state
  const dispatch = (action) => stateChanger(state, action)
  return { getState, dispatch }
}

createStore 接受两个参数,一个是表示应用程序状态的 state;另一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是至关于本节开头的 dispatch 代码里面的内容。数组

createStore 会返回一个对象,这个对象包含两个方法 getState 和 dispatchgetState 用于获取 state 数据,其实就是简单地把 state 参数返回。app

dispatch 用于修改数据,和之前同样会接受 action,而后它会把 state 和 action 一并传给 stateChanger,那么 stateChanger 就能够根据 action 来修改 state 了。函数

如今有了 createStore,咱们能够这么修改原来的代码,保留原来全部的渲染函数不变,修改数据生成的方式:spa

let appState = {
  title: {
    text: 'React.js 小书',
    color: 'red',
  },
  content: {
    text: 'React.js 小书内容',
    color: 'blue'
  }
}

function stateChanger (state, action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      state.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      state.title.color = action.color
      break
    default:
      break
  }
}

const store = createStore(appState, stateChanger)

renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
renderApp(store.getState()) // 把新的数据渲染到页面上

针对每一个不一样的 App,咱们能够给 createStore 传入初始的数据 appState,和一个描述数据变化的函数 stateChanger,而后生成一个 store。须要修改数据的时候经过 store.dispatch,须要获取数据的时候经过 store.getState设计

监控数据变化

上面的代码有一个问题,咱们每次经过 dispatch 修改数据的时候,其实只是数据发生了变化,若是咱们不手动调用 renderApp,页面上的内容是不会发生变化的。可是咱们总不能每次 dispatch 的时候都手动调用一下 renderApp,咱们确定但愿数据变化的时候程序可以智能一点地自动从新渲染数据,而不是手动调用。code

你说这好办,往 dispatch里面加 renderApp 就行了,可是这样 createStore 就不够通用了。咱们但愿用一种通用的方式“监听”数据变化,而后从新渲染页面,这里要用到观察者模式。修改 createStorehtm

function createStore (state, stateChanger) {
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    stateChanger(state, action)
    listeners.forEach((listener) => listener())
  }
  return { getState, dispatch, subscribe }
}

咱们在 createStore 里面定义了一个数组 listeners,还有一个新的方法 subscribe,能够经过 store.subscribe(listener) 的方式给 subscribe 传入一个监听函数,这个函数会被 push 到数组当中。对象

咱们修改了 dispatch,每次当它被调用的时候,除了会调用 stateChanger 进行数据的修改,还会遍历 listeners 数组里面的函数,而后一个个地去调用。至关于咱们能够经过 subscribe 传入数据变化的监听函数,每当 dispatch 的时候,监听函数就会被调用,这样咱们就能够在每当数据变化时候进行从新渲染:

const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState()))

renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
// ...后面无论如何 store.dispatch,都不须要从新调用 renderApp

对观察者模式不熟悉的朋友可能会在这里晕头转向,建议了解一下这个设计模式的相关资料,而后进行练习: 实现一个 EventEmitter 再进行阅读。

咱们只须要 subscribe 一次,后面无论如何 dispatch 进行修改数据,renderApp函数都会被从新调用,页面就会被从新渲染。这样的订阅模式还有好处就是,之后咱们还能够拿同一块数据来渲染别的页面,这时 dispatch 致使的变化也会让每一个页面都从新渲染:

const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState()))
store.subscribe(() => renderApp2(store.getState()))
store.subscribe(() => renderApp3(store.getState()))
...

本节的完整代码:

function createStore (state, stateChanger) {
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    stateChanger(state, action)
    listeners.forEach((listener) => listener())
  }
  return { getState, dispatch, subscribe }
}

function renderApp (appState) {
  renderTitle(appState.title)
  renderContent(appState.content)
}

function renderTitle (title) {
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = title.text
  titleDOM.style.color = title.color
}

function renderContent (content) {
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = content.text
  contentDOM.style.color = content.color
}

let appState = {
  title: {
    text: 'React.js 小书',
    color: 'red',
  },
  content: {
    text: 'React.js 小书内容',
    color: 'blue'
  }
}

function stateChanger (state, action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      state.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      state.title.color = action.color
      break
    default:
      break
  }
}

const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState())) // 监听数据变化

renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

总结

如今咱们有了一个比较通用的 createStore,它能够产生一种咱们新定义的数据类型 store,经过 store.getState 咱们获取共享状态,并且咱们约定只能经过 store.dispatch 修改共享状态。store 也容许咱们经过 store.subscribe 监听数据数据状态被修改了,而且进行后续的例如从新渲染页面的操做。

相关文章
相关标签/搜索