Redux basic tutorial

本文的读者为了解flux概念,熟悉react,了解es6语法的同学

redux 是最近很火的一个 flux 框架,短短的一个月现在已经有2900+的 star 了,watch之后每天收到几百封 pr 的邮件,废话就不多说了。
为什么要用 redux ,请看连接 The Evolution of Flux Frameworks

主要特点

  • Everything (Stores, Action Creators, configuration) is hot reloadable. —— 配合起 hotloader 应该特别爽,虽然现在用的是 browserify,好处感觉并不明显。
  • store 中的数据不受限制,可以是 number object array 等等,废话,因为它的 store 只是简单的函数。
  • 提供 devtools ,监控 action 的触发以及 state 的变化。
  • 源码清晰简单,轻量级,根本不需要文档,直接看源码就行了....缺点就是如果看不懂源码,光看文档会觉得不够清晰。
  • api 很精简,不用记茫茫多的 api
  • every thing is simple function
  • connecterprovider 这两个东西用起来总觉得很繁琐,不那么优雅。

下面通过写的一个简单counter的例子 介绍 redux 的核心方法以及一些需要注意的地方。

  • 同步与异步两种actionCreator
  • middleware的使用
  • dispatch actions
  • 拿到并同步state

代码放在https://github.com/yeatszhang/redux-tutorial, 需要安装gulp

代码是基于分支 v1.0.0-rc api略微有些区别,详情见 Breaking API changes for 1.0

actionCreator

actions creator 是用来生成 action 的函数,在默认情况下可以接受返回object或者function 的函数,很多人学习flux的时候把action与actionCreator弄混淆....:

// 直接返回object
actionCreators.addTodo = function(text) {
  return {
    type: types.ADD_TODO,
    text
  };
}

// 返回函数
actionCreators.incrementAsync = function() {
  return (dispatch, getState) => {
    // actionCreator中可以通过getState获得当前的state
    console.log(getState());
    // 异步action
    setTimeout(() => {
      dispatch({
        type: INCREMENT_COUNTER2,
      });
    }, 1000);
  };
};

在没有使用任何 middleware 的情况下,只有这有两种 action 可以被 dispatch

app

在动态内容的最外层应该使用Provider进行包裹,provider接收store作为参数,注意children是一个函数并不是reactElement
provider将store作为context往子节点进行传递,并实现store的热替换。因此在provider内的组件其实可以不通过connect来拿到dispatch以及state,而直接通过context拿到store对象,不过作者不推荐这么做。

import React from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
// redux midlleware repositories
import thunk from 'redux-thunk';
// 将 redux 与 react 相关的部分,如 connector provider 单独抽取出来
import { Provider } from 'react-redux';
import reducers from '../reducers';
import CounterApp from './CounterApp.js';
import logMiddleware from '../middleWares/logMiddleware.js';

const reducer = combineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(thunk, logMiddleware)(createStore);
const store = createStoreWithMiddleware(reducer);
// 使用middleWare thunk, 如果没有自定义中间层的需求可以直接写
// const store = createStore(reducer);

class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        {() => <CounterApp />}
      </Provider>
    );
  }
}

smart component

smart component拥有两个特点:

  1. 自动关联store中的state并自动re-render
  2. 可以通过dispatch来分发事件,从而触发store的更新

刚接触redux的同学肯定会觉得这个connect很难以理解。还是在代码里面说把。。。

/**
 * Created by yichizhang on 15/7/26.
 */

import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { Connector } from 'react-redux';
import Counter from '../components/Counter';
import actionCreators1 from '../actionCreators/actionCreators1.js';
import actionCreators2 from '../actionCreators/actionCreators2.js';

// state 是各reducer中state的集合
function select(state) {
  // 从各reducer中挑选出component需要监听的state
  return {
    counter1: state.reducer1.counter,
    counter2: state.reducer2.counter,
  };
}

export default class CounterApp extends Component {
  // select函数的返回值会与dispatch组装程一个object作为参数
  // 从这里看出connector就是帮忙拿到provider中store的dispatch方法以及挑选出需要使用的state
  renderChild({ counter1, counter2, dispatch}) {
    // 个人觉得这样使用action十分不方便,尤其是当组件只需要触发actions不需要监听store的变化的时候。我会偷懒通过context去拿到dispatch~~
    const actions1 = bindActionCreators(actionCreators1, dispatch);
    const actions2 = bindActionCreators(actionCreators2, dispatch);
    const props = { ...actions1, ...actions2, counter1, counter2 };
    // 所有的action以及state都会以props的形式提供给Counter,然后在Counter里面就可以为所欲为了~
    return <Counter {...props} />;
  }

  render() {
    return (
      <Connector select={select}>
        {this.renderChild}
      </Connector>
    );
  }
}

reducer

redux认为程序员不需要去写store中的逻辑而只需要写明对state的处理逻辑就好:

old  sate => action  => new state

这是一个完全同步的过程。reducer只需要声明初始状态以及state在接收到action之后的改变规则就可以了。

import React from 'react/addons';
import {INCREMENT_COUNTER1, DECREMENT_COUNTER1} from '../constants/actionsTypes.js';
const update = React.addons.update;

// state可以是任何类型
const initialState = {
  counter: 0,
};

// reducer只是一个简单的switch方法
export default function counter(state = initialState, action = null) {
  switch (action.type) {
    case INCREMENT_COUNTER1:
      // 需要注意的是connector当select中的state发生变化时会做一个shallow equal的操作,
      // 所以如果需要操作引用值的时候一定不能直接赋值,需要使用addon中的update或者immutable.js,知道看到这两个工具又不想继续学了..其实很简单
      // 这样可以大大避免重复的render,从而提高性能
      return update(state, {
        counter: {
          $set: state.counter + 1,
        },
      });
    case DECREMENT_COUNTER1:
      return update(state, {
        counter: {
          $set: state.counter - 1,
        },
      });
    default:
      return state;
  }
}

middleWare

感兴趣的同学可以看看,一般来说默认的thunk就够用了。我在例子里加了个log的中间层

// 打印触发的action
function logMiddleware() {
  // 这里的next是下一个middleWare
  return function(next) {
    return function(action) {
      // 打印此action并使用下一个middleWare处理该action
      console.log(action);
      next(action);
    };
  };
}

export default logMiddleware;

// 下面是默认的thunk middleWare

function thunkMiddleware(_ref) {
  var dispatch = _ref.dispatch;
  var getState = _ref.getState;

  return function (next) {
    return function (action) {
      // 如果是函数则将dispatch与getState作为参数执行函数,否则交给写一个middleware处理
      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    };
  };
}

结语

其实redux不明白的地方直接看源码更好,redux的代码量很小而且组织也很清晰,建议大家都去看,不过作者貌似函数式编程的思维很重,大量使用修饰器的语法,还有reduce~ 挺绕的~

之后会总结自己阅读redux源码的一些心得,以及各功能模块的实现原理~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容