Redux學習手冊

扶牆哥發表於2018-08-18

前言

我們在學習react技術棧中,不可避免的會去接觸學習redux,然而redux確是一個難點,它不同於之前學習原生js或者是react.js那樣,直接寫業務邏輯就能解決問題。而是更多的體現在一種架構思想上,什麼時候用redux,為什麼要用redux,以及如何去用redux。本文將用案例加說明的方式引導學習。

·需求基礎

react.js+es6 你需要有這兩個基礎,如果沒有,可以學習redux用法和思想,因為下文案例將採用react來開發。

1.什麼是redux?

redux是一個狀態管理庫,它提供一種可控管理狀態的方法,它能夠幫你管理程式碼中的那些看起來很糟糕的狀態,狀態只是一些變數,那麼這些變數自然有無數種方法去改變,你可以將這些變數看做你的孩子,你可以想象一下當你的孩子過多,幾十個?上百個?而何況這些孩子還有可能朝著各種歪瓜裂棗的方向變化著。。。

redux正是基於這種情況,給狀態限制在了一個框架中,任何不“正確的方式”將無法去修改,並且所有允許修改的方法,都可見且便於管理。

2.什麼時候用redux?

如果你覺得不用redux很累,我想你會主動用的。

3.redux詳解

在使用 Redux 之前,你必須要謹記它的三大原則:單一資料來源、state 是隻讀的和使用純函式執行修改。

3.1單一資料來源

整個應用的 state 都被儲存在一棵樹中,並且這棵狀態樹只存在於唯一一個 store 中。
複製程式碼

這使得來自服務端的 state 可以輕易地注入到客戶端中;並且,由於是單一的 state 樹,程式碼除錯、以及“撤銷/重做”這類功能的實現也變得輕而易舉。

3.2只讀的 state

唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。
複製程式碼

這就表示無論是使用者操作或是請求資料都不能直接修改 state,相反它們只能通過觸發 action 來變更當前應用狀態。其次,action 就是普通物件,因此它們可以被日誌列印、序列化、儲存,以及用於除錯或測試的後期回放。

3.3使用純函式執行修改

函式在有相同的輸入值時,產生相同的輸出

函式中不包含任何會產生副作用的語句

在這裡,reducer 要做到只要傳入引數相同,返回計算得到的下一個 state 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變數修改,只進行單純執行計算。

3.4redux結構

請看我隨手畫的草圖。

Redux學習手冊
我們通過action發起修改state的請求,action通過自身的type屬性找到reducer裡對應的方法,reducer則經過一系列操作返回一個新的state物件,通過以舊換新將舊的state取代。

(1)state(狀態/資料)

state就是我們定義的狀態。 我們只需要瞭解兩點

1 .state存在於store中,並且state與store是一對一的關係,即單一的資料來源state存在唯一的store中。

2 .state只能通過發起action去修改。

(2)action(修改state的唯一方法)

//新增
export function addtodo(todo) {
  return{
      type:"ADDTODO",
      todo
  }
}
複製程式碼

這是一個簡單的進行新增的action

1.action是一個物件,用於描述修改state的一些行為。

2.內部的第一個屬性type,用於指定進行何種操作,對應的操作存在於reducer中,它的作用類似於指路牌。

2.第二個屬性,todo是一個引數,它可以傳入任何值,類似於形參,我們將會把它傳入到reducer中去使用,用於動態去修改state。

(3)reducer (執行action的地方)

reducer裡整合了我們將要對state所做的各種操作,這裡不要將它於action混淆,action只是指引了對state的操作,而reducer則是操作的具體實現。

export default function active(state="all",action) {
  switch (action.type){
      case "UPDATEACTIVE":
         return action.todo
      default:
          return state;
  }
}
複製程式碼

這是一個最簡單的reducer結構,裡面甚至只有一個action方法。reducer中有兩個引數,除此之外,我們還需要了解reducer的一些特性。

1.第一個引數,代表著我們我們所定義的狀態,在這裡我們將state引入,並不是用於我們將要對state進行操作,reducer中的純函式的特性也不允許我們去對state進行可能修改的操作。我們引入state是為了獲取state的值,以便於我們組成新的值。

2.第二個引數,action,這就是我們傳入的action,我們獲取action.type決定執行何種方法,獲取action.todo(todo的命名不固定)決定將如何去修改state。

3.永遠不要去試圖在reducer中修改state,這是不可取,也是不規範的。(如果直接修改,那要redux幹嘛?) 所有直接修改state,或者可能對state造成影響的操作都被禁止,(我們所做的僅僅是克隆一個跟state長得比較像的資料進行返回取代state)

4.reducer並不一定是對整個state結構進行操作,如果state的結構樹比較深,我們可以將reducer拆分,分別對部分state進行操作,最後只需要使用Redux 提供的 combineReducers() 工具類進行合併即可

import {combineReducers} from "redux"
import todos from "./todos"
import active from "./active"

const reducer = combineReducers({
    todos,
    active
})
export default reducer;
複製程式碼

(4)store

Store 用來存放整個應用的 state,並將 action 和 reducer 聯絡起來。它主要有以下幾個職能:

儲存整個應用的 state,它提供了以下三個api介面

1. getState() 方法獲取 state

2.dispatch(action) 方法更新 state

3.subscribe(listener) 來註冊、取消監聽器

import {createStore,applyMiddleware} from "redux"
import reducer from "../reducers/App"

const state = {
   todos:[
       {content:"redux學習",flag:false},
   ],
   active:"all"
}
const store=createStore(reducer,state);
export default store;
複製程式碼

4.具體案例(選看)

Redux學習手冊

這是一個簡單的案例,雖然簡單,但內部具體邏輯功能有將近十幾個,十分適合新人學習。

具體案例可以去todo官網檢視---------> todo

我們新建一個基於react的專案 (使用 npm i create-react-app myapp 生成)

然後我們修改專案結構為如下。

Redux學習手冊

具體的html,css程式碼,以及react的元件劃分就不詳解了,不是本文重點。

另外在react中使用redux需要安裝這兩個依賴庫。redux和react-redux

Redux學習手冊

1.編寫state

const state = {
    todos:[
        {content:"redux學習",flag:false},
    ],
    active:"all"
}
複製程式碼

可以看到,我們的state物件中有兩條屬性,第一個todos就是我們的內容資料,其中有兩個屬性,第一個content是集體任務內容,第二個flag是定義任務的完成狀態的,false為未完成。

第二個暫時不用(用來定義顯示何種內容的)

2.編寫action

src/action/todo.js

//新增任務
export function addtodo(todo) {
    return{
        type:"ADDTODO",
        todo
    }
}
//刪除任務
export function deletetodo(todo) {
    return{
        type:"DELETETODO",
        todo
    }
}
//更新內容
export function updatecontent(todo) {
    return{
        type:"UPDATECONTENT",
        todo
    }
}
export function clear(todo) {
    return{
        type:"CLEAR",
        todo
    }
}
export function updateflag(todo) {
    return{
        type:"UPDATEFLAG",
        todo
    }
}
export function updateallflag(todo) {
    return{
        type:"UPDATEALLFLAG",
        todo
    }
}
export function updateactive(todo) {
    return{
        type:"UPDATEACTIVE",
        todo
    }
}
複製程式碼

前三個我加了註釋的action是本次案例講解的三個方法,由於我事先已經將其餘功能寫完,為了不報錯,就先不刪除了,只需要關注前三個功能即可。

2.編寫reducer

export default function todos(state=[],action) {
    switch (action.type){
        //新增。
        case "ADDTODO":
            return [...state,{content:action.todo,flag:false}]
        //刪除todo
        case "DELETETODO":
            var index=state.findIndex((item,index)=>index==action.todo)
            var arr=[]
            var j=0
            for (var i=0;i<state.length;i++){
                if (i!=index){
                    arr[j]=state[i]
                    j++
                }
                else {
                }
            }
            return arr
        //修改content
        case "UPDATECONTENT":
            var arr=[...state]
            var index=state.findIndex((item,index)=>index==action.todo.keyid)
            arr[index].content=action.todo.content
            return arr
        //統計未完成的任務(此任務不需要修改狀態,所以不用寫方法)
        case "CLEAR":
            var arr=[]
            var j=0
            for (var i=0;i<state.length;i++){
                if (state[i].flag==false){
                    arr[j]=state[i]
                    j++
                }
            }
            return arr
        //單選更新狀態
        case "UPDATEFLAG":
            var index=state.findIndex((item,index)=>index==action.todo)
            var newflag=state[index].flag==true?false:true
            var arr=[...state]
            arr[index].flag=newflag
            return arr
        //全選更新
        case "UPDATEALLFLAG":
            var arr=[...state]
            for (var i=0;i<arr.length;i++){
               arr[i].flag=action.todo
            }
            return arr
       
        default:
            console.log('進錯物件啦')
            return state;
    }
}
複製程式碼

我將reducer進行了拆分這個程式碼是對todos內容的操作,todos是一個陣列結構,所以我們只需要返回一個同樣的陣列結構即可。

2.1新增todos

我們已知在reducer中無法使用push等修改引數的方法。那我們如何在一個數字中新增資料呢?

這裡我們就需要用到es6中的擴充套件運算子了 (在reducer中擴充套件運算子是老顧客)

既然[...state]=state 那麼[...state,{content:action.todo,flag:false}]就相當於在state中新增了一條資料。我們直接返回即可。

我們來看看在addtodo元件中呼叫這個方法

 add(e){
        let content=e.target.value.trim();
        if(content){
            if(e.keyCode==13)
            {
                if(content){
                    this.props.add.addtodo(content)
                    e.target.value=""
                }
            }

        }
    }
複製程式碼

可知當我們按下回車是,會將input中的內容作為引數呼叫此方法。當然,我這兒貼出的只是核心程式碼,集體元件的使用可以自己去操作。 我們來看看效果

Redux學習手冊

好像還不錯,你也可以繼續新增,例如內容為空無法新增等,按esc放棄新增等。

2.1刪除todos

刪除基本邏輯是點選刪除,呼叫刪除的action,還要傳入我們需要刪除的內容,(注意傳入的引數最好不要是具體的內容,因為同樣的任務,資料很有可能是一樣的,那麼當我們刪除,就有可能刪錯,最好傳入要刪除內容的陣列下標)

deletetodo(){
      setTimeout(()=>this.props.comments.deletetodo(this.props.keyid),1000)
  }
  
deletetodo(){
      this.props.comments.deletetodo(this.props.keyid)
  }
複製程式碼

這兩個程式碼是沒有區別的,使用定時器只是為了普及一個知識,我們在redux中如果不使用中介軟體Middleware來加強store的話,是無法使用非同步操作的,但是我們在react中是可以使用非同步操作的,我們在react中呼叫store中的同步操作,一樣可以實現非同步action操作。

還是直接來看看效果吧

Redux學習手冊

2.1編輯todos

1.編輯功能第一個功能就是單機內容出現編輯框,並且編輯框內容為原來的內容,失去焦點編輯框消失,內容框顯現。 2.修改功能,修改內容為失去焦點時編輯框的內容。

第一個功能既可以使用dom操作來完成,也可以使用新狀態(此狀態為todo元件私有),為了簡單化,就採用dom來實現。 第二個功能則是呼叫更新內容的action

 case "UPDATECONTENT":
           var arr=[...state]
           var index=state.findIndex((item,index)=>index==action.todo.keyid)
           arr[index].content=action.todo.content
           return arr
複製程式碼

我們來回顧之前寫的更新內容的reduce, 可以看到,我們傳入的action.todo,必須包含兩個引數,一個是要修改的資料下標,第二個就是新的內容。

我們直接上todo的失去焦點程式碼

 onblur(e){
      if (e.target.value.trim()==""){
          this.props.comments.deletetodo(this.props.keyid)
      }
      else {
          this.props.comments.updatecontent({ content:e.target.value.trim(),keyid:this.props.keyid})
      }
      var label=document.getElementsByClassName("label")
      var edit=document.getElementsByClassName('edit')
      for(var i=0;i<label.length;i++){
          if (e.target==edit[i]){
              label[i].style.display="block"
          }
      }
      e.target.style.display="none"
  }
複製程式碼

寫了那麼多,只有這一句是核心程式碼,其餘都是邏輯處理this.props.comments.updatecontent({ content:e.target.value.trim(),keyid:this.props.keyid})

老規矩,看看效果。

Redux學習手冊

到這三個功能都講解完了

總結

本文,在前面講解了redux的基本用法,後面則以一個案例的形式應用了redux。

另外,案例雖然使用了react-redux,我在文中卻並沒有講解react-redux的用法,因為redux只是一種架構思想,它可以用在任何地方,並不侷限於react。如果你剛好學習react,那麼你有必要去學習 redux和react-redux。如果是vue,那麼你得去學vuex了。

相關文章