redux1 - 20行代碼實現redux

手寫實現redux,react-redux的api,中間件原理

redux源碼參考
準備工作

首先建一個React的腳手架項目, 刪除src下的所有東西,保留一個空的 index.js

當前目錄
index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src/ index.js

// 空 的js

需求1:js 實現渲染(風影嘎拉)三個字到 div 容器中

let state = {
  name: '風影嘎拉',
  color: '#f00',
}
function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}
render(state)

啓動服務:
在這裏插入圖片描述

當前的設計呢,存在諸多問題, 比如 state 變量誰都可以更改,不安全, 而每次更改時,不同的人可能會更改不同的值,
那麼,是不是可以通過派發動作的方式,來改變具體的屬性, 比如只改變名字, 只改變顏色, 接着下邊改進

  1. 改成通過派發一個動作,讓狀態改變,一個動作幹一件事
let state = {
  name: '風影嘎拉',
  color: '#f00',
}

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

// 添加動態改寫邏輯
const CHANGE_NAME = 'CHANGE_NAME'
const CHANGE_COLOR = 'CHANGE_COLOR'
function dispatch(action) {
  switch (action.type) {
    case CHANGE_NAME:
      state.name = '手鞠'
      break
    case CHANGE_COLOR:
      state.color = action.color
      break
    default:
      break
  }
}
// 派發動作改變名字
dispatch({ type: CHANGE_NAME })
//派發動作改變顏色, 並傳參
dispatch({ type: CHANGE_COLOR, color: 'green' })
render(state)

在這裏插入圖片描述
雖然改進了,一個動作做一件事, 但還是那個問題,全局的state 變量可以隨意更改,
比如說,我可以手動把state 的值置位 null, 這顯然是不合理的, 我希望變量能私有化

變量私有化, 可以採用 IIFE 函數(明顯此處不合適), 也可以採用函數閉包的形勢

創建一個 createStore 方法, 用來創建數據源

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

// 添加動態改寫邏輯
const CHANGE_NAME = 'CHANGE_NAME'

/**
 *
 * 進行變量私有化
 * 函數,閉包
 */
function createStore() {
  let store = {
    name: '風影嘎啦',
    color: '#f26',
  }
  // 獲取狀態
  function getState() {
    return store 
  }
  // 派發動作改變數據源
  function dispatch(action) {
    switch (action.type) {
      case CHANGE_NAME:
        store.name = '手鞠'
        break
      default:
        break
    }
  }
  // 導出
  return {
    getState,
    dispatch,
  }
}
// 創建 store 數據源
let store = createStore()

// 派發 action 改變名字 
store.dispatch({
  type: CHANGE_NAME,
})
// 調用 store.getState() 獲取數據源
render(store.getState())

在這裏插入圖片描述
到這裏,前邊的問題算是解決了, 但是又有一個新問題了,狀態外部無法改變,內部直接寫死了,簡單畫個草圖
在這裏插入圖片描述
此時,A, B, C 三個組件,可能每個組件都有自己的特有的數據源,但此時的數據源可能只適合 A 組件
所以,截下來要解決的問題就是,能動態的數據源, 固定的輸入,得到固定的輸出

  1. 在 createStore() 方法時,傳入一個純函數
  2. 解決數據狀態由用戶自己具體指定
  3. 固定的輸入,得到固定的輸出
function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

/**
 *
 * 進行變量私有化
 * 函數,閉包
 */

function createStore(reducer) {
  // 默認初始狀態
  let state
  // 得到總的狀態樹
  function getState() {
    return state
  }
  // 派發動作
  function dispatch(action) {
    // 得到新狀態
    state = reducer(state, action)
  }
  // 由於默認 state 樹是沒有狀態的, 是一個控制,所以先派發一次action,給初始狀態賦值,
  // 由於用戶自己定義的reducer 默認返回初始狀態,由於派發的這個type類型找不到,所以默認返回了初始值
  dispatch({ type: '@@REDUX_INIT' })
  return {
    getState,
    dispatch,
  }
}

// 初始狀態
let initState = {
  name: '我愛羅',
  color: '#f26',
}

// 添加動態改寫邏輯
const CHANGE_NAME = 'CHANGE_NAME'
// 狀態用戶自己定義,傳入老的狀態,返回新狀態
function reducer(state = initState, action) {
  switch (action.type) {
    case CHANGE_NAME:
      return {
        ...state,
        name: action.name,
      }
    default:
      return state
  }
}
// 得到數據源
let store = createStore(reducer)
// 派發action動作
store.dispatch({
  type: CHANGE_NAME,
  name: '7代目止水',
})

render(store.getState())

在這裏插入圖片描述
此時,基本實現了簡化版redux, 然而還有一個問題, 就是如果你先渲染,然後再派發動作的話, 視圖是不會改變的,你只能先派發action , 纔會立馬更新視圖

let store = createStore(reducer)
// 如果在這不會視圖更新
render(store.getState())
store.dispatch({
  type: CHANGE_NAME,
  name: '手鞠',
})
// 如果先派發動作,視圖會更新
// render(store.getState())

那如果我就想先渲染更新,接着再渲染後,繼續提交action,還要引起視圖的更新,就像上邊代碼一樣,先渲染,再render

此時可以使用發佈訂閱模式,來監聽你的改變
一個簡單的發佈訂閱模式:
在這裏插入圖片描述
接着實現發佈訂閱,那既然可以訂閱, 就可以取消訂閱

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

/**
 *
 * 進行變量私有化
 * 函數,閉包
 */

function createStore(reducer) {
  // 默認初始狀態
  let state
  let listeners = [] // 存儲所有的監聽函數
  // 得到總的狀態樹
  function getState() {
    return state
  }
  // 派發動作
  function dispatch(action) {
    // 得到新狀態
    state = reducer(state, action)
    // 訂閱
    listeners.forEach((fn) => fn())
  }
  // 由於默認 state 樹是沒有狀態的, 是一個控制,所以先派發一次action,給初始狀態賦值,
  // 由於用戶自己定義的reducer 默認返回初始狀態,由於派發的這個type類型找不到,所以默認返回了初始值
  dispatch({ type: '@@REDUX_INIT' })
  // 添加發布訂閱模式
  function subscribe(listener) {
    listeners.push(listener)
    // 返回一個取消訂閱方法(就是個高階函數)
    return function () {
      listeners = listeners.filter((fn) => fn !== listener)
    }
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}
let app = {
  name: '我愛羅',
  color: '#f26',
}

// 添加動態改寫邏輯
const CHANGE_NAME = 'CHANGE_NAME'
const CHANGE_COLOR = 'CHANGE_COLOR'

function reducer(state = app, action) {
  switch (action.type) {
    case CHANGE_NAME:
      return {
        ...state,
        name: action.name,
      }
    case CHANGE_COLOR:
      return {
        ...state,
        color: action.color,
      }
    default:
      return state
  }
}

let store = createStore(reducer)

// 必選先派發action , 再去觸發渲染,不能先渲染,再派發動作
/**
 * 解決辦法: 添加發布訂閱模式
 */
const AppRender = () => render(store.getState())

// 先調用渲染一次
AppRender()

let unSubscribe = store.subscribe(AppRender)

// 模擬延時渲染
setTimeout(() => {
  store.dispatch({
    type: CHANGE_NAME,
    name: '手鞠',
  })
  // 取消訂閱, 下邊的不會在執行了
  // unSubscribe()
  store.dispatch({
    type: CHANGE_COLOR,
    color: 'blue',
  })
}, 1500)

最後總結代碼也就20多行吧,暴露出3個api

function createStore(reducer) {
  let state
  let listeners = []
  function getState() {
    return state
  }
  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach((fn) => fn())
  }
  dispatch({ type: '@@REDUX_INIT' })
  function subscribe(listener) {
    listeners.push(listener)
    return function () {
      listeners = listeners.filter((fn) => fn !== listener)
    }
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章