上一节 的咱们有了 appState
和 dispatch
:html
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
和 dispatch
。getState
用于获取 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
就不够通用了。咱们但愿用一种通用的方式“监听”数据变化,而后从新渲染页面,这里要用到观察者模式。修改 createStore
:htm
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
监听数据数据状态被修改了,而且进行后续的例如从新渲染页面的操做。