redux的使用流程及个人心得

Redux 是React生态中重要的组成部分。很多人都说,简单的应用可以不用此工具。但是我个人认为,中小型应用使用的话,可以使文件结构更加规范,代码可读性更强。因为React提出将展示组件与容器组件分离的思想,所以降低了React 与Redux之间的耦合度。
网上广为流传的Redux流向图,可以帮助我们更好地理解并使用。
Redux Flux.png
我个人粗浅的理解是:
Store的角色是整个应用的数据存储中心,集中大部分页面需要的状态数据;
ActionCreators ,view 层与data层的介质;
Reduce ,接收action并更新Store。
所以流程是 用户通过界面组件 触发ActionCreator,携带Store中的旧State与Action 流向Reducer,Reducer返回新的state,并更新界面。

所以也可以按照这个流程思想,来构建代码的结构了。

实现一个很简单的结构.png
上图实现的就是输入输出的东西。输入框内输入一些内容,confirm后,label显示相应内容。

最开始
先安装几个库

npm install --save prop-types
npm install --save react-redux
npm install --save redux

1、首先构造界面
component/AddName.js
-这是一个纯React代码 ,结构清晰。

//component/AddName.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'

class AddName extends Component {
  //声明属性
  static propTypes = {
    lastname:PropTypes.string.isRequired,
    addNameCreater:PropTypes.func.isRequired,
    lastage:PropTypes.number.isRequired,
    addAgeCreater:PropTypes.func.isRequired,
    addNameAsync:PropTypes.func.isRequired
  }
//点击事件
  handlerFunc = () =>{
    const inputName = this.refs.inputValueTest.value;
    this.props.addNameCreater(inputName);
  }
  handlerAgeFunc = () =>{
    const inputage = this.refs.inputValueAge.value;
    this.props.addAgeCreater(inputage);
  }
  handlerAsyncFunc = () =>{
    const inputName = this.refs.inputValueTest.value;
    this.props.addNameAsync(inputName);
  }
//渲染界面
  render() {
    const {lastname,lastage} = this.props;   
    return (
      <div>
        <header className="App-header">
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <label> {lastname} </label><br/>
        <input ref="inputValueTest" /><br/>
        <button onClick={this.handlerFunc}>confirm</button><br/>

        <label> {lastage} </label><br/>
        <input ref="inputValueAge" />
        <button onClick={this.handlerAgeFunc}>confirm</button><br/>

        <button onClick={this.handlerAsyncFunc}>Async Confirm</button><br/>
      </div>
    );
  }
}

export default AddName;

2、然后,根据流程图,我们需要定义一些操作了,也就是ActionCreator.它会传达用户的操作信息以及一些数据
先定义一些常量供我们使用,这里就两种操作,一是添加名字,二是添加年龄,实际都一样。为了后面实现reducer的合并强行写了俩。这写常量一般都定义在actionTpye文件中

Redux/actionType.js
export const ADDNAME = ‘ADDNAME’
export const ADDAGE = ‘ADDAGE’
接着就是写ActionCreator ,定义了一些操作类型,告诉store自己是干什么的,需要什么样的数据。

Redux/actions.js

import { ADDNAME,ADDAG

E } from "./action-type";

//包含所有的action creator
export const addNameCreater = (name) =>({type:ADDNAME,data:name})
export const addAgeCreater = (age) => ({type:ADDAGE,data:age})
export const addNameAsync = (name) =>{
    return dispatch =>{
        setTimeout(()=>{
            dispatch(addNameCreater(name))
        },2000);
    }
}

3、Reducer 会接收到action的信息。将会进行状态(数据)的处理,相当于react中的setState()的功能。如果有多个reducer ,可以使用combineReducers方法将其合并,并暴露出去。
Redux/reducer.js

//包含n个reducer函数的模块
import {ADDNAME, ADDAGE} from './action-type'
import {combineReducers} from 'redux'
function addName(state='initRedux',action){ //形参默认值
    switch(action.type){
        case ADDNAME:
            return action.data
        default:
            return state
    }
}
function addAge(state=0,action){
    switch(action.type){
        case ADDAGE:
            return action.data
        default:
            return state
    }
}

export const finalReducer = combineReducers({
    addName,addAge
})

其中state=‘initRedux’ 、state=0 相当于我们在React组件内部初始化state.

4、一切操作还是基于Store 的。类似于中央集权。所以还要把Store建立出来
Redux/store.js

import {createStore,applyMiddleware} from 'redux'
import {finalReducer } from './reducers'
import thunk from 'redux-thunk'
//生成store对象
const store = createStore(finalReducer,applyMiddleware(thunk));//内部会第一次调用reducer函数,得到初始state 

export default store

因为reducer会更新Store中的状态(数据),所以需要引入reducer ,并创建store.

到此,流程图到这里就走完了。不过2、3、4都是redux中负责接管React 状态的功能。1是React负责展示的组件。两者并没啥关系。既然有了展示组件,接下来就要有容器组件了。也就是能够将React与redux相关联的一个组件。

5、构建容器组件
containers/App.js

import React from 'react'
import {connect} from 'react-redux'
import {addNameCreater,addAgeCreater,addNameAsync} from '../redux/actions'
import AddName from '../component/AddName'
export default connect(
    state => ({
        lastname:state.addName,
        lastage:state.addAge
    }),
    {addNameCreater,addAgeCreater,addNameAsync}
)(AddName)

这里用到了react- redux中的connect 。可以将React与redux关联起来。AddName就是第一步写的组件名。state中关联了React中的属性。这里面涉及到两个API,到第二章详细描述。

6、添加store
src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'

import App from './containers/App'
import store from './redux/store'

ReactDOM.render((
//使用Provider 组件将APP主组件包裹住,这样内部组件都有Store种提供的属性。
    <Provider store={store}>
        <App/>
    </Provider>
), document.getElementById('root'));

这样就OK了。

文件结构.png
上图是主要的文件结构,
-redux 集中管理状态(数据)
-component 专注于React 展示组件部分
-containers 集中处理React与redux交互的部分

此外,redux还可以处理一些异步请求。这样的话。可以做到data 和view 的管理分离,增强了工程结构的可读性与可维护性。

1、Action 与 Reducer的详细理解
先回顾一下不使用Redux的情况下,React的Class该怎么写。

import React, { Component } from 'react';

class Test extends Component{
    constructor(props){
        super(props);
        state={
            name:''
        }
    }
    handlerFunc = () =>{
      const inputName = this.refs.inputValueTest.value;
      this.setState({
        name:inputName
      })
    }
    render() {
      const {name} = this.state;   
      return (
      <div>
        <label> {name} </label><br/>
        <input ref="inputValueTest" /><br/>
        <button onClick={this.handlerFunc}>confirm</button><br/>
      </div>
       );
    }
}

嗯,主要就是这样:constructor 里初始化状态,然后渲染组件,onClick里绑定点击事件,事件函数中setState()一下,此时得到了新的状态,并使虚拟DOM重新渲染,最终Label显示我们输入的值。

这个过程理解了的话,也就能够和Redux进行对照理解。

然后看看使用Redux 后,组件是怎么写的:

import React, { Component } from 'react';
import PropTypes from 'prop-types'
class Test extends Component{
    static propTypes = {
      firstname:PropTypes.string.isRequired,
      addName:PropTypes.func.isRequired
    }
    handlerFunc = () =>{
      const inputName = this.refs.inputValueTest.value;
      this.props.addName(inputName);
    }
    render() {
      const {firstname} = this.props;   
      return (
      <div>
        <label> {firstname} </label><br/>
        <input ref="inputValueTest" /><br/>
        <button onClick={this.handlerFunc}>confirm</button><br/>
      </div>
       );
    }
}

观察两种写法,会发现:
(1)初始化状态没有了,
(2)setState()也移除了,然后通过属性结构赋值。

总之,状态管理方面的东西都被移除了。因为采用Redux框架,本身就是要让他接管状态的,所以,redux框架下的React的代码就很好理解了。就是react组件里面是没有State相关的东西(当然,特殊情况下还是需要用的组件自己的状态管理的)。它所要做的就是,传递动作(按钮点击)或者请求(请求数据),并根据自身属性向HTML中填入数据。这里用到了prop-types这个库。来进行属性的校验。

之后可以这样理解,setState()方法在Redux那边被拆分成两步,现在Action中,把获取到的值,弄成一个Object,代码如下:

import { ADDNAME} from “./action-type”;

//包含所有的action creator
export const addName = (name) =>({type:ADDNAME,data:name})
这里面的addName 就是React代码里按钮触发的方法中要执行的函数,name 就是我们或得到的输入框里的值。

接着redux帮我们做的就是用Store中提供的dispatch方法,把这个动作传到reducer那去,然后来看看Reducer做了什么:

import {ADDNAME} from './action-type'
const initialState = {
    name:'Peter'
}
const name = (state=initialState,action) => { //形参默认值
    switch(action.type){
        case ADDNAME:
            return action.data
        default:
            return state
    }
}    

由于我们把React中的状态让redux单独拎出来,所以最终会生成一棵大的状态树,也就是一个Object。所以这里的name,最终会成为这个状态树的一个属性,值就是返回值,action.data ,对应action里传入的值。可以看一下,状态树可以通过Store.getState获得:

State.png

Reducer 可以类别理解为,充当了setState的角色。并且也负责了初始化state的任务。

所以,reducer在case到ADDNAME 这个动作之后,就把从action那传来的值,做了一个相当于setState()的操作。要是一开始没有触发任何动作,则Store会帮我们在程序运行之初执行一遍Reducer,使用初始值,initialState

this.setState({
  name:action.data
})

2、展示组件与容器组件
redux写的代码单拎出来与react代码是没啥联系的。写完上面的东西后,一运行就会报错,一大堆undefined。这个时候就需要一个connect来连接Redux和React。现在有这么一个库’react-redux’. 使用里面的connect 即可。如下:

import React from 'react'
import {connect} from 'react-redux'
import {addName} from '../redux/actions'
import Test from '../component/Test'
export default connect(
    state => ({`在这里插入代码片`
        firstname :state.name
    }),
    {addName}
)(Test) 

(1)容器组件
这样就写好了一个组件,官方称之为容器组件,我个人把更愿意把它称之为封装组件,就是引入之前写好的React组件Test,再引入之前action和reducer 里的动作、状态,进行一个封装,这样就把Test里事件函数与属性匹配到了一起。

其中 firstname 对应Test 里面的firstname 属性 ,state.name 就是reducer 里定义的那个name.这样的话,到时候Test组件里用的firstname的值,就是state.name给赋上去的。属性状态匹配完之后要匹配动作,Test中点击事件中执行的addName,就匹配action里的addName。因为我这里两边都命名为addName,根据es6规则,属性名和属性值相同的话,可以省略不写,这样就单写一个就可以。这样,点击按钮之后,数据就会通过action传到Reducer那去做类似于setState()这样的操作。
最终拿出去用的,也都是这个组件,可以把它命名为TestContainer.jsx 用于路由或者直接放到哪都可以。
(2)展示组件 这个就是Test那个组件。里面一般不包含任何Redux相关的东西。纯净的React组件。称之为展示组件。可以理解为容器组件(封装组件)的一部分

3、异步
一个web应用,大多数都需要做请求操作的。而在React + Redux这样的一个结构中,异步请求操作最合适的位置就是放到Redux中的action中。首先,展示组件,即Test组件,这个是view层。view层显然不适合掺杂异步请求的。然后reducer其实是实现的类似于setState()的作用,并且这个时候才请求数据就有点晚了。所以在Redux中的Action中做异步请求是比较合适的。action其实也是个传接数据的载体。
既然选择在Redux中进行异步请求,就需要引入一个库 react-thunk,来帮助我们进行异步请求.
首先进行配置。

import {createStore,applyMiddleware} from 'redux'
import {finalReducer } from './reducers'
import thunk from 'redux-thunk'
//生成store对象
const store = createStore(finalReducer,applyMiddleware(thunk));//内部会第一次调用reducer函数,得到初始state 

export default store
第一步 npm i redux-thunk;
第二步 引入 redux-thunk的thunk 以及Redux的applyMiddleware
第三步,在createStore中加入这个库
很简单。
然后就是使用了
正常来说,一般我们是触发一个动作,让他进行请求,得到数据后,再去setState.在Action中也是一样。如下:

export const addNameAsync = (name) =>{
    return dispatch =>{
        setTimeout(()=>{
            dispatch(addNameCreater(name))
        },2000);
    }
}

这里用setTimeout模拟请求过程。两秒后,数据请求到了,再把数据给Reducer进行状态替换。这里的话,我们需要手动调用dispatch这个函数,向Reducer去分发动作。

总结
Redux这块一开始接受很多概念确实很难理解。学习就是如此,一下子铺天盖地的抛出很多你从来没听过的概念,无论是谁都接受的很困难。作为学习者,就需要类比,理解,然后追根溯源,看看底层如何实现。

就本篇与前一篇的例子而言,原本不到一百行的东西,在使用了Redux之后,分出了这么多个文件。确实,折中小来小去的玩意儿,放在一个文件里写就完事儿了。但是如果没有状态管理的这个意识。在进行大型项目的构建时,就很麻烦,一个请求过来的数据,可能要在多个组件用。这个时候,无论MobX 还是Redux这类的状态管理库,会节省很多开发时间,提高开发效率。

另外就是封装性很好,清晰的文件结构和代码结构,能够大大提高项目的可扩展行、可维护性以及降低耦合度。而对于一些把任何东西写到一起的那种项目,一个页面上千行甚至几千行,扩展维护起来的体验可想而知。

作为开发者,还是要对自己写的东西负责。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值