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,action)=>{
    //input    
    if (action.type===CHANGE_INPUT_VALUE){        
        const  newState=JSON.parse(JSON.stringify(state));//简单的深拷贝        
        newState.inputValue=action.value;        
        return newState;    
    }     
    
    //button    
    if (action.type===ADD_TODO_ITEM){        
        const  newState=JSON.parse(JSON.stringify(state));//把老数据拷贝一份        
        newState.list.push(newState.inputValue);//在列表中新加输入框内容        
        newState.inputValue='';//点击提交之后,输入框清空        
        // console.log(newState);        
        return newState;//返回给store    
    }     
    
    //点击删除    

    if (action.type===DELETE_TODO_ITEM){        
        const newState=JSON.parse(JSON.stringify(state));        
        newState.list.splice(action.index,1);        
        return newState;     
    }    
    return state;
}

7.store/actionTypes.js

export const CHANGE_INPUT_VALUE='change_input_value';
export const ADD_TODO_ITEM='add_todo_item';
export const DELETE_TODO_ITEM='delete_todo_item';

8.store/actionCreator.js

//用actionCreator创建统一的action

import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './actionTypes';//引入常量
// 为了封装  const  action={
//             // type:'add_todo_item'
//             type:ADD_TODO_ITEM
//         };

export  const getInputChangeAction=(value)=>({
    type:CHANGE_INPUT_VALUE,
    value:value,
});
export  const getAddItemActiom=()=>({
    type:ADD_TODO_ITEM,
});
export const getDeleteItemAction=(index)=>({
    type:DELETE_TODO_ITEM,
    index:index,
});

猜你喜欢

转载自blog.csdn.net/qq_38277366/article/details/82919745