今天,我们通过解读官方示例代码(counter)的方式来学习React+Redux。
例子
这个例子是官方的例子,计数器程序。前两个按钮是加减,第三个是如果当前数字是奇数则加一,第四个按钮是异步加一(延迟一秒)。
源代码:
https://github.com/lewis617/react-redux-tutorial/tree/master/redux-examples/counter
组件 components/Counter.js
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 import React , { Component , PropTypes } from 'react' class Counter extends Component { render ( ) { const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this .props ; return ( <p > Clicked: {counter} times {' '} <button onClick ={increment} > +</button > {' '} <button onClick ={decrement} > -</button > {' '} <button onClick ={incrementIfOdd} > Increment if odd</button > {' '} <button onClick ={() => incrementAsync()}>Increment async</button > </p > ) } } Counter .propTypes = { increment : PropTypes .func .isRequired , incrementIfOdd : PropTypes .func .isRequired , incrementAsync : PropTypes .func .isRequired , decrement : PropTypes .func .isRequired , counter : PropTypes .number .isRequired }; export default Counter
上述代码,我们干了几件事:
从props中导入变量和方法
渲染组件
有的同学可能会急于想知道props的方法和变量是怎么来,下面我们继续解读。
容器 containers/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import Counter from '../components/Counter' import * as CounterActions from '../actions/counter' function mapStateToProps (state ) { return { counter : state.counter } } function mapDispatchToProps (dispatch ) { return bindActionCreators (CounterActions , dispatch) } export default connect (mapStateToProps, mapDispatchToProps)(Counter )
看到这里,很多刚接触Redux同学可能已经晕了,我来图解下Redux的流程。
state就是数据,组件就是数据的呈现形式,action是动作,action是通过reducer来更新state的。
上述代码,我们干了几件事:
把state
的counter
值绑定到props上
把四个action创建函数绑定到props上
connect 那么为什么就绑定上去了呢?因为有connect
这个方法。这个方法是如何实现的,或者我们该怎么用这个方法呢?connect
这个方法的用法,可以直接看api文档 。我也可以简单描述一下:
第一个参数,必须是function,作用是绑定state的指定值到props上面。这里绑定的是counter
第二个参数,可以是function,也可以是对象,作用是绑定action创建函数到props上。
返回值,是绑定后的组件
这里还有很多种其他写法,我喜欢在第二个参数绑定一个对象,即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import Counter from '../components/Counter' import * as CounterActions from '../actions/counter' function mapStateToProps (state ) { return { counter : state.counter } } export default connect (mapStateToProps, CounterActions )(Counter )
还可以不写第二个参数,后面用dispatch来触发action的方法,即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import Counter from '../components/Counter' import * as CounterActions from '../actions/counter' function mapStateToProps (state ) { return { counter : state.counter } } export default connect (mapStateToProps)(Counter )
后面在组件中直接使用dispatch()
来触发action创建函数。
action和reducer两个好基友负责更新state actions/counter.js
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 export const INCREMENT_COUNTER = 'INCREMENT_COUNTER' export const DECREMENT_COUNTER = 'DECREMENT_COUNTER' export function increment ( ) { return { type : INCREMENT_COUNTER } } export function decrement ( ) { return { type : DECREMENT_COUNTER } } export function incrementIfOdd ( ) { return (dispatch, getState ) => { const { counter } = getState () if (counter % 2 === 0 ) { return } dispatch (increment ()) } } export function incrementAsync (delay = 1000 ) { return dispatch => { setTimeout (() => { dispatch (increment ()) }, delay) } }
reducers/counter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 import { INCREMENT_COUNTER , DECREMENT_COUNTER } from '../actions/counter' export default function counter (state = 0 , action ) { switch (action.type ) { case INCREMENT_COUNTER : return state + 1 case DECREMENT_COUNTER : return state - 1 default : return state } }
reducers/index.js
1 2 3 4 5 6 7 8 9 import { combineReducers } from 'redux' import counter from './counter' const rootReducer = combineReducers ({ counter }) export default rootReducer
上述代码我们干了几件事:
写了四个action创建函数
写了reducer用于更新state
将所有reducer(这里只有一个)打包成一个reducer
看到这里,有很多初次接触redux的同学可能已经晕了,怎么那么多概念?为了形象直观,我们在开发工具(React dev tools)上看看这些state,props什么的:
action的方法和state的变量是不是都绑定上去了啊。state怎么看呢?这个需要借助Redux的开发工具,也可以通过Connect(Counter)
组件的State来查看redux那颗全局唯一的状态树:
那个storeState
就是全局唯一的状态树。我们可以看到只有一个counter
而已。
注册store store/configureStore.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducer from '../reducers' const createStoreWithMiddleware = applyMiddleware ( thunk )(createStore) export default function configureStore (initialState ) { const store = createStoreWithMiddleware (reducer, initialState) if (module .hot ) { module .hot .accept ('../reducers' , () => { const nextReducer = require ('../reducers' ) store.replaceReducer (nextReducer) }) } return store }
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import App from './containers/App' import configureStore from './store/configureStore' const store = configureStore ()render ( <Provider store ={store} > <App /> </Provider > , document .getElementById ('root' ) )
上述代码,我们干了几件事:
用中间件使action创建函数可以返回一个function代替一个action对象
如果在热替换状态(Webpack hot module replacement)下,允许替换reducer
导出store
将store
放进Provider
将Provider
放在组件顶层,并渲染
applyMiddleware、thunk applyMiddleware
来自Redux可以包装 store 的 dispatch()
thunk
作用使action创建函数可以返回一个function代替一个action对象
服务 server.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var webpack = require ('webpack' )var webpackDevMiddleware = require ('webpack-dev-middleware' )var webpackHotMiddleware = require ('webpack-hot-middleware' )var config = require ('./webpack.config' )var app = new (require ('express' ))()var port = 3000 var compiler = webpack (config)app.use (webpackDevMiddleware (compiler, { noInfo : true , publicPath : config.output .publicPath })) app.use (webpackHotMiddleware (compiler)) app.get ("/" , function (req, res ) { res.sendFile (__dirname + '/index.html' ) }) app.listen (port, function (error ) { if (error) { console .error (error) } else { console .info ("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser." , port, port) } })
webpack.config.js
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 var path = require ('path' )var webpack = require ('webpack' )module .exports = { devtool : 'cheap-module-eval-source-map' , entry : [ 'webpack-hot-middleware/client' , './index' ], output : { path : path.join (__dirname, 'dist' ), filename : 'bundle.js' , publicPath : '/static/' }, plugins : [ new webpack.optimize .OccurenceOrderPlugin (), new webpack.HotModuleReplacementPlugin (), new webpack.NoErrorsPlugin () ], module : { loaders : [ { test : /\.js$/ , loaders : [ 'babel' ], exclude : /node_modules/ , include : __dirname } ] } }
npm start
后执行node server
,触发webpack。webpack插件功能如下:
OccurenceOrderPlugin
的作用是给经常使用的模块分配最小长度的id。
HotModuleReplacementPlugin
是热替换,热替换和dev-server的hot
有什么区别?不用刷新页面,可用于生产环境。
NoErrorsPlugin
用于保证编译后的代码永远是对的,因为不对的话会自动停掉。
webpackHotMiddleware server.js中webpackHotMiddleware
的用法是参考官网 的,没有为什么,Express中间件就是在请求后执行某些操作。
教程示例代码及目录 示例代码:https://github.com/lewis617/react-redux-tutorial
目录:http://www.liuyiqi.cn/tags/React/
发布时间:
2016年1月20日 - 07时01分