動手實現 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 監聽數據數據狀態被修改了,而且進行後續的例如從新渲染頁面的操做。

相關文章
相關標籤/搜索