React系列---Redux高阶运用

参考资料:《深刻React技术栈》react


高阶reducer

高阶函数是指将函数做为参数或返回值的函数,高阶reducer就是指将reducer做为参数或返回值的函数。

在Redux架构中,reducer是一个纯函数,它的职责是根据previousState和action计算出新的state。在复杂应用中,Redux提供的combineReducers让咱们能够把顶层的reducer拆分红多个小的reducer,分别独立地操做state树的不一样部分。而在一个应用中,不少小粒度的reducer每每有不少重复的逻辑,那么对于这些reducer,如何抽取公共逻辑,减小代码冗余呢?这种状况下,使用高阶reducer是一种较好的解决方案。redux

reducer复用

咱们将顶层的reduce拆分红多个小的reducer,确定会碰到reducer复用问题。例若有A和B两个模块,它们的UI部分类似,此时能够经过配置不一样的props来区别它们。那么这种状况下,A和B模块能不能共用一个reducer呢?答案是否认的。咱们先来看一个简单reducer:架构

const LOAD_DATA = 'LOAD_DATA';
const initialState = { ... };

function loadData() {
    return {
        type: LOAD_DATA,
        ...
    };
}

function reducer(state = initialState, action) {
    switch(action.type) {
        case LOAD_DATA:
            return {
                ...state,
                data: action.payload
            };
        default:
            return state;
    }
}

若是咱们将这个reducer绑定到A和B两个不一样模块,形成的问题将会是,当A模块调用loadData来分发相应的action时,A和B的reducer都会处理这个action,而后A和B的内容就彻底一致了。框架

这里咱们必需意识到,在一个应用中,不一样模块间的actionType必须是全局惟一的。异步

所以,要解决actionType惟一的问题,还有一个方法就是经过添加前缀的方式来作到:函数

function generateReducer(prefix, state) {
    const LOAD_DATA = prefix + 'LOAD_DATA';
    
    const initialState = { ...state, ...};
    
    return function reducer(state = initialState, action) {
        switch(action.type) {
            case LOAD_DATA:
                return {
                    ...state,
                    data: action.payload
                };
            default:
                return state;
        }
    }
}

这样只要A和B模块分别调用generateReducer来生成相应的reducer,就能解决reducer复用的问题了。而对于prefix,咱们能够根据本身的项目结构来决定,例如${页面名称}_${模块名称}。只要可以保证全局惟一性,就能够写成一种前缀。工具

reducer加强

除了解决复用问题,高阶reducer的另外一个重要做用就是对原始的reducer进行加强。redux-undo就是典型的利用高阶reducer来加强reducer的例子,它主要做用是使任意reducer变成能够执行撤销和重作的全新reducer。咱们来看看它的核心代码实现:this

function undoable(reducer) {
    const initialState = {
        // 记录过去的state
        past: [],
        // 以一个空的action调用reducer来产生当前值的初始值
        present: reducer(undefined, {}),
        // 记录后续的state
        future: []
    };
    
    return function(state = initialState, action) {
        const { past, present, future } = state;
        
        switch(action.type) {
            case '@@redux-undo/UNDO':
                const previous = past[past.length - 1];
                const newPast = past.slice(0, past.length - 1);
                
                return {
                    past: newPast,
                    present: previous,
                    future: [ present, ...future ]
                };
            case '@@redux-undo/REDO':
                const next = future[0];
                const newFuture = future.slice(1);
                
                return {
                    past: [ ...past, present ],
                    present: next,
                    future: newFuture
                };
            default:
                // 将其余action委托给原始的reducer处理
                const newPresent = reducer(present, action);
                
                if(present === newPresent) {
                    return state;
                }
                
                return {
                    past: [ ...past, present ],
                    present: newPresent,
                    future: []
                };
        }
    };
}

有了这高阶reducer,就能够对任意一个reducer进行封装:spa

import { createStore } from 'redux';

function todos(state = [], action) {
    switch(action.type) {
        case: 'ADD_TODO':
        // ...
    }
}

const undoableTodos = undoable(todos);
const store = createStore(undoableTodos);

store.dispatch({
    type: 'ADD_TODO',
    text: 'Use Redux'
});

store.dispatch({
    type: 'ADD_TODO',
    text: 'Implement Undo'
});

store.dispatch({
    type: '@@redux-undo/UNDO'
});

查看高阶reducer undoable的实现代码能够发现,高阶reducer主要经过下面3点来加强reducer:双向绑定

  • 可以处理额外的action;
  • 可以维护更多的state;
  • 将不能处理的action委托给原始reducer处理。

Redux与表单

React单向绑定的特性极大地提高了应用的执行效率,可是相比于简单易用的双向绑定,单向绑定在处理表单等交互的时候着实有些力不从心。具体到React应用中,单向绑定意味着你须要手动给每一个表单控件提供onChange回调函数,同时须要将它们的状态初始化在this.state中。不只如此,一个体验友好的表单还须要有明确的错误状态和错误信息,甚至某些输入项还须要异步校验功能。也就是说,表单里的一个有效字段至少须要2~3个本地状态。

在Angular.js中,表单相关的问题在框架层面已经获得了很好的解决。那么,对于React+Redux应用,有没有什么好的方案呢?

下面咱们从两个层面来解答这个问题:对于简单的表单应用,为了减小重复冗余的代码,可使用redux-form-utils这个工具库,它能利用高阶组件的特性为表单的每一个字段提供value和onChange等必须值,而无需你手动建立;对于复杂的表单,则能够利用redux-form。虽然一样基于高阶组件的原理,但若是说redux-form-utils是一把水果刀的话,那么redux-form就是一把多功能的瑞士军刀。除了提供表单必须的字段外,redux-form还能实现表单同步验证、异步验证甚至嵌套表单等复杂功能。

使用redux-form-utils减小建立表单的冗余代码

了解redux-form-utils以前,先来看看如何使用原生React处理表单:

import React, { Component } from 'react';

class Form extends Component {
    constructor(props) {
        super(props);
        
        this.handleChangeAddress = this.handleChangeAddress.bind(this);
        this.handleChangeGender = this.handleChangeGender.bind(this);
        
        this.state = {
            name: '',
            address: '',
            gender: ''
        };
    }
    
    handleChangeName(e) {
        this.setState({
            name: e.target.value
        });
    }
    
    handleChangeAddress(e) {
        this.setState({
            address: e.target.value
        });
    }
    
    handleChangeGender(e) {
        this.setState({
            gender: e.target.value
        });
    }
    
    render() {
        const { name, address, gender } = this.state;
        return (
            <form className="form">
              <input name="name" value={name} onChange={this.handleChangeName} />
              <input name="address" value={address} onChange={this.handleChangeAddress} />
              <select name="gender" value={gender} onChange={this.handleChangeGender}>    
                <option value="male" />
                <option value="female" />
              </select>
            </form>
        );
    };
}

能够看到,虽然咱们的表单里只有3个字段,可是已经有很是多的冗余代码。若是还须要加上验证等功能,那么这个表单对应的处理代码将会更加膨胀。

仔细分析表单的代码实现,咱们发现几乎全部的onChange处理器逻辑都很相似,只是须要改变表单字段便可。对于某些复杂的输入控件,好比本身封装了一个TimePicker组件,也许回调名称不是onChange,而是onSelect。一样,onSelect回调里提供的参数也许并非React的合成事件,而是一个具体的值。经过分析表单控件可能的输入和输出,咱们将经过使用redux-form-utils减小Redux处理表单应用时的冗余代码:

// components/MyForm.js
import React, { Component } from 'react';
import { createForm } from 'redux-form-utils';

@createForm({
    form: 'my-form',
    fields: ['name', 'address', 'gender']
})

class Form extends Component {
    render(){
        const { name, address, gender } = this.props.fields;
        return (
          <form className="form">
            <input name="name" value={...name} />
            <input name="address" value={...address} />
            <select {...gender}>    
              <option value="male" />
              <option value="female" />
            </select>
          </form>
        );
    }
}

能够看到,实现一样功能的表单,代码量减小了近一半以上。

redux-form-utils提供了两个方便的工具函数---createForm(config)和bindRedux(config),前者能够看成decorate使用,传入表单的配置,自动为被装饰的组件添加表单相关的props;然后者能够生成与Redux应用相关的reducer、initialState和actionCreator等。

下面先看看如何在reducer里整合redux-form-utils:

// reducer/MyForm.js
import { bindRedux } from 'redux-form-utils';

const { state: formState, reducer: formReducer } = bindRedux({
    form: 'my-form',
    fields: ['name', 'address', 'gender'],
});

const initialState = {
    foo: 1,
    bar: 2,
    ...formState
};

function myReducer(state = initialState, action) {
    switch(action.type) {
        case 'MY_ACTION': {
            // ...
        }
        
        default:
            return formReducer(state, action);
    }
}

咱们把一样的配置传给bindRedux方法,并得到这个表单对应的reducer和初始状态formState,并将这些内容整合在reducer中。

完成createForm和bindRedux这两个函数后,一个基于Redux的表单应用就完成了。为了后续修改表单更加灵活,建议将配置文件单独保存,并分别在组件和reducer中引入对应的配置文件。

使用redux-form完成表单异步验证

redux-form-utils为咱们提供了实现表单最基本的功能,可是为了填写表单的体验更加友好,在把数据提交到服务端以前,咱们应该作一些基本的表单校验,好比填写字段不能为空等。要实现校验等复杂的表单功能,须要用到redux-form。

在使用和配置方面,redux-form与redux-form-utils没有太多的差别,惟一不一样的是redux-form须要在Redux应用的state树中挂载一个独立的节点。这意味着,全部使用redux-form建立的表单中的字段都会在一个固定的位置,如state.form.myForm或state.form.myOtherForm均挂载在state.form下:

import { createStore, combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';

const reducers = {
    // 其余的reducer...
    // 全部表单相关的reducer挂载在form下
    form: formReducer
};

const reducer = combineReducers(reducers);
const store = createStore(reducer);

完成了基本的配置后,如今看看redux-form如何帮咱们完成表单验证功能:

import React, { Component } from 'react';
import { reduxForm } from 'redux-form';

function validate(values) {
    if(values.name == null || values.name === '') {
        return {
            name: '请填写名称'
        };
    }
}

@reduxForm({
    form: 'my-form',
    fields: ['name', 'address', 'gender'],
    validate
});

class Form extends Component {
    render(){
        const { name, address, gender } = this.props.fields;
        return (
          <form className="form">
            <input name="name" value={...name} />
            { name.error && <span>{name.error}</span> }
            <input name="address" value={...address} />
            <select {...gender}>    
              <option value="male" />
              <option value="female" />
            </select>
            <button type="submit">提交</button>
          </form>
        );
    }
}

在上面的表单中,咱们在提交时对name字段作了非空验证,而在Form组件的render方法中,同时添加了显示相应错误的逻辑。触发验证、从新渲染、表单纯洁性判断等过程,均被redux-form进行了封装,对使用者透明。

能够看到,使用redux-form校验表单十分简单易用,从很大程度上填补了Redux应用在框架层面处理表单应用的不足。


参考资料:《深刻React技术栈》

相关文章
相关标签/搜索