JavaScript 的狀態容器 Redux

ldzsl發表於2021-09-09

Redux

JavasSript 的狀態容器
跟 React 沒有關係,Redux 支援 React、Angular、Ember、JQuery 甚至 JavaScript。

Action、Reducer、Store

圖片描述

Redux

三大原則

  • 單一資料來源

  • State 是可讀的

  • 使用純函式來執行

Action

概念:

  • 記錄了使用者行為的資料的載體

  • Action 是 Store 資料的唯一來源

定義:

  • Action 是一個 JavaScript 物件

  • Action 內有一個 Type 欄位

  • Action 通常被定義為字串常量

  • 儘量減少在 Action 中傳遞的資料

設計 Todo 所需的 Action

var actionAddTodo = {    type: 'ADD_TODO',    text: '吃飯'};var actionCompleteTodo = {  type:'COMPLETE_TODO',  index:2};var actionSelectFilter = {  type:'SETFILTER',  filter:'SHOW_ALL'};

Action 函式

// 建立物件的工廠模式function createAction(text){    var o = new Object();
    o.type = ADD_TODO;
    o.text = text;    return o;    
}function addTodo(text){    return {        type: 'ADD_TODO',
        text
    }
}

State

概要

  • 存放程式資料的一顆 ,或者說是一個資料庫。

  • State 是隻讀的,可能是一個 JavaScript 物件、陣列、Immutable.js 的資料結構

  • 唯一能更新 State 的方法就是觸發 Action ,使用 Store 的 Dispatch 更新 State 。

為什麼強調 State 只讀

  • 單一資料來源,State 是程式的唯一資料來源

  • 確保檢視和網路請求只能表現出我想要修改 State , 然後透過觸發 Action 修改

  • 只有唯一更新 State 方法,我們可以更容易的實現撤銷 / 重做這類應用

設計 State

Todo 的任務列表

var initState = {    filter: 'SHOW_ALL',    todos: []
}

設計 State 注意事項

  • 應該儘量是 State 可以輕鬆的轉化為 JSON

  • 儘可能把 State 規範化,不存在巢狀

  • 把所有資料都放到一個物件裡,每個資料以 ID 作為主鍵

  • 把 State 想象成一個資料庫

//方式一[{  id: 1,  title: 'Some Article',  author: {    id: 1,    name: 'Dan'
  }
}, {  id: 2,  title: 'Other Article',  author: {    id: 1,    name: 'Dan'
  }
}]//方式二{  result: [1, 2],  entities: {    articles: {      1: {        id: 1,        title: 'Some Article',        author: 1
      },      2: {        id: 2,        title: 'Other Article',        author: 1
      }
    },    users: {      1: {        id: 1,        name: 'Dan'
      }
    }
  }
}

Object.assign(target,...source) 函式

把所有的源物件屬性複製到目標物件並放回。

// 用法一var o1 = {a:1};var o2 = {b:2};var o3 = {c:3};var obj1 = Object.assign(o1,o2,o3);console.log(o1); // {a:1,b:2,c:3}console.log(obj1); // {a:1,b:2,c:3}// 用法二var o4 = {a:1,b:2};var o5 = {b:3,c:4};var obj2 = Object.assign({},o4,o5);console.log(obj2); // {a:1,b:2,c:3,d:4}

怎麼使用 Object.assign()

必須保證 Reducer 是一個純函式,我們不能改變傳入的 State,所以我們需要使用 Object.assign({},state)複製一個 State

var state = {filter:'SHOW_ALL',todos:['x1','x2']};var obj3 = Object.assign({},state,{todos:[state.todos[1],'x3']})console.log(obj3)

Reducer

var createStore = Redux.createStore;var combineReducers = Redux.combineReducers;var applyMiddleware = Redux.applyMiddleware;const ADD_TODO = 'ADD_TODO';const COMPLETE_TODO = 'COMPLETE_TODO';const SETFILTER = 'SETFILTER';const FILTER = {    SHOW_ALL: 'SHOW_ALL',    SHOW_COMPLETE: 'SHOW_COMPLETE',    SHOW_ACTIVE: 'SHOW_ACTIVE'}function addTodo(text){    return {        type: ADD_TODO,
        text
    }
}function completeTodo(index){    return {        type: COMPLETE_TODO,
        index
    }
}function selectFilter(filter){    return {        type: SETFILTER,
        filter
    }
}var initState = {    filter:'SHOW_ALL',    todos: []
}function todoApp(state = initState,action){    switch(action.type){        case SETFILTER: return Object.assign({},state,{            filter: action.filter
        });        case ADD_TODO: return Object.assign({},state,{            todos:[...state.todos,{text: action.text,complete: false}]            
        });        case COMPELETE_TODO: return Object.assign({},state,{            todos: return [
                ...state.slice(0, parseInt(action.index),                Object.assign({},state[action.index],{                    completed: true                    
                }),
                ...state.slice(parseInt(action.index) + 1)
            ]
        });        default:            return state;
    }
}

拆分 Reducer

將不存在以來關係的欄位拆分給不同的子 Reducer 管理。
例如 Filter 和 Todos 倆個欄位不存在相互依賴。

function setFilter(state = FILTER.SHOW_ALL,action){    switch(action.type){        case SETFILTER:            return action.filter;        default:            return state;
    }
}function todos(state = [], action){    switch(action.type){        case ADD_TODO:            return [...state, {                text: action.type,                completed: false            
            }];        case COMPLETE_TODO:            return [
                ...state.slice(0,parse(action.index)),                Object.assign({},state[action.index],{                    completed: true
                }),
                ...state.slice(parseInt(action.index) + 1)
            ];        default:            return state;
    }
}

combineReducers 的使用

將每個 Reducer 拼接起來返回一個完整的 state

函式部分原始碼

// reducers -> Object// example -> reducers = { filter: setFilter, todos: todos}function combineReducers(reducers) {    var reducerKeys = Object.keys(reducers) // array -> ['filter','todos']
    var finalReducers = {}    for (var i = 0; i 

使用例子

var todoApp = combineReducers({    filter: setFilter,    todos: todos
})

Store

維持應用所有 State 的一個物件,也可是說一個方法的集合

var store = createStore(todoApp)

Store 的方法

  • getState

  • dispatch 唯一能改變 state 的函式

  • subscribe 增加監聽,當 dispatch action 的時候就會觸發

  • replaceReducer 替換當前用來計算的 reducer

var createStore = Redux.createStore;var combineReducers = Redux.combineReducers;var applyMiddleware = Redux.applyMiddleware;const ADD_TODO = 'ADD_TODO';const COMPLETE_TODO = 'COMPLETE_TODO';const SETFILTER = 'SETFILTER';const FILTER = {  SHOW_ALL:'SHOW_ALL',  SHOW_COMPLETE:'SHOW_COMPLETE',  SHOW_ACTIVE:'SHOW_ACTIVE'}function addTodo(text){  return {    type:ADD_TODO,
    text
  }
}function completeTodo(index){  return {    type:COMPLETE_TODO,
    index
  }
}function selectFilter(filter){    return {        type:SETFILTER, 
        filter
    }
}var initState = {  filter:'SHOW_ALL',  todos:[]
}function todos(state = [], action) {  switch (action.type) {    case ADD_TODO:      return [...state, {        text: action.text,        completed: false
      }];    case COMPLETE_TODO:      return [
        ...state.slice(0, parseInt(action.index)),        Object.assign({}, state[action.index], {          completed: true
        }),
        ...state.slice(parseInt(action.index)+ 1)
      ];    default:      return state;
  }
}function setFilter(state = FILTER.SHOW_ALL,action){  switch(action.type){    case SETFILTER:      return action.filter;    default:      return state;
  }
}var todoApp = combineReducers({  filter:setFilter,    todos:todos
});var store = createStore(todoApp);var unsubscribe = store.subscribe(()=>{  console.log(store.getState());
});console.log('新增吃飯');
store.dispatch(addTodo('吃飯'));console.log('新增睡覺');
store.dispatch(addTodo('睡覺'));console.log('完成吃飯');
store.dispatch(completeTodo(0));console.log('新增打豆豆');
store.dispatch(addTodo('打豆豆'));console.log('完成睡覺');
store.dispatch(completeTodo(0));console.log('setFilter');
store.dispatch(selectFilter(FILTER.SHOW_COMPLETE));
unsubscribe();

小結

  • Action 使用者發起一個動作請求的請求內容

  • State 儲存應用現有的狀態

  • Reducer 根據 Action 請求內容的 Type 欄位去匹配要進行的動作與修改的狀態

  • Store 儲存庫,把 Reducer 傳入 createStore 的構造器中得到,只有透過它的 dispatch 方法傳入一個 Action 請求內容,之後自動去 Reducer 中匹配 Type 欄位,之後去修改相應的狀態



作者:Nodelover
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2325/viewspace-2811829/,如需轉載,請註明出處,否則將追究法律責任。

相關文章