實作 Redux(五):reducer


Posted by YongChenSu on 2020-12-14

appState 以及 stateChanger 合併

stateChanger 若沒有傳入 state,會初始化數據。

function stateChanger (state, action) {
  if (!state) {
    return {
      title: {
        text: 'React.js 小書',
        color: 'red',
      },
      content: {
        text: 'React.js 小書內容',
        color: 'blue'
      }
    }
  }
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'UPDATE_TITLE_COLOR':
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state
  }
}

優化 createStore 成一個參數,初始化 state

因為 state 和 stateChanger 合併到一起。

createStore 不再通過參數傳入,而是一個區域變數 let state =null

createStore 內部第一次的 dispatch 導致 state 初始化完成,後續的 dispatch 就是修改數據的行為。

function createStore (stateChanger) {
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = stateChanger(state, action)
    listeners.forEach((listener) => listener())
  }
  dispatch({}) // 初始化 state
  return { getState, dispatch, subscribe }
}


總結

這邊實作出來的 stateChanger 就是 reducer

reducer 就是修改 createStore 參數的名字

reducer 不允許有副作用,不能在裡面操作 DOM、不能發 Ajax 請求,更不能直接修改 state

reducer初始化以及計算新的 state

createStore

createStore,接受一個叫 reducer 的函數作為參數,只接收 stateaction

如果沒有傳入 state 或是 statenull,就會回傳一個初始化的數據。

如果有傳入 state,便可根據 action 來"修改"數據,但其實它沒有修改數據、也不能修改數據,而是透過修改記憶體位置。若不能識別 action,就不會產生新的數據,而把 state 原封不動回傳。

本節完整程式碼

let appState = {
  title: {
    text: 'React.js 小書',
    color: 'red',
  },
  content: {
    text: 'React.js 小書內容',
    color: 'blue'
  }
}

let newAppState = {
  ...appState, // 複製 appState 裡面的內容
  title: { // 用一個新的物件覆蓋原來 title 屬性
    ...appState.title, // 複製原來 title 裡面的內容
    text: '新的 React.js 小書' // 覆蓋 text 屬性
  }
}

let newAppState1 = {
  ...newAppState, // 複製 newAppState 裡面的內容
  title: { // 用一個新的物件覆蓋原來 title 屬性
    ...newAppState.title, // 複製原來 title 裡面的內容
    text: '新的 React.js 小書' // 覆蓋 color 屬性
  }
}

console.log('1. ', newAppState !== newAppState1) // ture
console.log('2. ', appState.title !== newAppState.title) // ture
console.log('3. ', appState.content !== appState.content) // false

// 新增渲染函數
function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 没有傳入,所以加了默認參數 oldAppState = {}
  if (newAppState === oldAppState) return
  console.log('render app...')
  renderTitle(newAppState.title, oldAppState.title)
  renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle) {
  if (newTitle === oldTitle) return
  console.log('render title')
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = newTitle.text
  titleDOM.style.color = newTitle.color
}

function renderContent (newContent, oldContent) {
  if (newContent === oldContent) return
  console.log('render content')
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = newContent.text
  contentDOM.style.color = newContent.color
}

function stateChanger (state, action) {
  if (!state) {
    return {
      title: {
        text: 'React.js 小書',
        color: 'red',
      },
      content: {
        text: 'React.js 小書內容',
        color: 'blue'
      }
    }
  }
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'UPDATE_TITLE_COLOR':
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state // 沒有修改,回傳原來的狀態
  }
}

// createStore
function createStore (reducer) {
  let state = null
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach((listener) => listener())
  }
  dispatch({}) // 初始化 state
  return { getState, dispatch, subscribe }
}

function themeReducer (state, action) {
  if (!state) return {
    themeName: 'Red Theme',
    themeColor: 'red'
  }
  switch (action.type) {
    case 'UPATE_THEME_NAME':
      return { ...state, themeName: action.themeName }
    case 'UPATE_THEME_COLOR':
      return { ...state, themeColor: action.themeColor }
    default:
      return state
  }
}

const store = createStore(themeReducer)

// const store = createStore(appState, stateChanger)
let oldState = store.getState() // 儲存舊的 state
store.subscribe(() => {
  const newState = store.getState() // 數據可能變化,獲取新的 state
  renderApp(newState, oldState) // 把新舊的 state 傳進去渲染
  oldState = newState // 渲染完成後,把新的 newState 變成舊的 oldState,等待下一次重新渲染
}) // 監聽數據變化

renderApp(store.getState()) // first render
store.dispatch({type: 'UPDATE_TITLE_TEXT', text: 'React 小書'}) // 修改內容
store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'blue'}) // 修改標題顏色
// renderApp(store.getState()) // redner again

參考資源


#程式導師實驗計畫第四期 #前端 #React #react book #Redux #reducer







Related Posts

margin 與 padding 差別在哪裡?(2)

margin 與 padding 差別在哪裡?(2)

【HTML & CSS】學習筆記 #02

【HTML & CSS】學習筆記 #02

【Day 2】Docker Image (映像檔) & Dockerfile

【Day 2】Docker Image (映像檔) & Dockerfile


Comments