1. 程式人生 > >Redux原理及工作流程和使用方法(四)

Redux原理及工作流程和使用方法(四)

使用Redux目的:

在react中元件與元件之間的通訊很麻煩,於是借用redux進行第三方的通訊
通過把資料儲存在公共區域store裡,實現各個元件間快速通訊

一、Redux結構圖
在這裡插入圖片描述

Redux的三個非常重要的組成部分:

  • action
  • reducer
  • store

action 通知 reducer 修改 state,store 管理 state。

注:store是一個聯絡和管理。具有如下職責

  • 維持應用的state;
  • 提供getState()方法獲取 state
  • 提供dispatch(action)方法更新 state;
  • 通過subscribe(listener)註冊監聽器;
  • 通過subscribe(listener)返回的函式登出監聽器。

各部分的身份
把這個過程比擬成圖書館的一個流程來幫助理解。

  • React Component(借書的人 )
    需要借書的人
  • Action Creator(具體借書的表達)
    想借書的人向圖書館管理員說明要借的書的那句話。
  • Store(圖書館管理員)
    負責整個圖書館的管理。是Redux的核心
  • Reducers(圖書館管理員的記錄本)
    管理員需要藉助Reducer(圖書館管理員的記錄本)來記錄。

工作流程

借書的人(ReactComponent)說了一句話(Action Creator)向圖書館管理員(Store)借一本書,圖書館管理員年紀大了啊記不住啊,便掏出了自己的記錄本(Reducers)。看了看知道了那本書有沒有,在哪,怎麼樣。這樣一來管理員就拿到了這本書,再把這本書交給了借書人。
翻譯過來就是:

元件想要獲取store中的資料State, 用ActionCreator建立了一個請求交給Store,Store藉助Reducer確認了該State的狀態,Reducer返回給Store一個結果,Store再把這個State轉給元件。

二、使用Antd棧(UI頁面佈局)實現TodoList頁面佈局

1、安裝antd棧:

npm install antd --save

2、引入樣式:

import 'antd/dist/antd.css';

3、從 antd 引入相應模組

如:Input, Button, List。。。
//入口檔案:index.js
ReactDOM.render(
    <
TodoList/>, document.getElementById('root') )
//TodoList元件
import React, { Component } from "react";
import { Input, Button, List  } from "antd";//通過antd棧官網引入需要的元件
import 'antd/dist/antd.css';//引入antd棧的樣式檔案

//列表中要顯示的資料
const data = [
    'Racing car sprays burning fuel into crowd.',
    'Japanese princess to wed commoner.',
    'Australian walks 100km after outback crash.',
    'Man charged over missing wedding girl.',
    'Los Angeles battles huge wildfires.',
];


//TodoList
class TodoList extends Component{
    render() {
        return(
            <div style={{marginTop:'10px',  marginLeft:'10px'}}>
                <div >

                    <Input placeholder = 'qq' style = {{width:'300px', marginRight:'10px'}} />{/* 屬性placeholder:預設顯示的值*/}
                    <Button type="primary">提交</Button>
                </div>
                <List
                    style={{marginTop:'10px', width:'300px'}}
                    bordered
                    dataSource={data}
                    renderItem={item => (<List.Item>{item}</List.Item>)}
                />

            </div>
        )
    }
}
export default TodoList;

三、使用Redux

Redux是解決資料傳遞問題的框架,它把資料統一放在store中進行管理。所以在Redux中,store是最重要的一個環節,在編碼中也要先建立store!!!

1、安裝Redux

npm install --save redux

2、建立Store
在src下新建一個資料夾store,然後建立一個index.js檔案,用於存放store中的程式碼:

//首先從redux這個第三方模組引入createStore方法,呼叫這個方法就可以建立一個store
import { createStore } from 'redux';
const store = createStore();//建立store
export default store;//匯出store

3、引出reducer這個身份並建立reducer
仍然通過圖書館的例子進行分析:
store已經有了,但是store這個管理員記不住管理的那麼多資料,需要一個小的記錄本幫助他(store)管理資料,所以在建立store的時候需要將這個記錄本一起傳遞給他(store),這樣這個store才可以知道自己管理的資料

接下來建立這個記錄本(Reducer)
在新建的資料夾store下建立reducer.js檔案

//reducer存放資料
//reducer的內容需要返回一個函式,這個函式接受兩個引數state,action,
//state是整個store儲存的資料
const defaultState = {//初始化資料
    inputValue:'213',
    list:[1,32]
}; 
//reducer可以接收state,但是絕不能修改state
export default (state = defaultState, action) => {
    return state;
}

4、將reducer傳遞給store

./store/index.js

//store的內容
import { createStore } from 'redux';
//引入reducer
import reducer from './reducer'
const store = createStore(reducer);//建立store,並將reducer傳遞給store
export default store;//匯出store

到此store就建立好了,接下來在元件中可以從store中取資料了

5、元件從store中取資料

./src/TodoList .js元件

import React, { Component } from "react";
import { Input, Button, List  } from "antd";//通過antd棧官網引入需要的元件
import 'antd/dist/antd.css';//引入antd棧的樣式檔案
import store from './store'//引入store
class TodoList extends Component{
    constructor(props){
        super(props);
        this.state = store.getState();//獲取store中的資料
    }
    render() {
        return(
            <div style={{marginTop:'10px',  marginLeft:'10px'}}>
                <div >
                    <Input value={this.state.inputValue} placeholder = 'qq' style = {{width:'300px', marginRight:'10px'}} />{/* 屬性placeholder:預設顯示的值*/}
                    <Button type="primary">提交</Button>
                </div>
                <List
                    style={{marginTop:'10px', width:'300px'}}
                    bordered
                    dataSource={this.state.list}
                    renderItem={item => (<List.Item>{item}</List.Item>)}
                />
            </div>
        )
    }
}
export default TodoList;

以上完成了從store中取資料的功能

四、實現改變store中的資料:

在實現改變store中的資料之前先安裝個redux除錯工具(外掛Redux DevTools),通過該外掛可以很方便的對redux進行除錯
問題:
在這裡插入圖片描述

解決:在建立store的方法中增加個引數window.REDUX_DEVTOOLS_EXTENSION && window.REDUX_DEVTOOLS_EXTENSION()方可使用除錯工具:

./store/index.js

import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;

仍以圖書館的例子說明:
假如一個人要去圖書館借書,他要對管理員store說“我要借書”這句話,但是這句話是通過Action(Creators)建立的,首先建立這句話(建立action):
Action是一個物件的形式

  • Type:告訴store要做的事情是什麼?
  • Value:傳過去一個結果

建立好action後,要把這句話傳給store,那如何傳給store呢?
Store提供了一個方法(dispatch),呼叫這個方法就可以將action傳給store了

當input輸入框發生改變的時候,store中對應的值要發生改變!

import React, { Component } from 'react';
import { Input, Button, List } from 'antd';
import store from './store/index';

import 'antd/dist/antd.css';

class TodoList extends Component{

    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleValueChange = this.handleValueChange.bind(this)
    }

    handleValueChange(e){
        //告訴store,輸入的型別和輸入框中的值
        const action = {
            type:'change_inpue_value',
            value:e.target.value
        }
        //把action傳給store, store自動傳給reducer
	store.dispatch(action);
    }
    render(){
        return(
            <div style={{marginTop:'10px', marginLeft:'10px'}}>
                <div>
                    <Input value={this.state.inputValue} placeholder='ww'
                           style = {{width: '300px', marginRight:'10px'}}
                            onChange = {this.handleValueChange}/>
                    <Button  type="primary">提交</Button >
                </div>
                <List
                    style = {{width: '300px', marginTop:'10px'}}
                    bordered
                    dataSource={this.state.list}
                    renderItem={item => (<List.Item>{item}</List.Item>)}
                />
            </div>
        )
    }
}
export default TodoList;

現在已經把action傳遞給了store。但是這個管理員store不知道應該怎麼做,需要去查記錄表(reducer),
那麼如何去查記錄本(reducer)呢?他(stroe)要把當前stroe裡的資料和action一起傳給記錄本(reducer)

很好的一件事情,當store接受到action後,Store會自動的把當前store中儲存的資料(舊資料)和接受的action一起傳給reducer,有reducer去告訴store要去做什麼。

現在reducer就接受到了之前的資料,也拿 到了action這句話,現在就通過reducer來做這件事情,最後將newState返回給了store

export default (state=defaultState,action)=>{    //input   
  if (action.type==='change_input_value'){        
  	//將之前的state做一次深拷貝,到newState,再對newState進行修改
     const  newState=JSON.parse(JSON.stringify(state));//簡單的深拷貝        	
     newState.inputValue=action.value;        
     //返回新資料給store,並將store中之前的資料替換為新的資料,
     //這樣就完成了資料的改變
     return newState;    
  }     
}

Store變化了,元件更新:需要在元件上做個處理
元件監聽store裡面的變化,只要store裡面的資料發生改變,則立即執行subscribe函式裡的函式

constructor(props){
   super(props);
   this.state = store.getState();
   //監聽store裡面的變化,只要一變化
   //只要store裡面的資料發生改變,則立即執行subscribe函式裡的函式
   store.subscribe(this.handleStoreChange)
   this.handleInputValue = this.handleInputValue.bind(this);
}

當store中的資料發生了變化,就會執行函式,從store中重新取一會資料,來跟新state

handleStoreChange=()=>{
  this.setState(store.getState());
   // console.log('store change')
   // 感知store發生變化之後,從store裡獲取最新的資料,然後進行設定
};

五、actionTypes的拆分

在以上程式碼中,action的type定義為一個字串,當這個出現錯誤時,控制檯不會有錯誤提示,對排錯帶來給很大的不變,
這樣我們把這個type抽取出來,放到一個單獨的檔案中,定義為常量,當常量在引用的時候,出項錯誤,控制檯就會有錯誤提示
。即可解決這個問題

定義actionType的常量
src/store/actionType.js:

export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_LIST_ITEM = "add_list_item";

在TodoList的元件中引入定義的常量

...
import { CHANGE_INPUT_VALUE, ADD_LIST_ITEM } from "./store/actionType"
...
handleInputValue(e){
    const action = {
        type : CHANGE_INPUT_VALUE,
        value : e.target.value
    }
    store.dispatch(action);
}
handleBtnClick(){
    const action = {
        type : ADD_LIST_ITEM

    }
    store.dispatch(action);
}
    ...

六、使用actionCreator統一建立action

1、建立actionCreators.js

import { CHANGE_INPUT_VALUE, ADD_LIST_ITEM } from "./actionType"
export const getInputChangeAction = (value) => ({
    type : CHANGE_INPUT_VALUE,
    value
})

2、元件中使用

...
import { getInputChangeAction } from './store/actionCreators'
...
handleInputValue(e){
    const action = getInputChangeAction(e.target.value);
    store.dispatch(action);
}
...

七、整體結構優化

1:優化的目標
1.1:action的type由公共的actionTypes管理

const  action={
    // type:'add_todo_item'
    type:ADD_TODO_ITEM
};

1.2:將action封裝成物件,寫在actionCreator.js檔案裡

//action封裝之前
const  action={    
// type:'add_todo_item'    type:ADD_TODO_ITEM};
// action封裝之後
const action=getAddItemActiom();
store.dispatch(action);
export  const getAddItemActiom=()=>({
    type:ADD_TODO_ITEM,
});

2:目錄結構
在這裡插入圖片描述

3.index.js

import React from 'react';
import ReactDOM from 'react-dom'; 
import TodoList from './TodoList';  
ReactDOM.render(
	<TodoList />, 
	document.getElementById('root')
);

4.TodoList.js

import React ,{Component}from 'react';
import 'antd/dist/antd.css'
import {Input,Button,List} from 'antd'
import store from './store/index';
import {getInputChangeAction,getAddItemActiom,getDeleteItemAction}from './store/actionCreator';//從封裝的actionCreator解構出自定義的函式

class TodoList extends  Component{

    constructor(props){
        super(props);
        this.state=store.getState();
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleItemDelet = this.handleItemDelet.bind(this);
        //監聽store裡面的變化,只要一變化
        // 只要store裡面的資料發生改變,則立即執行subscribe函式裡的函式
        store.subscribe(this.handleStoreChange)
    }
    render(){
        return(
            <div style={{margin:'10px',marginLeft:'10px'}}>
                <div>
                    <Input
                        value={this.state.inputValue}
                        placehoder="todo list "
                        style={{width:'300px'}}
                        onChange={this.handleInputChange}
                    />
                    <Button
                        type= "primary"
                        onClick={this.handleBtnClick}
                    >提交</Button>
                </div>
                <List
                    style={{marginTop:'10px',width:'300px'}}
                    bordered
                    dataSource={this.state.list}
                    renderItem={(item,index) => (<List.Item onClick={this.handleItemDelet.bind(this, index)} >{item}</List.Item>)}//這個這個參考antd官網
                />
                </div>
        )
    }

    handleInputChange=(e)=>{
        // console.log(e.target.value);
        // 獲取input的value值
        // 告訴store,輸入的型別和輸入框中的值
        // const  action={
        //     // type:'change_input_value',
        //     type:CHANGE_INPUT_VALUE,//由公共常量代替,防止,字串不一致
        //     value: e.target.value,
        // };
        const action=getInputChangeAction(e.target.value);
        //把action傳給store , store自動傳給reducer
        store.dispatch(action);
    };

    //reducer返回newState之後,store傳遞newState給元件
    handleStoreChange=()=>{
        this.setState(store.getState());
        // console.log('store change')
        // 感知store發生變化之後,從store裡獲取最新的資料,然後進行設定
    };

    //提交按鈕(又一次流程)
    handleBtnClick=()=>{
        // //action封裝之前
        // const  action={
        //     // type:'add_todo_item'
        //     type:ADD_TODO_ITEM
        // };
        // action封裝之後
        const action=getAddItemActiom();
        store.dispatch(action);
    };

    //點選刪除
    handleItemDelet=(index)=>{
        // const  action={
        //     // type:'delete_todo_item',
        //     type:DELETE_TODO_ITEM,
        //     index:index,
        // };
        // action封裝之後
        const action=getDeleteItemAction(index);
        store.dispatch(action);
    }


}
export default TodoList;

5.store/index.js

import {createStore} from 'redux';
import reducer from './reducer';  

//1-store是唯一的
//2-只有store才能改變自己的內容(state)
//3-reducer必須是純函式
const store = createStore(    
	reducer,    
	window.__REDUX_DEVTOOLS_EXTENSION__ &&
	 window.__REDUX_DEVTOOLS_EXTENSION__()    
	//如果安裝了redeux devtools擴充套件這個工具(谷歌商店裡下載),
	//則在控制檯裡使用這個方法(為了除錯redux)
);
export default store;

6.store/reducer.js

import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './actionTypes';//引入常量
const  defaultState={
    inputValue:'',
    list:[]
};
//reducer可以接收state,但是絕不能修改state
// reducer必須是純函式
// 純函式:給固定的輸入,一定有固定的輸出(不能有不固定的日期函式),不會有副作用(改變引數的值)
export default (state=defaultState