动手实现 Redux(一):优雅地修改共享状态

从这节起咱们开始学习 Redux,一种新型的前端“架构模式”。常常和 React.js 一并提出,你要用 React.js 基本都要伴随着 Redux 和 React.js 结合的库 React-redux。html

要注意的是,Redux 和 React-redux 并非同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你能够把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。前端

若是把 Redux 的用法从新介绍一遍那么这本书的价值就不大了,我大可把官网的 Reducers、Actions、Store 的用法、API、关系重复一遍,画几个图,说两句很玄乎的话。可是这样对你们理解和使用 Redux 都没什么好处,本书初衷仍是跟开头所说的同样:但愿你们对问题的根源有所了解,了解这些工具到底解决什么问题,怎么解决的。react

如今让咱们忘掉 React.js、Redux 这些词,从一个例子的代码 + 问题开始推演。redux

用 create-react-app 新建一个项目 make-redux,修改 public/index.html 里面的 body 结构为:架构

  <body>
    <div id='title'></div>
    <div id='content'></div>
  </body>

删除 src/index.js 里面全部的代码,添加下面代码,表明咱们应用的状态:app

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

咱们新增几个渲染函数,它会把上面状态的数据渲染到页面上:函数

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
}

很简单,renderApp 会调用 rendeTitle 和 renderContent,而这二者会把 appState里面的数据经过原始的 DOM 操做更新到页面上,调用:工具

renderApp(appState)

你会在页面上看到:学习

这是一个很简单的 App,可是它存在一个重大的隐患,咱们渲染数据的时候,使用的是一个共享状态 appState,每一个人均可以修改它。若是我在渲染以前作了一系列其余操做:spa

loadDataFromServer()
doSomethingUnexpected()
doSomthingMore()
// ...
renderApp(appState)

renderApp(appState) 以前执行了一大堆函数操做,你根本不知道它们会对 appState作什么事情,renderApp(appState) 的结果根本无法获得保障。一个能够被不一样模块任意修改共享的数据状态就是魔鬼,一旦数据能够任意修改,全部对共享状态的操做都是不可预料的(某个模块 appState.title = null 你一点意见都没有),出现问题的时候 debug 起来就很是困难,这就是老生常谈的尽可能避免全局变量。

你可能会说我去看一下它们函数的实现就知道了它们修改了什么,在咱们这个例子里面还算比较简单,可是真实项目当中的函数调用和数据初始化操做很是复杂,深层次的函数调用修改了状态是很难调试的。

但不一样的模块(组件)之间确实须要共享数据,这些模块(组件)还可能须要修改这些共享数据,就像上一节的“主题色”状态(themeColor)。这里的矛盾就是:“模块(组件)之间须要共享数据”,和“数据可能被任意修改致使不可预料的结果”之间的矛盾。

让咱们来想办法解决这个问题,咱们能够学习 React.js 团队的作法,把事情搞复杂一些,提升数据修改的门槛:模块(组件)之间能够共享数据,也能够改数据。可是咱们约定,这个数据并不能直接改,你只能执行某些我容许的某些修改,并且你修改的必须大张旗鼓地告诉我。

咱们定义一个函数,叫 dispatch,它专门负责数据的修改:

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

全部对数据的操做必须经过 dispatch 函数。它接受一个参数 action,这个 action是一个普通的 JavaScript 对象,里面必须包含一个 type 字段来声明你到底想干什么。dispatch 在 swtich 里面会识别这个 type 字段,可以识别出来的操做才会执行对 appState 的修改。

上面的 dispatch 它只能识别两种操做,一种是 UPDATE_TITLE_TEXT 它会用 action的 text 字段去更新 appState.title.text;一种是 UPDATE_TITLE_COLOR,它会用 action 的 color 字段去更新 appState.title.color。能够看到,action 里面除了 type 字段是必须的之外,其余字段都是能够自定义的。

任何的模块若是想要修改 appState.title.text,必须大张旗鼓地调用 dispatch

dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

咱们来看看有什么好处:

loadDataFromServer() // => 里面可能经过 dispatch 修改标题文本
doSomethingUnexpected()
doSomthingMore() // => 里面可能经过 dispatch 修改标题颜色
// ...
renderApp(appState)

咱们不须要担忧 renderApp(appState) 以前的那堆函数操做会干什么奇奇怪怪得事情,由于咱们规定不能直接修改 appState,它们对 appState 的修改必须只能经过 dispatch。而咱们看看 dispatch 的实现能够知道,你只能修改 title.text 和 title.color

若是某个函数修改了 title.text 可是我并不想要它这么干,我须要 debug 出来是哪一个函数修改了,我只须要在 dispatch的 switch 的第一个 case 内部打个断点就能够调试出来了。

原来模块(组件)修改共享数据是直接改的:

咱们很难把控每一根指向 appState 的箭头,appState 里面的东西就没法把控。但如今咱们必须经过一个“中间人” —— dispatch,全部的数据修改必须经过它,而且你必须用 action 来大声告诉它要修改什么,只有它容许的才能修改:

咱们不再用担忧共享数据状态的修改的问题,咱们只要把控了 dispatch,全部的对 appState 的修改就无所遁形,毕竟只有一根箭头指向 appState 了。

本节完整的代码以下:

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

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
}

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

下一节咱们会把这种 dispatch 的模式抽离出来,让它变得更加通用。

相关文章
相关标签/搜索