阅读 35

react漫谈 redux

redux简述

先来讲讲redux是什么吧,我们通常在react开发中都会这么说说,引入redux处理数据流,但是这里讲的redux已经是经过二次封装的内容react-redux了,而他的原始形态才是redux,是一个不依赖react就能进行数据流处理的组件。而redux的又是脱胎于flux思想。flux是由facebook工程师提出的一种解决方案,他的名字是拉丁语的flow,主要是为了解决MVC架构存在的问题。他的核心思想是数据和逻辑永远单向进行流动。 讲到这里,我们先描述下flux当时想要解决的问题(笑,也就是大名鼎鼎的MVC结构。

MVC结构下代码的书写方式

首先,我们先考虑下传统的react的数据处理的组件结构来实现一个博客的论坛评论app 论坛评论的app主要包含两个功能:

  1. 评论展示区
  2. 评论编辑区

我们尝试着去书写一个评论展示区的方案,专门用一个model组件来处理数据,再使用一个纯函数组件来进行业务逻辑组件的展示 代码如下:

import React,{Component,PropTypes}from 'react';
class CommentListContainer extends Component{
    constructor(props){
        super(props)
        this.state={loading:true,error:'',value:''}
    }
    
    componentDidMount(){
        this.props.promise.then(res=>res.json())
        .then(value=>this.setState({loading:false,value}))
        .catch(error=>this.setState({loading:false,error}))
    }
    
    render(){
        const{loading,error,value}=this.state
        if(loading){
            return <span>loading....</span>
        } else if(error.length!==0){
            return <span>Error:{error}</span>
        }else{
            return(
            <CommentList comments={list}
            )
        }
    }
}

function CommentList({comments}){
    return(
        <ul>
            {
                comments.map((v,i)=>(
                <li key={v.id}>{v.text}</li>
                ))
            }
        </ul>
    )
}


复制代码

以上是我们在不使用redux的时候进行的react组件方案

这样子看起来是和数据解耦了,但是实际上数据还是在对应的组件内部进行保存,并不算彻底的解耦

而以上的模型就是我们我们在MVC模型中讲述的Model和View,至于为什么没有Controller,因为对于纯函数组件来说,他不需要感知他做了什么内容,只需要知道用户的操作需要激发一个更改,所以如果有数据操作,也会放在Model中进行代码的书写,而View仅仅进行一个操作的动作 MVC Model负责同步数据,校验数据 View负责可视化,Controller负责连接View和Controller, MVC是上世纪80年代被提出的概念,直到2005年,他的问题被放大 那么MVC的问题是什么呢,下面用一张流程图来展示

流程图
这是一张我在processon上绘制的流程图,因为model的set和get都是暴露在外的,所以view可以随意更改model的数值,一个view往往只会更改一个model,但是一个model更改了可能多个view的数值都被同时更改,而且model之间也能随意更改对方的

Flux的解决方案

讲完了mvc的解决方案来讲讲flux的解决方案,用一张图表示刚刚说的核心思想,数据和逻辑永远单向流动

流程图
数据保证从action->dispather->store->view->action这样一个循环 这种渲染其实是一种全面的渲染,但是因为使用了virtual DOM,并不会过多的影响性能,而且通过pureRender等方式保障节点的局部渲染。 但是flux并不是万能的,从复杂场景来说,比如浏览器的控制台,flux流程能比较好的降低复杂度

redux在实际中的运用

运用方式想必大家都很熟悉了,通过connect连接store,dispatch分发action来触发的机制 这里注重给大家介绍几种比较实用的中间件

  1. redux-form-utils 这个中间件的目的是为了减少创建表单的冗余代码 大家可以想象下原生react处理表单,下面只展示核心代码
handleChangeName(e){
    this.setState({
        name:e.target.value
    })
}

handleChangeAddress(e){
    this.setState({
        address:e.target.value
    })
}

render(){
    const {name,address}=this.state
    return(
    <form>
        <input  name="name" value={name} onChange={this.handleChangeName} />
        <input  name="address" value={address} onChange={this.handleChangeAddress} />
    </form>
    )
}
复制代码

可以看得出来,这个change事件的代码非常容易,而且大部分处理逻辑类似,这个时候用中间件可以怎么写

import {createForm } from 'redux-form-utils'
@createForm({
 form:'my-form',
 fields:['name','address']
})
class Form extends Component{
    render(){
        const {name,address}=this.props.fields;
        return(
            <form className='form'>
                <Input name='name' {...name} />
                <Input name='address' {...address} />
            </form>
        )
    }
}

复制代码

通过deractor+高阶组件的写法,内部封装了有关于form表单的处理来实现对代码的简写 用过antd的同学肯定发现了,这种写法其实就是antd的form表单的写法. redux-form-utils除了createForm还有个bindRedux,具体作用是在如果表单的数据是用来整个存放在redux中的表单数据

  1. redux-thunk 主要是以多参数的形式currying实现对函数的惰性求值。可以用来改造同步的dispatch为异步 核心代码如下:
function createThunkMiddleware(extraArgument){
    return({dispatch,getState})=>next=>action{
        if(typeof action==='function'){
            return action(dispatch,getstate,extraArgument)
        }
        return next(action)
    }
}
复制代码

当action为函数的时候,这个行为就被拦截了,而不是派发到reducer中去找到对应的action触发内容,这里的action就是一个thunk函数,将dispatch和getState派发到函数中 初始化thunk函数方法如下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
复制代码

具体调用中可以这么写

let apis=(url,params)=>{
    return(dispatch,getState)=>{
        fetch(url,params).then(result=>{
            dispatch({
                type:'GET_WEATHER_SUCCESS',
                payload:result
            });
        }).catch(e=>{
            dispatch({
                type:'GET_WEATHER_ERROR',
                error:error
            })
        })
    }
}


store.dispatch(apis('https://xx.xx.xx.xx',{a:1,b:2}))

复制代码

后续再补充别的中间件