Redux基础

171 阅读8分钟

Redux介绍

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。(一个用来管理管理数据状态和UI状态的JavaScript应用工具)其实,有点像vue中的vuex。不过,虽然redux作为react御用状态容器,但实际redux已经做到了对三大框架以及原生JS的支持。随着JavaScript单页应用(SPA)开发日趋复杂,JavaScript需要管理比任何时候都要多的state(状态),Redux就是降低管理难度的。 Redux流程图: 基础入门部分,我们要搞清楚的就是这样一个流程。 流程图中有四个部分(store、actions、reducer和view),他们用一些箭头联系起来。那么接下来我们通过代码演示,让大家对四个抽象概念和其间关系有清晰的认知。

创建Redux中的仓库-store和reducer

Redux工作流程中有四个部分,最重要的就是store这个部分,因为它把所有的数据都放到了store中进行管理。在编写代码的时候,因为重要,所以要优先编写store。 使用Redux之前,我们先安装:npm install --save redux

安装好redux之后,在src目录下创建一个store文件夹,然后在文件夹下创建一个index.js文件。 index.js就是整个项目的store文件,打开文件,编写下面的代码。

import { createStore } from 'redux'  // 引入createStore方法
const store = createStore()          // 创建数据存储仓库
export default store                 //暴露出去

这样,我们的仓库就已经建立好了。但是仓库的内容要更新,我们在管理这些状态时把更新的方法写入index.js显然是不合适的。那么我们需要建立一个新文件reducer.js。这两概念之间的关系可以比作是老板和财务之间的关系,老板手上的原始财务资料是需要加工的,那么在财务加工完之前,老板手中的资料是不会变动的,财务完成工作后把材料交给老板审批。审批过后,老板更新手中的材料。

那么新建完reducer.js后,我们写入以下代码:

const defaultState = {}  //默认数据
export default (state = defaultState,action)=>{  //就是一个方法函数
    return state
}

接下来,我们把两者联系起来(在store中引入reducer,注意看引入的固定格式):

import { createStore } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
const store = createStore(reducer) // 创建数据存储仓库,以参数的形式传递给store。
export default store   //暴露出去

下面,我们制作一个todoList的案例带大家体会reducer的defaultState有了数据之后如何。

在store中为todoList初始化数据

在reducer.js文件的defaultState对象中添加数据,为什么在reducer里?这里相当于直接是老板已经把材料给财务了。

const defaultState = {
    inputValue : '请输入内容',
    list:[
        '早上4点起床,锻炼身体',
        '中午下班游泳一小时'
    ]
}
export default (state = defaultState,action)=>{
    return state
}

组件获得store中的数据

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

class App extends React.Component {
    constructor(props){
        super(props)
        this.state=store.getState();
        console.log(this.state)
    }
    render() { 
        return ();
    }
}

通过setState这个API来获得state中的数据。我们来看看控制台中是否打印了数据: 我们给出UI层面的代码:

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



class TodoList extends Component {
constructor(props){
    super(props)
    //关键代码-----------start
    this.state=store.getState();
    //关键代码-----------end
    console.log(this.state)
}
    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>

                    <Input placeholder={this.state.inputValue} style={{ width:'250px', marginRight:'10px'}}/>
                    <Button type="primary">增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        //关键代码-----------start
                        dataSource={this.state.list}
                        //关键代码-----------end
                        renderItem={item=>(<List.Item>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
}
export default TodoList;

Redux Dev Tools的安装与配置

1、建议用Microsoft Edge,进入微软商店直接下载Redux DevTools。

2、Google科学上网下载Redux DevTools。 修改store中的index.js代码:

import { createStore } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // 创建数据存储仓库
export default store   //暴露出去

体验Redux的流程

接下来我们通过todoList案例的完善,引入action的角色并完整地展示Redux的流程。 首先,我们为input框添加一个事件,进行以下修改: 接下来,我拿就要创建action了。action就是一个对象,这个对象一般有两个属性,第一个是对action的描述,第二个是要改变的值。

changeInputValue = (e) => {
   const action ={
        type:'changeInput',
        value:e.target.value
   }
   store.dispatch(action)//传递给store
}

前文已经说了store只是一个仓库,它并没有管理能力,它会把接收到的action自动转发给Reducer。我们现在先直接在Reducer中打印出结果看一下。 这里的reducer接收到了action。现在要作的就是改变store里的值。我们先判断type是不是正确的,如果正确,我们需要从新声明一个变量newState。(记住:Reducer里只能接收state,不能改变state。),所以我们声明了一个新变量,然后再次用return返回回去。

export default (state = defaultState,action)=>{
    if(action.type === 'changeInput'){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.inputValue = action.value
        return newState
    }
    return state
}

现在store里的数据已经更新了,但是组件还没有进行更新。我们需要打开组件文件App.js,在constructor,写入下面的代码。 效果: 完成了store中数据的更新,那么我们接下来实现todoList的实例。

完善todoList实例

为按钮添加点击事件:

<Button 
    type="primary"
    onClick={this.clickBtn}
>增加</Button>

这时候已经把action传递给了store,然后去Reducer里编写业务逻辑就可以了。 好的,我们来看看效果: 为每一项添加删除事件: 定义reducer中的处理逻辑: 来看效果:

工作技巧

一:

1、把Action Types 单度写入一个文件

写Redux Action的时候,我们写了很多Action的派发,产生了很多Action Types,如果需要Action的地方我们就自己命名一个Type,会出现两个基本问题:

这些Types如果不统一管理,不利于大型项目的服用,设置会长生冗余代码。 因为Action里的Type,一定要和Reducer里的type一一对应在,所以这部分代码或字母写错后,浏览器里并没有明确的报错,这给调试带来了极大的困难。 那我司中会把Action Type单独拆分出一个文件。在src/store文件夹下面,新建立一个actionTypes.js文件,然后把Type集中放到文件中进行管理。

export const  CHANGE_INPUT = 'changeInput'
export const  ADD_ITEM = 'addItem'
export const  DELETE_ITEM = 'deleteItem'

2、引入Action中并使用

写好了ationType.js文件,可以引入到TodoList.js组件当中,引入代码如下:

import { CHANGE_INPUT , ADD_ITEM , DELETE_ITEM } from './store/actionTypes'

引入后可以在下面的代码中进行使用这些常量代替原来的Type值了.

changeInputValue(e){
    const action ={
        type:CHANGE_INPUT,
        value:e.target.value
    }
    store.dispatch(action)
}
clickBtn(){
    const action = { type:ADD_ITEM }
    store.dispatch(action)
}
deleteItem(index){
    const action = {  type:DELETE_ITEM, index}
    store.dispatch(action)
}

3、引入Reducer并进行更改

也是先引入actionType.js文件,然后把对应的字符串换成常量,整个代码如下:

import {CHANGE_INPUT,ADD_ITEM,DELETE_ITEM} from './actionTypes'

const defaultState = {
    inputValue : 'Write Something',
    list:[
        '早上4点起床,锻炼身体',
        '中午下班游泳一小时'
    ]
}
export default (state = defaultState,action)=>{
    if(action.type === CHANGE_INPUT){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.inputValue = action.value
        return newState
    }
    //state值只能传递,不能使用
    if(action.type === ADD_ITEM ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list.push(newState.inputValue)  //push新的内容到列表中去
        newState.inputValue = ''
        return newState
    }
    if(action.type === DELETE_ITEM ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list.splice(action.index,1)  //push新的内容到列表中去
        return newState
    }
    return state
}

这样就实现了复用,比如说增删改查,那查不可能用一次,可能很多地方都会用到,这样我们直接引入文件使用就可以了,可以避免冗余代码。还有就是这样如果我们写错了常量名称,程序会直接在浏览器和控制台报错,可以加快开发效率,减少找错时间。

二:

1、编写actionCreators.js文件

在/src/store文件夹下面,建立一个心的文件actionCreators.js,先在文件中引入上节课编写actionTypes.js文件。

import {CHANGE_INPUT}  from './actionTypes'

引入后可以用const声明一个changeInputAction变量,变量是一个箭头函数,代码如下:

import {CHANGE_INPUT}  from './actionTypes'

export const changeInputAction = (value)=>({
    type:CHANGE_INPUT,
    value
})

2、修改todoList中的代码

有了文件,就可以把actionCreatores.js引入到TodoLisit中。

import {changeInputAction} from './store/actionCreatores'

引入后,可以把changeInputValue()方法,修改为下面的样子。

changeInputValue(e){
        const action = changeInputAction(e.target.value)
        store.dispatch(action)
}

然后再浏览器中打开程序,进行测试,也是完全正常的。

3、修改另两个Action方法

安装上面的例子,修改另两个方法,actionCreatores.js全部代码如下:

import {CHANGE_INPUT , ADD_ITEM,DELETE_ITEM}  from './actionTypes'

export const changeInputAction = (value)=>({
    type:CHANGE_INPUT,
    value
})

export const addItemAction = ()=>({
    type:ADD_ITEM
})

export const deleteItemAction = (index)=>({
    type:DELETE_ITEM,
    index
})

这个文件写完,可以把TodoList.js文件里的所有action都改为直接调用方法的模式。代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
import store from './store'
//关键代码-------------start
import {changeInputAction , addItemAction ,deleteItemAction} from './store/actionCreatores'
//关键代码------------end

class TodoList extends Component {
constructor(props){
    super(props)
    this.state=store.getState();
    this.changeInputValue= this.changeInputValue.bind(this)
    this.storeChange = this.storeChange.bind(this)
    this.clickBtn = this.clickBtn.bind(this)
    store.subscribe(this.storeChange) //订阅Redux的状态
}
    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>
                    <Input 
                        placeholder={this.state.inputValue} 
                        style={{ width:'250px', marginRight:'10px'}}
                        onChange={this.changeInputValue}
                        value={this.state.inputValue}
                    />
                    <Button 
                        type="primary"
                        onClick={this.clickBtn}
                    >增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        dataSource={this.state.list}
                        renderItem={(item,index)=>(<List.Item onClick={this.deleteItem.bind(this,index)}>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
    storeChange(){
        console.log('store changed')
        this.setState(store.getState())
    }
    //--------关键代码------start
    changeInputValue(e){
        const action = changeInputAction(e.target.value)
        store.dispatch(action)
    }
    clickBtn(){
        const action = addItemAction()
        store.dispatch(action)
    }
    deleteItem(index){
        const action = deleteItemAction(index)
        store.dispatch(action)
    }
    //--------关键代码------end
}
export default TodoList;

都写完了,我们就可以到浏览器中进行查看了,功能也是完全可以的。这节课我们实现Redux Action和业务逻辑的分离,我觉的这一步在你的实际工作中是完全由必要作的。这样可打打提供程序的可维护性。

总结三点

1、store必须是唯一的,多个store是坚决不允许,只能有一个store空间

2、只有store能改变自己的内容,Reducer不能改变

3、Reducer必须是纯函数