从这节起咱们开始学习 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
的模式抽离出来,让它变得更加通用。