Why Redux and How

George Zou

将更多的业务逻辑放到前端实现

  • 更好的交互体验
  • 前后端分离
  • 减轻服务器压力
  • 同一套API可以应用于多终端

使得页面更加复杂

更多交互 => 更多的业务逻辑 => 更多的代码

难以迭代维护

依赖模糊

请求过多

Managing complexity is the most important technical topic in software development. In my view, it’s so important that Software’s Primary Technical Imperative has to be managing complexity.

软件开发的首要任务是控制复杂度

– Steve McConnell in Code Complete (more)

处理复杂前端架构的诀窍在于

提高可预测性

React 就是这么做的

render(){

    return (<div>
        <header>
            <title>xxx's Order List</title>
        </header>
        <table>
            <tr>
                <td>Order Id</td>
                <td>Details</td>
            </tr>
        </table>
    </div>)

}

View

A Simple React Component

componentDidMount(){
    $.post('app/init',{},function(data){
        this.setState(data)
    })
}

Data Manage

Require Data From API

Wait! What had happened?

OptionA => Request A => Result A

OptionB => Request B => Result B

...

Result A: this.setState({a: 1})

Result B: this.setState({a: 2})

...

Why state.a = 2 ?

用户进行了那些操作?

如果代码需要修改

Where can I find ajax request?

What do you do with state?

View and Model or State

从View中抽离数据模型

对数据(状态)进行统一的管理

数据 ==> 视图

动态渲染

What react do

render

可预测状态容器

What We Need

Predictable State Container

State?

状态树

Action

Reducer

View

(previousState, action) => newState
{
    type: 'fetch-apple',
    payload: {
        apple: {
            weight: '10斤',
        }
    }
}

我要吃一个十斤的大苹果

给你,不客气

Action Creator

Redux

{
        type: 'tologin',
        payload: {
            username: username,
            password: password
        }
}

Action

一个普通的Object,type属性是必须的

描述当前所进行的操作操作。

关于标准Action的

“国际惯例”

制造action 的 pure fucntion

function LoginActionCreator(username, password) {
    let status = false;
    if(username === 'admin' && password === '123'){
        status = true;
    }
    return {
        type: 'tologin',
        payload: {
            status: status
        }
    }
}

Action Creator

Reducer

描述状态(state)如何改变

export default function usersReducer(state = initialState, action) {
    switch(action.type) {
        case 'login':
            return {
                ...state,
                status: aciton.payload.status
            };

      case 'change-contact-phonenum':
        return state.map(contact =>
          contact.id === action.id ?
            { ...contact, phoneNum: action.payload.phoneNum } :
            contact
        );
    }
}

Reducer

描述状态(state)如何改变

import { combineReducers } from 'redux';

import user from './user';
import blacklist from './blacklist';

const rootReducer = combineReducers({
    user:user,
    blacklist: blacklist
});

连接多个模块的Reducer

组成完整的状态树

  1. 获取状态 getState()
  2. 更新状态 dispatch(action)
  3. 注册监听 subscribe(listener)

Store

Middleware

Action 到达 Reducer 时间点之前提供第三方补充

function validMiddleware(action) {
    if(!action.payload.login) {
        action.payload.text = "请登录";
    }
}

function logMiddleware(action) {
    log.info(action.type, action.payload);
}

自定义一些有意思的Middleware


    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };

Redux-Promise

Action Creator 中进行异步操作

More Advice

Example

var UserList = React.createClass({
  getInitialState: function() {
    return {
      users: []
    }
  },

  componentDidMount: function() {
    var _this = this;
    $.get('/path/to/user-api').then(function(response) {
      _this.setState({users: response})
    });
  },

  render: function() {
    return (
      <ul className="user-list">
        {this.state.users.map(function(user) {
          return (
            <li key={user.id}>
              <Link to="{'/users/' + user.id}">{user.name}</Link>
            </li>
          );
        })}
      </ul>
    );
  }
});

It work well right?

But,如果我们还需要在开始的时候查询别的数据


  componentDidMount: function() {
    var _this = this;
    $.get('/path/to/user-api').then(function(response) {
        _this.setState({users: response})
        $.get('/path/user-stats').then(function(response) {
            //do something...
        }
    });
  },

使得代码中数据操作和模板组件

强耦合

Separating Presentational and Container Components

分离容器组件和可视化组件

可视化组件

Presentational Components

import React from 'react';

class Teachers extends React.Component{
    constructor(props){
        super(props);
    }

    render(){
        let { teacherList }  = this.props;

        return (
            <div>
                <h2>Teacher List</h2>
                <div>
                    <table>
                        <thead>
                        <th>
                            <td>ID</td>
                            <td>Name</td>
                            <td>Phone</td>
                            <td>Address</td>
                        </th>
                        </thead>
                        <tbody>
                        {
                            teacherList.map(teacher => {
                                return (
                                    <tr>
                                        <td>{teacher.id}</td>
                                        <td>{teacher.name}</td>
                                        <td>{teacher.phone}</td>
                                        <td>{teacher.address}</td>
                                    </tr>
                                )
                            })
                        }

                        </tbody>

                    </table>

                </div>
            </div>
        )
    }
}

export default Teachers;

容器组件组件

Container Components

class TeacherContainer extends React.Component{
    constructor(props){
        super(props);

    }

    getPageParam(){
        let page = parseInt(this.props.params.pageNum);
        return isNaN(pagfe) ? 1 : page;
    }

    getCourseCode(){
        let courseCode = this.props.params.courseCode;
        return courseCode;
    }

    componentWillMount(){
        let courseCode = this.getCourseCode();
        let page = this.getPageParam();

        this.props.getTeachers({
            courseCode,
            page
        })
    }

    componentWillReceiveProps(props){
        let courseCodeNow = this.getCourseCode(),
            courseCodeNext = props.params.courseCode;

        let pageNow = this.getPageParam(),
            pageNext = parseInt(props.params.pageNum);

        if( pageNow != pageNext
            || courseCodeNow != courseCodeNext){

        }
    }

    render(){
        return (
            <Teachers {...this.props} />
        )
    }
}

function mapStateToProps(store){

    return {
        teachers:store.teachers.teachers
    }
}

export default connect(mapStateToProps,{ getTeachers })(TeacherContainer);

Why Redux

一切围绕“资料流”

Redux程序中全部的资料都遵循一样的生命周期模式,使得程序资料可预测

Predictable

Thanks & Question

参考资料

@GeorgeZouQ

Made with Slides.com