Redux管理你的React应用
使用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开发调试工具:redux-devtools
React应用无刷新保存工具:hot-loader
不明真相的群众,可能这里需要我来安利一下Flux数据流的思想,看图:
╔═════════╗ ╔════════╗ ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝ ╚════════╝ ╚═════════════════╝
^ │
└──────────────────────────────────────┘
注意:图片仅仅是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的小例子。你可以在这里找到全部的源代码:https://github.com/matthew-sun/redux-example。
快速查看
1.git clone git@github.com:matthew-sun/redux-example.git 2.npm install && npm start 3.open localhost:3000 |
目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
. +-- 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 +-- css | +-- pure.min.css +-- index.html |
Index.js
在入口文件中,我们需要把App和redux建立起联系。Provider是react-redux提供的组件,它的作用是把store和视图绑定在了一起,这里的Store就是那个唯一的State树。当Store发生改变的时候,整个App就可以作出对应的变化。{() => }是声明了一个返回的函数传进Provider的props.children里,这个方法将会在React的 0.14版本得到简化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* 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相等的常量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* app/constants/actionTypes.js */ import keyMirror from 'react/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),也强烈推荐这样去做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/* 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的方法中出现过,只是不太常用。简单快速的用代码样例来回顾一下:
1
2
3
4
5
6
7
8
9
10
11
|
/* 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没有发生改变的组件子树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/* 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了。
1
2
3
4
5
6
7
8
9
10
11
12
|
/* 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为例子:
1
2
3
4
5
6
7
8
|
/* 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的机会。
1
2
3
4
5
6
7
8
9
10
|
/* 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) { return buildStore(rootReducer, initialState); } |
UI
智能组件和木偶组件,因为本文主要是介绍Redux,对这个感兴趣的同学可以看一下这篇文章Smart and Dumb Components。本项目中在结构上会把智能组件放在containers中,木偶组件放于components中。
containers
智能组件,会通过react-redux函数提供的connect函数把state和actions转换为旗下木偶组件所需要的props。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/* 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调试,为你在开发过程中带来乐趣。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/* 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 */ 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) { return buildStore(rootReducer, initialState); } |
在你的代码中加上上面的两段代码,运行npm run debug命令,就可以用调试工具来管理你的项目了。
延伸阅读
写在最后
刚接触到Redux和React技术的时候,我几乎是夜夜难以入眠的,技术革新带来的新的思想总是不断的刺激着我的大脑。非常建议你也能来试试Redux,体会我在开发中得到的这种幸福感。
Redux管理你的React应用的更多相关文章
- 使用Redux管理你的React应用
因为redux和react的版本更新的比较频繁,博客园这里用的redux版本是1.0.1,如果你关心最新版本的使用技巧,欢迎来我的Github查看(https://github.com/matthew ...
- 使用Redux管理你的React应用(转载)
本文转载自: http://www.cnblogs.com/matthewsun/p/4773646.html
- 使用Redux管理React数据流要点浅析
在图中,使用Redux管理React数据流的过程如图所示,Store作为唯一的state树,管理所有组件的state.组件所有的行为通过Actions来触发,然后Action更新Store中的stat ...
- 前端(十):使用redux管理数据
react本身能够完成动态数据的监听和更新,如果不是必要可以不适用redux. 安装redux: cnpm install redux --save,或者yarn add redux. 一.react ...
- angular8 + redux 管理状态
1. angular8.1.1 ----- package.json { "name": "angular-demo", "version" ...
- react,react-router,redux+react-redux 构建一个React Demo
创建初始化应用 加速我们的npm. npm install -g cnpm --registry=https://registry.npm.taobao.org 利用create-react-app ...
- 借鉴redux,实现一个react状态管理方案
react状态管理方案有很多,其中最简单的最常用的是redux. redux实现 redux做状态管理,是利用reducer和action实现的state的更新. 如果想要用redux,需要几个步骤 ...
- Redux 管理React Native数据
现在让我们看看大致的流程: React 可以触发 Action,比如按钮点击按钮. Action 是对象,包含一个类型以及相关的数据,通过 Store 的 dispatch() 函数发送到 Store ...
- Redux学习(3) ----- 结合React使用
Redux 和React 进行结合, 就是用React 做UI, 因为Redux中定义了state,并且定义了改变或获取state的方法,完全可以用来进行状态管理,React中就不用保存状态了,它只要 ...
随机推荐
- NDK/JNI学习--进口hello-jniproject
上一篇文章,简单的设置NDK开发环境,本文通过导入C:\android-ndk-r9d\samples\hello-jni(NDK自带example)来疏通真个环境. 打开Eclipse IDE.指定 ...
- javascript推断的浏览器类型
<script> window["MzBrowser"]={};(function() { if(MzBrowser.platform) return; var ua ...
- 站点搭建从零開始(四) server的配置
原文发表在我的个人站点,欢迎大家訪问~转载请保留本段,或注明原文:http://www.hainter.com/website-primer-4 域名解析设置好了,通常我们就能够訪问我们的网站了,上篇 ...
- 【JAVA高级】——myEclipse连接mysql启动数据库服务
背景: DRP项目要求使用Oracle数据库,但眼下因为种种原因,临时还装不了Oracle.但也不能闲着啊,就拿mysql来试试.安装完mysql以后,使用myEclipse连接数据库,就一直报错,报 ...
- 生活中的大数据 hadoop
大数据和我有关吗?大数据就是大量的数据吗?只有互联网公司才有大数据吗?想盘活大数据必须买昂贵的软硬件吗?大数据怎么存储计算?大数据,这个时下最火热的互联网词语,你了解多少呢?
- Cocos2dx 3.0开发环境的搭建--Eclipse建立在Android工程
一.前言: 这部分描述了Cocos2d-x 3.0的一些基础内容,以及在Eclipse上上编译我们的Cocos2d-x项目,成功把Helloworld执行起来了.看完本篇博客之后.你就会知道Cocos ...
- js操作string它substr方法
substr 方法 从指定的指定起始位置的长度返回一个子. stringvar.substr(start [, length ]) 參数 stringvar 必选项. 要提取子字符串的字符串文字或 S ...
- Xamarin.Android 入门实例(1)之获取与解析JSON
1.Main.axml 视图界面 2.视图代码 <?xml version="1.0" encoding="utf-8"?> <LinearL ...
- UML学习(一)类图和对象图
对象是一个概念,一种抽象或者事物.对象能够是具有现实意义的事物,也能够是抽象的一个概念.比方,一家公司或者一个进程. 类是一组对象的集合或者抽象的概念.类具有同样的属性和方法. 介绍完基本对象和类的基 ...
- SecureCRT 6.7.1 RI和谐 皴 补丁 方法
它之前被使用SecureCRT 6.5.3 版本号,咋看和谐补丁,即使中国版本也可(现在才发现SecureCRT.6.2.0) 可是换为 6.7.1 后就怎么也注冊不了了.. 没办法试了各种办法: 先 ...