使用Redux管理你的React应用

React是最好的前端库,因为其发源于世界上最好的后端语言框架。 —信仰

4.0 will likely be the last major release. Use Redux instead. It’s really great. —Flummox框架作者 acdliteAndrew Clark

为什么使用React还需要使用别的框架来搭配?

React的核心是使用组件定义界面的表现,是一个View层的前端库,那么在使用React的时候我们通常还需要一套机制去管理组件与组件之间,组件与数据模型之间的通信。

为什么使用Redux?

Facebook官方提出了FLUX思想管理数据流,同时也给出了自己的实现来管理React应用。可是当我打开FLUX的文档时候,繁琐的实现,又臭又长的文档,实在难以让我有使用它的欲望。幸好,社区中和我有类似想法的不在少数,github上也涌现了一批关于实现FLUX的框架,比较出名的有Redux,Reflux,Flummox

其中Redux的简单和有趣的编程体验是最吸引我的地方。

  • 简单。和其它的FLUX实现不一样,Redux只有唯一的state树,不管项目变的有多复杂,我也仅仅只需要管理一个State树。可能你会有疑问,一个state树就够用了?这个state树该有多大?别着急,Redux中的Reducer机制可以解决这个问题。
  • 有趣。忙于迭代项目的你,体会编程带来的趣味是有多久没有体会到了?瞧下面这张图,右边那个调试工具是啥?整个应用的action和state都这么被轻松的管理了?行为还能被保存,删除,回滚,重置?修改了代码,页面不刷新也能产生变化?别开玩笑了,不行,世界那么大,让我去试试!

Redux DevTools

注:Redux开发调试工具:redux-devtools
React应用无刷新保存工具:react-transform

不明真相的群众,可能这里需要我来安利一下Flux数据流的思想,看图:

注意:图片仅仅是FLUX思想,而不是Facebook的实现。

大致的过程是这样的,View层不能直接对state进行操作,而需要依赖Actions派发指令来告知Store修改状态,Store接收Actions指令后发生相应的改变,View层同时跟着Store的变化而变化。

举个例子:A组件要使B组件发生变化。首先,A组件需要执行一个Action,告知绑定B组件的Store发生变化,Store接收到派发的指令后改变,那相应的B组件的视图也就发生了改变。假如C,D,E,F组件绑定了和B组件相同的Store,那么C,D,E,F也会跟着变化。

使用React和Redux开发一个小程序

为了更好的描述怎么样使用Redux管理React应用,我做了一个Manage Items的小例子。你可以在这里找到全部的源代码:github

快速查看

1.git clone git@github.com:matthew-sun/redux-example.git

2.npm install && npm start

3.open localhost:3000

目录结构

.
+-- app
|   +-- actions
|       +-- index.js
|   +-- components
|       +-- content.js
|       +-- footer.js
|       +-- searchBar.js
|   +-- constants
|       +-- ActionTypes.js
|   +-- containers
|       +-- App.js
|   +-- reducers
|       +-- index.js
|       +-- items.js
|       +-- filter.js
|   +-- utils
|   +-- configureStore.js
|   +-- index.js
+-- scss
|   +-- pure.scss
+-- index.html

Index.js

在入口文件中,我们需要把App和redux建立起联系。Provider是react-redux提供的组件,它的作用是把store和视图绑定在了一起,这里的Store就是那个唯一的State树。当Store发生改变的时候,整个App就可以作出对应的变化。这里的会传进Provider的props.children里。

/* app/index.js */

import React from 'react';
import { Provider } from 'react-redux';
import App from './containers/App';
import configureStore from './configureStore';

const store = configureStore();

React.render(
    <div>
        <Provider store={store}>
            <App />
        </Provider>
    </div>,
    document.getElementById('app'));

Constants

keyMirror这个方法非常的有用,它可以帮助我们轻松创建与键值key相等的常量。

/* app/constants/actionTypes.js */

import keyMirror from 'fbjs/lib/keyMirror';

export default keyMirror({
    ADD_ITEM: null,
    DELETE_ITEM: null,
    DELETE_ALL: null,
    FILTER_ITEM: null
});

// 等于
// export const ADD_ITEM = 'ADD_ITEM';
// export const DELETE_ITEM = 'DELETE_ITEM';
// export const DELETE_ALL = 'DELETE_ALL';
// export const FILTER_ITEM = 'FILTER_ITEM';

Actions

Action向store派发指令,action 函数会返回一个带有 type 属性的 Javascript Plain Object,store将会根据不同的action.type来执行相应的方法。addItem函数的异步操作我使用了一点小技巧,使用redux-thunk中间件去改变dispatch,dispatch是在View层中用bindActionCreators绑定的。使用这个改变的dispatch我们可以向store发送异步的指令。比如说,可以在action中放入向服务端的请求(ajax),也强烈推荐这样去做。

/* app/actions/index.js */

import { ADD_ITEM, DELETE_ITEM, DELETE_ALL, FILTER_ITEM } from '../constants/actionTypes';

export function addItem(item) {
    return dispatch => {
       setTimeout(() => dispatch({type: ADD_ITEM}), 1000)
    }
}
export function deleteItem(item, e) {
    return {
       type: DELETE_ITEM,
       item
    }
}
export function deleteAll() {
    return {
       type: DELETE_ALL
    }
}
export function filterItem(e) {
    let filterItem = e.target.value;
    return {
       type: FILTER_ITEM,
       filterItem
    }
}

Reducers

Redux有且只有一个State状态树,为了避免这个状态树变得越来越复杂,Redux通过 Reducers来负责管理整个应用的State树,而Reducers可以被分成一个个Reducer。

Reduce在javascript Array的方法中出现过,只是不太常用。简单快速的用代码样例来回顾一下:

  /* Array.prototype.reduce */

var arr = [1,2,3,4];
var initialValue = 5;
var result = arr.reduce(function(previousValue, currentValue) {
    return previousValue + currentValue
}, initialValue)
console.log(result)
// 15
// 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。
// 整个函数执行的过程大致是这样 ((((5+1)+2)+3)+4)

回到Redux中来看,整个的状态就相当于从[初始状态]merge一个[action.state]从而得到一个新的状态,随着action的不断传入,不断的得到新的状态的过程。(previousState, action) => newState,注意:任何情况下都不要改变previousState,因为这样View层在比较State的改变时只需要简单比较即可,而避免了深度循环比较。Reducer的数据结构我们可以用immutable-js,这样我们在View层只需要react-immutable-render-mixin插件就可以轻松的跳过更新那些state没有发生改变的组件子树。

/* app/reducers/items.js */

import Immutable from 'immutable';
import { ADD_ITEM, DELETE_ITEM, DELETE_ALL } from '../constants/actionTypes';

const initialItems = Immutable.List([1,2,3]);

export default function items(state = initialItems, action) {
    switch(action.type) {
        case ADD_ITEM:
            return state.push( state.size !=0 ? state.get(-1)+1 : 1 );
        case DELETE_ITEM:
            return state.delete( state.indexOf(action.item) );
        case DELETE_ALL:
            return state.clear();
        default:
            return state;
    }
}

连接reducers

Redux提供的combineReducers函数可以帮助我们把reducer组合在一起,这样我们就可以把Reducers拆分成一个个小的Reducer来管理Store了。

/* app/reducers/index.js */

import { combineReducers } from 'redux';
import items from './items';
import filter from './filter';

const rootReducer = combineReducers({
  items,
  filter
});

export default rootReducer;

Middleware

在Redux中,Middleware 主要是负责改变Store中的dispatch方法,从而能处理不同类型的 action 输入,得到最终的 Javascript Plain Object 形式的 action 对象。

redux-thunk为例子:

/* redux-thunk */
export default function thunkMiddleware({ dispatch, getState }) {
  return next =>
     action =>
       typeof action === ‘function’ ?
         action(dispatch, getState) :
         next(action);
}

当ThunkMiddleware 判断action传入的是一个函数,就会为该thunk函数补齐dispatch和getState参数,否则,就调用next(action),给后续的Middleware(Middleware 插件可以被绑定多个)得到使用dispatch的机会。

 /* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore = compose(applyMiddleware(thunk))(createStore);
export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);
  return store;
}

UI

智能组件和木偶组件,因为本文主要是介绍Redux,对这个感兴趣的同学可以看一下这篇文章Smart and Dumb Components。本项目中在结构上会把智能组件放在containers中,木偶组件放于components中。

containers

智能组件,会通过react-redux函数提供的connect函数把state和actions转换为旗下木偶组件所需要的props。

/* app/containers/App.js */

import React from 'react';
import SearchBar from '../components/searchBar';
import Content from '../components/content';
import Footer from '../components/footer';
import { connect } from 'react-redux';
import ImmutableRenderMixin from 'react-immutable-render-mixin';
import * as ItemsActions from '../actions';
import { bindActionCreators } from 'redux';

let App = React.createClass({
     mixins: [ImmutableRenderMixin],
     propTypes: {
         items: React.PropTypes.object,
         filter: React.PropTypes.string
     },
     render() {
         let styles = {
             width: '200px',
             margin: '30px auto 0'
         }
         const actions = this.props.actions;
         return (
             <div style={styles}>
                 <h2>Manage Items</h2>
                 <SearchBar filterItem={actions.filterItem}/>
                 <Content items={this.props.items} filter={this.props.filter} deleteItem={actions.deleteItem}/>
                 <Footer addItem={actions.addItem} deleteAll={actions.deleteAll}/>
             </div>
         )
     }
 })

export default connect(state => ({
     items: state.items,
     filter: state.filter
}), dispatch => ({
     actions: bindActionCreators(ItemsActions, dispatch)
}))(App);

components

木偶组件,各司其职,没有什么关于actions和stores的依赖,拿出项目中也可独立使用,甚至可以和别的actions,stores进行绑定。

  • SearchBar:查找Item。
  • Content:控制Items的显示,删除一个Item。
  • Footer:新增Item,删除全部Item。

调试工具

使用redux-devtools调试,为你在开发过程中带来乐趣。

/* app/index.js */

function renderDevTools(store) {
  if (__DEBUG__) {
    let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react');
    return (
      <DebugPanel top right bottom>
        <DevTools store={store} monitor={LogMonitor} />
      </DebugPanel>
    );
  }else {
    return null;
  }
}

React.render(
    <div>
        <Provider store={store}>
            <App />
        </Provider>
        {renderDevTools(store)}
    </div>,
  document.getElementById('app'));
/* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore;

if(__DEBUG__) {
    buildStore = compose(
      applyMiddleware(thunk),
      require('redux-devtools').devTools(),
      require('redux-devtools').persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
    )(createStore)
}else {
    buildStore = compose(applyMiddleware(thunk))(createStore)
}

export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);

  if(module.hot) {
    module.hot.accept('./reducers', () => {
      store.replaceReducer(require('./reducers'));
    });
  }

  return store;
}

在你的代码中加上上面的两段代码,运行npm run debug命令,就可以用调试工具来管理你的项目了。

延伸阅读

Redux Document
Awesome-redux

写在最后

刚接触到Redux和React技术的时候,我几乎是夜夜难以入眠的,技术革新带来的新的思想总是不断的刺激着我的大脑。非常建议你也能来试试Redux,体会我在开发中得到的这种幸福感。

源码前往此处下载

打赏

  • 支付宝
  • 微信

文章来源,根据@孙成瑞_Matthew - 博客整理而来

时间: 2024-08-30 20:50:37

使用Redux管理你的React应用的相关文章

React+Redux打造“NEWS EARLY”单页应用 一步步让你理解最前沿技术栈的真谛

之前写过一篇文章,分享了我利用闲暇时间,使用React+Redux技术栈重构的百度某产品个人中心页面.您可以参考这里,或者参考Github代码仓库地址. 这个工程实例中,我采用了厂内的工程构建工具-FIS,并贯穿了react+redux基本思想. 今天这篇文章给大家分享一个更加复杂,但是非常有趣的一个项目- News Early单页应用. 我把这个项目所有代码托管在了我个人Github之中,感兴趣的读者可以跟我探讨. 最近我发现,React Redux生态圈项目活跃.但是作品质量"良莠不齐&qu

Redux系列02:一个炒鸡简单的react+redux例子

前言 在<Redux系列01:从一个简单例子了解action.store.reducer>里面,我们已经对redux的核心概念做了必要的讲解.接下来,同样是通过一个简单的例子,来讲解如何将redux跟react应用结合起来. 我们知道,在类flux框架设计中,单向数据流转的方向无非如下: 转换成redux的语言,就是这个样子.接下来就看实际例子,一个简单到不存在实用价值的todo list. 例子:实际运行效果 本文的代码示例可以在github上下载,点击查看.README里有详细的运行步骤,

React 的慢与快:优化 React 应用实战

本文讲的是React 的慢与快:优化 React 应用实战, React 是慢的.我的意思是,任何中等规模的 React 应用都是慢的.但是在开始找备选方案之前,你应该明白任何中等规模的 Angular 或 Ember 应用也是慢的.好消息是:如果你在乎性能,使 React 应用变得超级快则相当容易.这篇文章就是案例. 衡量 React 性能 我说的 "慢" 到底是什么意思?举个例子. 我正在为 admin-on-rest 这个开源项目工作,它使用 material-ui 和 Redu

从零学React Native之05混合开发

本篇文章,我们主要讨论如何实现Android平台的混合开发. RN给Android端发送消息 首先打开Android Studio, Open工程, 在React Native项目目录下选择android子目录下的build.gradle文件打开. React Native已经默认帮我们创建好了两个类MainApplication和MainActivity public class MainApplication extends Application implements ReactAppli

[译] 2017 年了,这么多前端框架,你会怎样选择?

本文讲的是[译] 2017 年了,这么多前端框架,你会怎样选择?, 原文地址:Choosing a frontend framework in 2017 原文作者:Taras Mankovski 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:LeviDing 校对者:sunui, warcryDoggie 过去七年来,前端框架生态系统发展蓬勃.我们已经学了很多关于构建和维护大型应用的知识.我们看到了很多新想法的出现.其中一些新想法改变了我们构建 We

[译] setState() 门事件

本文讲的是[译] setState() 门事件, 原文地址:setState() Gate 原文作者:Eric Elliott 译文出自:掘金翻译计划 译者:reid3290 校对者:1992chenlu,qinfanpeng React setState() 解惑 译注:本文起因于作者的一条推特,他认为应该避免使用 setState(),随后引发论战,遂写此文详细阐明其观点.译者个人认为,本文主要在于"撕逼",并未深入介绍 setState() 的技术细节,希望从技术层面深入了解 s

《Angular从零到一》导读

本节书摘来自华章出版社<Angular从零到一>一书中作者Richard Banfield 著 易艺 译   前 言 一个大叔码农的Angular 2创世纪 作为一个出生于20世纪70年代的大叔,我在软件这个领域已经摸爬滚打了16年,从程序员.项目经理.产品经理,项目总监,到部门管理等各个角色都体验过,深深地了解到这个行业发展的速度之快是其他行业无法比拟的:从编程语言.各种平台.各种框架.设计模式到各类开源工具.组件林林总总,要学习的东西实在太多,因为变化太快. 但万变不离其宗,名词变化虽多,

weex vs react-native

前言 weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架. weex的目标在于抹平各个平台的差异性,从而简化应用开发.而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题. 进一步浏览weex和react-native的代码之后,可以得出如下的公式. weex = Vue.js + H5/Native react-native = React

详解vue.js组件化开发实践_javascript技巧

前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然成为了第一个问题.鉴于目前web前端mvvm框架以及组件化开发方式的流行,决定技术栈采用:vue + es6 + 组件化. 这里首先简单说下web前端组件化开发方式的历程: 最早的组件化结构,代码结构可能如下: - lib/components/calendar |- calendar.css |-