上一節 的咱們有了 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
監聽數據數據狀態被修改了,而且進行後續的例如從新渲染頁面的操做。