前言
我們在學習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結構
請看我隨手畫的草圖。
我們通過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.具體案例(選看)
這是一個簡單的案例,雖然簡單,但內部具體邏輯功能有將近十幾個,十分適合新人學習。
具體案例可以去todo官網檢視---------> todo
我們新建一個基於react的專案 (使用 npm i create-react-app myapp 生成)
然後我們修改專案結構為如下。
具體的html,css程式碼,以及react的元件劃分就不詳解了,不是本文重點。
另外在react中使用redux需要安裝這兩個依賴庫。redux和react-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中的內容作為引數呼叫此方法。當然,我這兒貼出的只是核心程式碼,集體元件的使用可以自己去操作。 我們來看看效果
好像還不錯,你也可以繼續新增,例如內容為空無法新增等,按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操作。
還是直接來看看效果吧
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。
另外,案例雖然使用了react-redux,我在文中卻並沒有講解react-redux的用法,因為redux只是一種架構思想,它可以用在任何地方,並不侷限於react。如果你剛好學習react,那麼你有必要去學習 redux和react-redux。如果是vue,那麼你得去學vuex了。