学习 redux.js 的 流水账

具体 redux 的使用请访问redux

本文主要是一个学习使用与源码的流水账,有点乱,多见谅。

有问题欢迎找我讨论,一起学习~

redux

redux 是一个 Flux 架构的实现,react-europe 上的演讲可以看到很酷的功能(视频需翻墙)

react 项目所有的代码都是可以 hot-reload,并且不丢失 state,这对于开发复杂的单页应用非常有帮助,基于大量 es6+ 特性开发,看源码也算是学习es6+的方法。

hot reload

redux demo

redux 是如何实现 hot-reload 的?

redux 代码整理

Provider

当你使用redux时,你应该只有一个store,react-redux提供了一个叫做Provider的组件来帮助你维护这个store

Provider主要逻辑是

  • 接收store,并将store绑定到childContext上
  • 当store发生变化时,更新store(这里的变化不是指应用数据的变化,而是主要指reducer的变化,为了实现不改变应用state,而hot-reload)
    import { createStore, combineReducers } from 'redux';
    /** 
     * redux 1.0后,与组件相关的代码被迁移到 react-redux
     * Provider 是一个redux 提供的组件,用来接收 store
     */ 
    import { Provider } from 'react-redux';
    //构造 reducer&store
    const reducer = combineReducers(reducers);
    const store = createStore(reducer);

    export default class App extends Component {
      render() {
        /** Provider 接收一个 props 为 store
         * Provider 的 children 必须为一个 function,
         * 因为 Provider 内部会执行 this.props.children()
         */
        return (    
          <Provider store={store}>
            {() => <TodoApp /> }
          </Provider>
        );
      }
    }

redux 为了方便开发,使用了decorator,来简化开发,上面的代码可以简化成

    import { createStore, combineReducers } from 'redux';
    import { provide } from 'react-redux';
    const reducer = combineReducers(reducers);
    const store = createStore(reducer);

    @provide(store);
    export default class App extends Component {
      render() {
        return (    
            {() => <TodoApp /> }
        );
      }
    }

Reducer

reducers 可以理解为一堆 newState = fn(oldState, action)的函数,例如

//传入 state 和 action,返回新的 state
export default function todos(state = initialState, action) {  
  switch (action.type) {
  case ADD_TODO:
    return [{
      id: (state.length === 0) ? 0 : state[0].id + 1,
      marked: false,
      text: action.text
    }, ...state];
      break;
  }
}        

那么 reducer 怎么被使用呢?

    // 把多个 reducers 合并成一个 reducer
    const reducer = combineReducers(reducers);
    const store = createStore(reducer);

combineReducers的源码如下(简化版)

export default function combineReducers(reducers) {  
  // 容错代码  pick 主要是把非 function 的 reducer 删除
  var finalReducers = pick(reducers, (val) => typeof val === 'function');
  // combineReducers最后返回的是一个函数,接收 steate 和 action
  // 看明白了吧,这个函数就是为了把多个 reducers 合并成一个 reducer
  return function combination(state = {}, action) {
    return mapValues(finalReducers, (reducer, key) => {
      //state 会根据 key 来区分
      var newState = reducer(state[key], action);
      return newState;
    });
  };
}

Store

store 会存储当前的 state、另外可以调用 action 和监听变化

Connector

import React, { Component } from 'react';  
import { bindActionCreators } from 'redux';  
import { Connector } from 'react-redux';  
import Header from '../components/Header';  
import MainSection from '../components/MainSection';  
import * as TodoActions from '../actions/TodoActions';

export default class TodoApp extends Component {  
  render() {
    return (
      <Connector select={state => ({ todos: state.todos })}>
        {this.renderChild}
      </Connector>
    );
  }
  renderChild({ todos, dispatch }) {
    const actions = bindActionCreators(TodoActions, dispatch);
    return (
      <div>
        <Header addTodo={actions.addTodo} />
        <MainSection todos={todos} actions={actions} />
      </div>
    );
  }
}

connector同样是react-redux提供的组件,主要用来将 store 的一部分数据,和组件进行绑定,方便当数据改变时,对组件进行更新(通过改变这个connector组件的state)

数据的检测都使用类似PureRenderMixin的判断,action中修改数据时应该注意。

connector 需要传入一个 select 的 props,select 是个函数,参数为 state

state 是当前应用的 state,当前应用的 state 是通过Provider的 this.context.state 传下来的

同样 connector 也有 Decorator 的简化版

import React, { Component } from 'react';  
import { bindActionCreators } from 'redux';  
import { connect } from 'react-redux';  
import Header from '../components/Header';  
import MainSection from '../components/MainSection';  
import * as TodoActions from '../actions/TodoActions';  
@connect({state => ({ todos: state.todos })})
export default class TodoApp extends Component {  
  render({ todos, dispatch }) {
      // actions 
    const actions = bindActionCreators(TodoActions, dispatch);
    return (
      <div>
        <Header addTodo={actions.addTodo} />
        <MainSection todos={todos} actions={actions} />
      </div>
    );
  }
}

action

action最简单可以返回一个包含 action type 的对象,redux 会对 action 进行封装

同样借助 middleware,你也可以返回 thunk、promise等数据类型

export function addTodo(text) {  
  return {
    type: types.ADD_TODO,
    text
  };
}

// Can also be async if you return a function
// by middleware  redux-thunk
export function incrementAsync() {  
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}


// Could also read state of a store in the callback form
export function incrementIfOdd() {  
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

由于redux所有数据(可以理解为state)的变化,都做了类似PureRenderMixin的判断,action中修改数据时应该注意,否则会出现数据不变的情况。

为了让数据改变生效,你应该类似这么写

function (state, action) {  
  return {
    ...state,
    isAuthenticated: true,
    email: action.email
  };
}

middleware

redux 的middleware是为了支持更多种的 action

    // Instead of this
    dispatch(action)
    // do this
    middleware(dispatch)(action)

    // simple middleware
    function promiseMiddleware(next) {
      return action =>
        action && typeof action.then === 'function'
          ? action.then(next)
          : next(action);
    }   
: ) boke.io