现在在生产环境中使用redux reselect使用合适吗

-产品/服务
-文章或作者
& 选择展区 &&
您的位置:& &
& 产品介绍
-产品/服务
-文章或作者
Lee Spring 力司百灵弹簧(苏州)有限公司
REDUX™ 波形弹簧
在空间狭小的场合可实现最佳性能
顾客建议我们公司扩大库存压缩弹簧产品的种类,以便囊括那些既适合安装到较小的组件上,同时又满足其他所有性能标准的弹簧。力司百灵弹簧公司的解决办法是,新推出了REDUX™ 波形弹簧,目的是在要求较小负荷变形的关键性环境中,REDUX™ 波形弹簧可以取代传统的圆钢丝压缩弹簧。
REDUX™ 波形弹簧波形弹簧的独到之处在于:就工作量而言,REDUX™ 波形弹簧所占据的空间相对狭小。事实上,REDUX™ 波形弹簧可使组件的尺寸和重量下降最多达 50%。REDUX™ 波形弹簧由 17-7 不锈钢单股细丝制成,包含连续的精密簧圈,且直径和波形一致。直径范围是:0.375 英寸(9.52 毫米)至 1.25 英寸(31.75 毫米),以便满足负荷要求:4 磅(1.8 千克)至 30 磅(13.6 千克)。
参考价格:-
Lee Spring 力司百灵弹簧(苏州)有限公司
电话:86-512-
江苏·苏州市工业园区东宏路15号1幢2楼
我要求购:
留言不能少于8个字符,广告性质的留言将被删除。
对 波浪弹簧 有何见解?请到
畅所欲言吧!
佳工机电网·嘉工科技现在项目已经有了,但是要把它放到生产环境中还是有些事情要做,在这最后一节,来把它们一一搞定。
打包部署文件
我们的源代码是没法直接跑起来的。ES6语法大部分浏览器还不完全支持,有些浏览器完全不支持。而less、sass这些样式框架就更不用说了。另外对这些代码最好进行压缩,以获得更快的访问速度。所以在正式发布这些代码前必须先要编译打包。webpack可是干这个的以大能手,看名字就知道了。那要怎么打包呢?终端执行:
npm run dist
现在我们的项目目录里多出了一个名为dist的文件夹,这里面就是要部署的全部内容。由于generator-react-webpack-redux已经为我们做好了webpack的一些配置,所以我们看到打包好的文件已经经过了压缩混淆。
服务器设置
如果我们在使用react-router的时候选择了浏览器历史管理方式,那么服务器必须要能够正确处理各种路径。实际上我们的应用只有一个页面文件,在访问各种有效路径的时候,服务都应该返回那唯一的页面。在开发过程中,我们通过npm start指令启动了一个node服务,它已经处理好了这些路由。但是在实际生产环境中,我们往往会使用一个静态服务器,比如nginx或apache。如果把刚才打包好的dist目录扔给nginx,你会发现只有根路径可以访问,通过点击跳转到各个路由没问题(也就是通过react-router控制的跳转),要直接在浏览器的地址栏输入"http://localhost/news"这样的自路径就404了。现在以nginx为例来配置好适合我们应用的路由。
我们所需配置的内容都在http & server节点下。
首先考虑对诸如/news这样的路径并不存在对应的页面文件,所以对于未知路径要都给打发到根路径下:
location / {
/Users/someone/my-project/dist;
index.html index.htm;
try_files $uri /index.html;
这样,我们在地址栏输入"http://localhost/news"以后,nginx没有找到news.html,它就尝试找index.html,inedex.html打开后,我们的代码就生效了,react-router看到地址栏里的路径是/news,它就会在一开始去匹配/news,并改变状态。
至于脚本、图片这些静态文件我们不用处理,因为nginx按照路径就可以直接找到这些文件。另外就是把后端服务的接口处理好,nginx代理tomcat这些后端服务是很常见的配置,只要注意在路径上服务和页面要能明显区分开,比如所有的后端服务接口都有.do后缀,这样配置就行了:
location ~*.do$ {
proxy_pass
http://192.168.1.1:8088;
分离样式文件
尽管在示例代码里我把样式都写成内联形式的了,但我还是建议写单独的样式文件。前面也提到过,样式文件可以直接在js代码中引入,这对于构造独立的模块非常方便。但是在默认状态下,我们会发现导出的文件没有css文件,实际上导入的样式是在代码运行时加到页面上的style标签里的。这样页面渲染性能不太好,而且会增大js文件的体积,最好还是把它拿出来。万能的npm里有专干这个的webpack插件,来把它装上先:
npm install extract-text-webpack-plugin --save-dev
然后要修改一下webpack的配置文件。由于这个插件只有在打包的时候才会用到,所以我们只改cfg/dist.js文件。引入这个插件,然后在plugins数组里添加相应的项目:
let ExtractTextPlugin = require('extract-text-webpack-plugin');
let config = _.merge({
plugins: [
new ExtractTextPlugin('app.css')
还要改一下loader。原本loader是写在cfg/base.js里面的,但是在开发环境中我们用不到这个插件,而如果使用了插件提供的loader就会报错,所以我们在dist.js里面把config.module.loaders数组覆盖。假如我们的项目里用到了css和less两种样式文件,就在config.module.loaders.push这一段前面添加如下代码:
config.module.loaders = [
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
test: /\.less/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader')
test: /\.(png|jpg|gif|woff|woff2)$/,
loader: 'url-loader?limit=8192'
这里除了两种样式文件的loader以外,还把base里的一个非样式的loader给带过来了,别把它忽略了,它很有用,一会儿再说。
现在再运行npm run dist,可以看到asset文件夹里多了一个app.css文件。别忘了在index.html文件里面引入新生成的样式文件。
webpack让我们可以在js代码中引入图片并使用,引入图片只需一个简单的require语句:
let logo = require('../images/logo.png');
然后可以像使用其它变量一样来使用这个图片:
return img src={logo}&
你可能觉得,一个图片直接用它的路径就行了,何必要装模作样的引入呢?我认为有这么做两个好处:
首先还是模块化。如果一个组件需要用到图片,在这个组件文件内引入图片,图片会在run dist时一并打包,不用担心图片丢失。
其次很多服务器会对图片进行CDN缓存,如果你替换了一张图片,很可能它在一段时间内不会生效,而通过webpack引入的图片是一内联base64或者重命名为唯一hash文件名的形式打包的,这样就不会出现恼人的缓存情况。
不只是在js中引入图片会被webpack处理,css里的图片也会被同样的方式处理。
如果你已经在你的项目里加上了几个小图片,你可能会发现打包后并没有看到图片或者图片比原来少,这是因为有一个临界值,低于它的图片会直接转成base64写在导出的js文件里。这样也好也不好,好处是图片在一开始就被载入,后面不会出现图片延后载入的效果,用户体验很好,不好就是base64比原图片大小更大,如果图片比较多,导出的js文件就会太大,让用户初始等待时间过长。所以我们要权衡利弊设置一个合适的临界值。前面我们在dist.js配置文件中重写loaders的时候把base里的一个loader带了过来,它就是干这个用的,test属性的正则表达式表明我们想让webpack处理什么格式的图片,loader属性最后的数字就是内联图片临界值,单位是字节。我们把它设置成1K吧:
test: /\.(png|jpg|gif|woff|woff2)$/,
loader: 'url-loader?limit=1024'
我们的目标是单页应用,但是当项目规模比较大的时候整个项目可能会被拆分成多个单页应用。拆分多个应用的关键在于要有多个入口文件。目前我们的项目只有一个入口文件:src/index.js。来看cfg/dist.js文件,里面的config对象中entry属性的值现在是一个index.js路径字符串。entry的值也可以是一个对象,这样就可以声明多个入口文件,对象的key对应着文件名。比如我们想要增加一个入口文件src/test.js,先搞点很简单的内容:
import React from 'react';
import { render } from 'react-dom';
div&TESTdiv&,
document.getElementById('app')
把cfg/dist.js中的config.entry改成这样:
app: path.join(__dirname, '../src/index'),
test: path.join(__dirname, '../src/test')
现在明确指定了两个入口文件,然后还要修改config.output.filename:
config.output.filename = '[name].js'
输出文件时,name会自动对应成entry中的key。执行npm run dist,现在asset目录中多出了个test.js。
使用这个文件需要另一个单独的页面,如果我们用静态html页面的话,要把页面路径添加到项目根目录下的package.json中,在scripts对象中有个copy属性,加到里面就行了,这样才能在run dist的时候把它一并拷贝到dist目录里。
最后,也许你还要修改一下nginx配置,让test路径单独匹配。
分离第三方库
你可能发现了刚才我们把文件分成多个入口时,新入口文件即使内容非常少,哪怕只渲染了一个div,生成的文件大小还有上百k。里面其实主要都是第三方库。这太不优雅了,既然这些第三方库几乎会被所有的应用重复使用,一定得把他们单拎出来。于是我们需要一个插件:CommonsChunkPlugin。这个插件不用单独安装了,它被包含在webpact.optimize里面。我们打算再输出一个叫commons.js的文件,包含全部第三方库。在cfg/dist.js的plugins数组里面添加这个插件:
new webpack.optimize.CommonsChunkPlugin('commons', 'commons.js')
然后在entry对象里面再添加一个commons属性,它的值是一个数组,包含所有我们想要拎出来的库:
app: path.join(__dirname, '../src/index'),
test: path.join(__dirname, '../src/test'),
commons: [
'react-dom',
'react-redux',
'react-router',
'redux-thunk'
OK,输出的文件多了个commons.js,而app.js和test.js比原来小了很多。这回优雅了。别忘了在所有的页面里都把commons.js引进去。
当项目非常大的时候,拆分多个入口文件是一种方案,还有一种方案是按需加载,也就是懒加载或异步加载。我们可以让用户真正进入一个路由时才把对应的组件加载进来,要实现这个非常简单,只需要一个webpack的loader:react-router-loader,先用npm把它安装上,然后修改src/routs.js文件,比如我们现在想让登录页面懒加载,那就把登录页面的路由改成这样:
Route path="login" component={require('react-router!./containers/Login')}/&
编译打包后,又多出了一个1.1.js文件,这就是在进入登录路由时要加载的文件,也就是单独的登录组件。其它的就不用我们管了,代码会自动处理的。
既然是按需加载,我们一定是希望初始的时候加载的代码尽量少,尽可能在进入某个路由时才载入相应的全部内容。我们的代码大致就三类东西:组件、action和reducer。组件很明显可以是独立载入的。reducer恐怕没办法,因为它需要指导整个仓库状态的建立。至于action,我们前面的示例代码是不独立的,因为reducer要依赖action文件里面的常量,我们只需要把所有的常量提出到一个公共的文件中,只有组件引用action文件。比如我们新建一个src/consts.js文件,内容是:
export const INPUT_USERNAME = 'INPUT_USERNAME'
export const INPUT_PASSWORD = 'INPUT_PASSWORD'
export const RECEIVE_NEWS_LIST = 'RECEIVE_NEWS_LIST'
export const SET_KEYWORD = 'SET_KEYWORD'
然后还以login为例,把src/reducers/login.js里面引入常量的目标改为consts.js:
import {INPUT_USERNAME, INPUT_PASSWORD} from '../consts'
src/actions/login.js里也这样引入常量。run dist后,1.1.js文件就包含了actions/login.js里面的内容。
添加hash后缀
在一个大型且需要频繁升级的项目中,静态文件往往需要添加hash后缀,这主要是出于两个原因:一个是所有版本的静态文件可以同时存在,而页面由后端控制,后端根据接口的版本绑定js和css文件,这样便于升级和回滚。另一个是防止缓存,这和前面图片重命名为hash值是一个道理。
让webpack为文件名添加后缀非常简单,只需要在输出的文件名上加上[hash]就可以了。比如我们想让app.js带上hash后缀,只需要在cfg/dist.js最后一句前面加上一句:
config.output.filename = 'app.[hash].js'
而对于插件生成的样式文件和公共js文件同样也是在文件名上加上[hash]就行了。
现在关键的问题是怎么应用这些有了hash后缀的文件。总不能每打一次包我们就手动改一下index.html把。
webpack的配置文件是js,这就意味着这个配置文件是活的,我们可以很容易把想做的事情通过代码实现。现在我要在每次打包后把index.html文件引入的js和css文件自动替换成带hash尾巴的形式,只需添加一个自己写的插件,其实就是一个函数。在cfg/dist.js里面的plugins数组里添加以下函数:
function() {
this.plugin("done", function(stats) {
let htmlPath = path.join(__dirname, '../dist/index.html')
let htmlText = fs.readFileSync(htmlPath, {encoding:'utf-8'})
let assets = stats.toJson().assetsByChunkName
Object.keys(assets).forEach((key)=&{
let fileNames = assets[key];
['js', 'css'].forEach(function(ext){
htmlText = htmlText.replace(key+'.'+ext, fileNames.find(function(item){
return new RegExp(key+'\\.\\w+\\.'+ext+'$').test(item)
fs.writeFileSync( htmlPath, htmlText)
很暴力,就是赤裸裸的node操作文件系统。这回dist文件夹中的index.html里引入的脚本和样式都是带hash的了。
在很多项目中,我们前端要提供的可能不是一个引用好js和css的html文件,而是一个map文件,里面有静态文件的版本信息(hash值),这样后端就能直接把需要的静态文件挂上。可以自己写一个跟上面代码类似的插件输出一个map文件,也可在万能的npm找个插件,比如map-json-webpack-plugin。上面那个功能也可以试试replace-webpack-plugin。
到这里,这一系列关于react的博客就算告一段落了。其实我还想写一个关于测试的,因为react+redux的这种模式非常利于测试,不过我还在琢磨测试当中,等琢磨得差不多了也许会补上一篇。
阅读(...) 评论()热门文章最新文章&&&&违法和不良信息举报电话:183-
举报邮箱:
Copyright(C)2016 大不六文章网
京公网安备78后使用快捷导航没有帐号?
Redux系列x:源码分析
摘要: 写在前面redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分比较直观。这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档。
  此外,源 ... ... ...
  写在前面
  redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分比较直观。
  这里假设读者对redux有一定了解,就不科普redux的概念和啥的啦,这部分建议直接看官方文档。
  此外,源码解析的中文批注版已上传至github,可点击查看。本文相关示例代码,可点击查看。
  源码解析概览
  将redux下载下来,然后看下他的目录结构。
npm install redux复制代码
  这里我们需要关心的主要是src目录,源码解析需要关心的文件都在这里面了
  index.js:redux主文件,主要对外暴露了几个核心API
  createStore.js:createStore 方法的定义
  utils:各种工具方法,其中applyMiddleware、combineReducers、bindActionCreators 为redux的几个核心方法,剩余的pick、mapValue、compose为普通的工具函数
?&&src git:(master) ? tree
├── createStore.js
├── index.js
└── utils
& & ├── applyMiddleware.js
& & ├── bindActionCreators.js
& & ├── combineReducers.js
& & ├── compose.js
& & ├── isPlainObject.js
& & ├── mapValues.js
& & └── pick.js复制代码
  源码解析:index.js
  超级简单,暴露了几个核心API,没了
mport createStore from './createStore';
import combineReducers from './utils/combineReducers';
import bindActionCreators from './utils/bindActionCreators';
import applyMiddleware from './utils/applyMiddleware';
import compose from './utils/compose';
&&createStore,
&&combineReducers,
&&bindActionCreators,
&&applyMiddleware,
};复制代码
  源码解析:createStore.js
  直接贴上源代码,并进行简单注解。看下redux.createStore(reducer, initialState)调用的文档说明,基本就能够看懂下面代码了。
  特别强调:虽然在几个文件里,createStore.js的代码行数是最多的,但却是最容易读懂的。下面几点比较关键
  1.redux.createStore(reducer, initialState) 传入了reducer、initialState,并返回一个store对象。
  2.store对象对外暴露了dispatch、getState、subscribe方法
  3.store对象通过getState() 获取内部状态
  4.initialState为 store 的初始状态,如果不传则为undefined
  5.store对象通过reducer来修改内部状态
  6.store对象创建的时候,内部会主动调用dispatch({ type: ActionTypes.INIT });来对内部状态进行初始化。通过断点或者日志打印就可以看到,store对象创建的同时,reducer就会被调用进行初始化。
import isPlainObject from './utils/isPlainObject';
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
// 初始化的时候(redux.createStore(reducer, initialState)时),传的action.type 就是这货啦
export var ActionTypes = {
&&INIT: '@@redux/INIT'
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
* @param {any} [initialState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
export default function createStore(reducer, initialState) {
&&if (typeof reducer !== 'function') {
& & throw new Error('Expected the reducer to be a function.');
&&var currentReducer =
&&var currentState = initialS
&&var listeners = [];
&&var isDispatching =
& &* Reads the state tree managed by the store.
& &* @returns {any} The current state tree of your application.
&&// 这个方法没什么好讲的,返回当前的state
&&function getState() {
& & return currentS
& &* Adds a change listener. It will be called any time an action is dispatched,
& &* and some part of the state tree may potentially have changed. You may then
& &* call `getState()` to read the current state tree inside the callback.
& &* @param {Function} listener A callback to be invoked on every dispatch.
& &* @returns {Function} A function to remove this change listener.
&&// 很常见的监听函数添加方式,当store.dispatch 的时候被调用
&&// store.subscribe(listener) 返回一个方法(unscribe),可以用来取消监听
&&function subscribe(listener) {
& & listeners.push(listener);
& & var isSubscribed =
& & return function unsubscribe() {
& && &if (!isSubscribed) {
& && &isSubscribed =
& && &var index = listeners.indexOf(listener);
& && &listeners.splice(index, 1);
& &* Dispatches an action. It is the only way to trigger a state change.
& &* The `reducer` function, used to create the store, will be called with the
& &* current state tree and the given `action`. Its return value will
& &* be considered the **next** state of the tree, and the change listeners
& &* will be notified.
& &* The base implementation only supports plain object actions. If you want to
& &* dispatch a Promise, an Observable, a thunk, or something else, you need to
& &* wrap your store creating function into the corresponding middleware. For
& &* example, see the documentation for the `redux-thunk` package. Even the
& &* middleware will eventually dispatch plain object actions using this method.
& &* @param {Object} action A plain object representing “what changed”. It is
& &* a good idea to keep actions serializable so you can record and replay user
& &* sessions, or use the time travelling `redux-devtools`. An action must have
& &* a `type` property which may not be `undefined`. It is a good idea to use
& &* string constants for action types.
& &* @returns {Object} For convenience, the same action object you dispatched.
& &* Note that, if you use a custom middleware, it may wrap `dispatch()` to
& &* return something else (for example, a Promise you can await).
&&// 以下情况会报错
&&// 1. 传入的action不是一个对象
&&// 2. 传入的action是个对象,但是action.type 是undefined
&&function dispatch(action) {
& & if (!isPlainObject(action)) {
& && &throw new Error(
& && &&&'Actions must be plain objects. ' +
& && &&&'Use custom middleware for async actions.'
& & if (typeof action.type === 'undefined') {
& && &throw new Error(
& && &&&'Actions may not have an undefined "type" property. ' +
& && &&&'Have you misspelled a constant?'
& & if (isDispatching) {
& && &throw new Error('Reducers may not dispatch actions.');
& && &isDispatching =
& && &// 就是这一句啦, 将 currentState 设置为 reducer(currentState, action) 返回的值
& && &currentState = currentReducer(currentState, action);
& & } finally {
& && &isDispatching =
& & // 如果有监听函数,就顺序调用
& & listeners.slice().forEach(listener =& listener());
& & // 最后,返回传入的action
& &* Replaces the reducer currently used by the store to calculate the state.
& &* You might need this if your app implements code splitting and you want to
& &* load some of the reducers dynamically. You might also need this if you
& &* implement a hot reloading mechanism for Redux.
& &* @param {Function} nextReducer The reducer for the store to use instead.
& &* @returns {void}
&&function replaceReducer(nextReducer) {
& & currentReducer = nextR
& & dispatch({ type: ActionTypes.INIT });
&&// When a store is created, an "INIT" action is dispatched so that every
&&// reducer returns their initial state. This effectively populates
&&// the initial state tree.
&&// redux.createStore(reducer, initialState) 的时候, 内部会 自己调用 dispatch({ type: ActionTypes.INIT });
&&// 来完成state的初始化
&&dispatch({ type: ActionTypes.INIT });
&&// 返回的就是这个东东了,只有四个方法
&&return {
& & dispatch,
& & subscribe,
& & getState,
& & replaceReducer
  源码解析:combineReducers.js
  bineReducers(reducerMap) 的作用在于合并多个reducer函数,并返回一个新的reducer函数。因此可以看到,combineReducers 返回了一个函数,并且该函数的参数同样是state、reducer。
  可以先看伪代码感受下,最终 store.getState() 返回的state,大概会是这么个样子{todos: xx, filter: xx}。简单的说,state被拆分成了两份,TodoReducer的返回值赋值给了state.todos,FilterReducer的返回值赋值给了state.filter。
function TodoReducer(state, action) {}
function FilterReducer(state, action) {}
var finalReducers = bineReducers({
& & todos: TodoReducer,
& & filter: FilterReducer
});复制代码
  同样是直接上注解后的代码,记住几个关键就差不多了:
  combineReducers(reducerMap) 传入一个对象,并返回一个全新的reducer。调用方式跟跟普通的reducer一样,也是传入state、action。
  通过combineReducers,对 store 的状态state进行拆分,reducerMap的key,就是 state 的key,而 调用对应的reducer返回的值,则是这个key对应的值。如上面的例子,state.todos == TodoReducer(state, action)
  redux.createStore(finalReducers, initialState) 调用时,同样会对 state 进行初始化。这个初始化跟通过普通的reducer进行初始化没多大区别。举例来说,如果 initialState.todos = undefined,那么 TodoReducer(state, action) 初始传入的state就是undefined;如果initialState.todos = [],那么 TodoReducer(state, action) 初始传入的state就是[];
  store.dispatch(action),finalReducers 里面,会遍历整个reducerMap,依次调用每个reducer,并将每个reducer返回的子state赋给state对应的key。
import { ActionTypes } from '../createStore';
import isPlainObject from '../utils/isPlainObject';
import mapValues from '../utils/mapValues';
import pick from '../utils/pick';
/* eslint-disable no-console */
function getUndefinedStateErrorMessage(key, action) {
&&var actionType = action && action.
&&var actionName = actionType && `"${actionType.toString()}"` || 'an action';
&&return (
& & `Reducer "${key}" returned undefined handling ${actionName}. ` +
& & `To ignore an action, you must explicitly return the previous state.`
function getUnexpectedStateKeyWarningMessage(inputState, outputState, action) {
&&var reducerKeys = Object.keys(outputState);
&&var argumentName = action && action.type === ActionTypes.INIT ?
& & 'initialState argument passed to createStore' :
& & 'previous state received by the reducer';
&&if (reducerKeys.length === 0) {
& & return (
& && &'Store does not have a valid reducer. Make sure the argument passed ' +
& && &'to combineReducers is an object whose values are reducers.'
&&if (!isPlainObject(inputState)) {
& & return (
& && &`The ${argumentName} has unexpected type of "` +
& && &({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
& && &`". Expected argument to be an object with the following ` +
& && &`keys: "${reducerKeys.join('", "')}"`
&&var unexpectedKeys = Object.keys(inputState).filter(
& & key =& reducerKeys.indexOf(key) & 0
&&if (unexpectedKeys.length & 0) {
& & return (
& && &`Unexpected ${unexpectedKeys.length & 1 ? 'keys' : 'key'} ` +
& && &`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
& && &`Expected to find one of the known reducer keys instead: ` +
& && &`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
// 对reducer做合法性检测
// store = Redux.createStore(reducer, initialState) --&
// currentState = initialState
// currentState = currentReducer(currentState, action);
// 从调用关系,调用时机来看, store.getState() 的初始值(currentState)
// 为 currentReducer(initialState, { type: ActionTypes.INIT })
// 1. 在初始化阶段,reducer 传入的 state 值是 undefined,此时,需要返回初始state,且初始state不能为undefined
// 2. 当传入不认识的 actionType 时, reducer(state, {type}) 返回的不能是undefined
// 3. redux/ 这个 namespace 下的action 不应该做处理,直接返回 currentState 就行 (谁运气这么差会去用这种actionType...)
function assertReducerSanity(reducers) {
&&Object.keys(reducers).forEach(key =& {
& & var reducer = reducers[key];
& & var initialState = reducer(undefined, { type: ActionTypes.INIT });
& & if (typeof initialState === 'undefined') {
& && &throw new Error(
& && &&&`Reducer "${key}" returned undefined during initialization. ` +
& && &&&`If the state passed to the reducer is undefined, you must ` +
& && &&&`explicitly return the initial state. The initial state may ` +
& && &&&`not be undefined.`
& & var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
& & if (typeof reducer(undefined, { type }) === 'undefined') {
& && &throw new Error(
& && &&&`Reducer "${key}" returned undefined when probed with a random type. ` +
& && &&&`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
& && &&&`namespace. They are considered private. Instead, you must return the ` +
& && &&&`current state for any unknown actions, unless it is undefined, ` +
& && &&&`in which case you must return the initial state, regardless of the ` +
& && &&&`action type. The initial state may not be undefined.`
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
export default function combineReducers(reducers) {
&&// 返回一个对象, key =& value 且value是function(其实就是过滤掉非function)
&&var finalReducers = pick(reducers, (val) =& typeof val === 'function');
&&var sanityE
& & // 对所有的子reducer 做一些合法性断言,如果没有出错再继续下面的处理
& & // 合法性断言的内容,见API注释
& & assertReducerSanity(finalReducers);
&&} catch (e) {
& & sanityError =
&&// 所有的 key: value,将value置成了undefined,费解...
&&// 总而言之, 初始state 就是 类似 {hello: undefined, world: undefined} 的东东
&&// TODO 确认这里的逻辑
&&var defaultState = mapValues(finalReducers, () =& undefined);
&&return function combination(state = defaultState, action) {
& & if (sanityError) {
& && &throw sanityE
& & var hasChanged =
& & // 这段代码,简单的说,就是循环一遍 finalState[key] = fn(reducer, key)
& & var finalState = mapValues(finalReducers, (reducer, key) =& {
& && &var previousStateForKey = state[key];
& && &var nextStateForKey = reducer(previousStateForKey, action);
& && &if (typeof nextStateForKey === 'undefined') {
& && &&&// 其他一个reducer返回的是undefined,于是挂啦...抛出错误
& && &&&var errorMessage = getUndefinedStateErrorMessage(key, action);
& && &&&throw new Error(errorMessage);
& && &// 这段代码有些费解,从redux的设计理念上来讲,除了不认识的action type,其他情况都应该返回全新的state
& && &// 也就是说
& && &// 1. action type 认识,返回新的state,于是这里 hasChanged 为 true
& && &// 2. action type 不认识,返回原来的state,于是这里 hasChanged 为 false
& && &// 3. 不管action type 是否认识, 在原来的state上修改,但是返回的是修改后的state(没有返回拷贝),那么,hasChanged还是为false
& && &hasChanged = hasChanged || nextStateForKey !== previousStateForK
& && &return nextStateForK
& & // 开发环境中(于是记得在生产环境去掉)
& & // 后面再研究这段代码,毕竟不是主线路...
& & if (process.env.NODE_ENV !== 'production') {
& && &var warningMessage = getUnexpectedStateKeyWarningMessage(state, finalState, action);
& && &if (warningMessage) {
& && &&&console.error(warningMessage);
& & return hasChanged ? finalState :
  源码解析:bindActionCreator.js
  别看API注释一大堆,除去合法性检查,关键代码其实就只有几句。先看个简单例子可能方便理解一些。看完之后可能会觉得,不就是对store.dispatch 的调用进行了便捷处理嘛。。。
var addTodo = function(text){
& & return {
& && &&&type: 'add_todo',
& && &&&text: text
var addTodos = function(){
& & return {
& && &&&type: 'add_todos',
& && &&&items: Array.prototype.slice.call(arguments, 0)
var reducer = function(state, action){
& & switch (action.type) {
& && &&&case 'add_todo':
& && && && &return state.concat(action.text);
& && &&&case 'add_todos':
& && && && &return state.concat(action.items);
& && &&&default:
& && && && &
var store = redux.createStore(reducer, []);
// 注意,关键代码在这里
var actions = redux.bindActionCreators({
& & addTodo: addTodo,
& & addTodos: addTodos
}, store.dispatch);
console.log('state is: ' + store.getState());
store.dispatch({type: 'add_todo', text: '读书'});
store.dispatch({type: 'add_todos', items: ['阅读', '睡觉']});
console.log('state is: ' + store.getState());&&// state is: 读书,阅读,睡觉
actions.addTodo('看电影');
console.log('state is: ' + store.getState());&&// state is: 读书,阅读,睡觉,看电影
actions.addTodos(['刷牙', '洗澡']);
console.log('state is: ' + store.getState());&&// state is: 读书,阅读,睡觉,看电影,刷牙,洗澡复制代码
所以,直接看代码吧,挺简单的。
import mapValues from '../utils/mapValues';
function bindActionCreator(actionCreator, dispatch) {
&&return (...args) =& dispatch(actionCreator(...args));
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
* @param {Function|Object} actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
* @param {Function} dispatch The `dispatch` function available on your Redux
* @returns {Function|Object} The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
// 假设 actionCreators === {addTodo: addTodo, removeTodo: removeTodo}
// 简单的来说 bindActionCreators(actionCreators, dispatch)
// 最后返回的是:
//& &addTodo: function(text){
//& && &dispatch( actionCreators.addTodo(text) );
//& &removeTodo: function(text){
//& && &dispatch( actionCreators.removeTodo(text) );
//&&或者说 actionCreators === addTodo (addTodo 为 actionCreator)
//&&最后返回的是
//&&function() {
//& &&&dispatch(actionCreators());
export default function bindActionCreators(actionCreators, dispatch) {
&&if (typeof actionCreators === 'function') {
& & return bindActionCreator(actionCreators, dispatch);
&&if (typeof actionCreators !== 'object' || actionCreators === null || actionCreators === undefined) {&&// eslint-disable-line no-eq-null
& & throw new Error(
& && &`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
& && &`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
&&return mapValues(actionCreators, actionCreator =&
& & bindActionCreator(actionCreator, dispatch)
  源码解析:applyMiddleware.js
  中间件应该是redux源码里面最绕的一部分,虽然看懂后,有一种“啊~原来不过如此”的感觉,但一开始还真是看的晕头转向的,API的说明、中间件的编写、applyMiddleware的源码实现,都不是那么好理解。
  在继续源码解析之前,推荐看下官方文档对于middleware的说明,链接传送门:
  虽然文档死长死长,但硬着头皮看完,还是有所收获的,终于知道 applyMiddleware 的实现这么绕了。。。
  例子:redux-thunk
  用redux处理过异步请求的同学应该用过redux-thunk,我们来看下他的源码,奇短无比,别说你的小伙伴了,我的小伙伴都惊呆了。
export default function thunkMiddleware({ dispatch, getState }) {
&&return next =& action =&
& & typeof action === 'function' ?
& && &action(dispatch, getState) :
& && &next(action);
  翻译成ES5,是这样子滴,之后你再看其他中间件的实现,其实都大同小异,下面我们写个自定义中间件,基本就可以看出点门路来。
'use strict';
Object.defineProperty(exports, "__esModule", {
&&value: true
exports.default = thunkM
function thunkMiddleware(store) {
&&var dispatch = store.
&&var getState = store.getS
&&return function (next) {
& & return function (action) {
& && &return typeof action === 'function' ? action(dispatch, getState) : next(action);
module.exports = exports['default'];复制代码
  自定义中间件:logger
  先看logger的实现
& & function middleware(store){
& && &&&return function(next){
& && && && &return function(action){
& && && && && & return next(action);
& && && && &}
& & }复制代码
  基本看出中间件声明的模版来了,就是下面这个样子。下面结合applyMiddleware的调用,来说明store、next、action 几个参数。
& & function logger(store){
& && &&&return function(next){
& && && && &return function(action){
& && && && && & console.log('logger: dispatching ' + action.type);
& && && && && & var result = next(action);
& && && && && & console.log('logger: next state ' + result);
& && && && && &
& && && && &}
& & }复制代码
  applyMiddleware调用例子
  完整的示例代码见本小节最后面。可以看到:
  applyMiddleware 的调用方式为 applyMiddleware(...middlewares)(react.createStore)。其实这里直接先创建 store,然后applyMiddleware(...middlewares)(store) 也很容易实现相同的效果,不过作者是故意这样设计的,为了避免在同一个store上多次应用同一个middlerware(参考官方文档:尝试 #6: “单纯”地使用 Middleware )
  中间件顶层的store参数,并不是常规的store,虽然它也有 getState、dispatch 两个方法
& & // 上面的store参数,其实就是这个对象
& & // 其中,store 为内部的store,我们在外面 storeWithMiddleWare.dipatch的时候,内部实现是转成 store.dispatch
& & // 此外,可以看到 middlewareAPI.dispatch 方法,是最终封装后的dispatch(千万注意,如果在中间件内部 调用 store.dispatch,可能导致死循环 )
& & var middlewareAPI = {
& && &getState: store.getState,
& && &// 最后面, dispatch 被覆盖, 变成包装后的 dispatch 方法
& && &dispatch: (action) =& dispatch(action)
& & };复制代码
  第二层的next函数,其实是一个“dispatch”方法。熟悉express的同学大概可以猜到它的作用。storeWithMiddleWare.dispatch(action) 的时候,会顺序进入各个中间件(按照定义时的顺序)。从当前的例子来看,大约如下,其实就是柯里化啦~:
storeWithMiddleWare.dispatch(action) --& logger(store)(next)(action) --& timer(store)(next)(action) --& store.dispatch(action)
  完整的示例代码
function reducer(state, action){
if(typeof state==='undefined') state = [];
switch(action.type){
case 'add_todo':
return state.concat(action.text);
function addTodo(text){
type: 'add_todo',
text: text
// 这里的 store,并不是 redux.createStore(reducer, initialState) 出来的 store
// 而是 {getState: store.getState, dispatch: function() { store.dispatch(action); }}
function logger(store){
return function(next){
return function(action){
console.log('logger: dispatching ' + action.type);
var result = next(action);
console.log('logger: next state ' + result);
function timer(store){
return function(next){
return function(action){
console.log('timer: dispatching ' + action.type);
var result = next(action);
console.log('timer: next state ' + result);
var createStoreWidthMiddleware = redux.applyMiddleware(
)(redux.createStore);
var storeWithMiddleWare = createStoreWidthMiddleware(reducer);
storeWithMiddleWare.subscribe(function(){
console.log('subscribe: state is : ' + storeWithMiddleWare.getState());
console.log( storeWithMiddleWare.dispatch(addTodo('reading')) );复制代码
  源码解析
  再次说下,建议先看下官方文档对中间件的介绍,不然可能会有点晕。
import compose from './compose';
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
* See `redux-thunk` package as an example of the Redux middleware.
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
&&从调用方法 applyMiddleware(...middlewares)(Redux.createStore) 可以看出
&&next 参数实际上是 Redux.createStore. 而 Redux.createStore 的调用方式为 Redux.createStore(reducer, initialState)
&&所以 applyMiddleware(...middlewares)
&&1. 参数: Redux.createStore
&&2. 返回值:一个function, 跟 Redux.createStore 接受的参数一样
export default function applyMiddleware(...middlewares) {
&&return (next) =& (reducer, initialState) =& {
& & // 内部先创建一个store (相当于直接调用 Redux.createStore(reducer, initialState))
& & var store = next(reducer, initialState);
& & // 保存最初始的store.dispatch
& & var dispatch = store.
& & var chain = [];
& & var middlewareAPI = {
& && &getState: store.getState,
& && &// 最后面, dispatch 被覆盖, 变成包装后的 dispatch 方法
& && &dispatch: (action) =& dispatch(action)
& & // 返回一个数组
& & // 贴个例子在这里做参考,redux-thunk
& & // function thunkMiddleware(store) {
& & //&&var dispatch = store.
& & //&&var getState = store.getS
& & //&&这里的next其实就是dispatch
& & //&&return function (next) {
& & //& & return function (action) {
& & //& && &return typeof action === 'function' ? action(dispatch, getState) : next(action);
& & //& & };
& & //&&};
& && &chain 是个数组, 参考上面的 middlleware (redux-thunk),可以看到,chain的每个元素为如下形式的function
& && &并且, 传入的 store.getState 为原始的 store.getState,而 dispatch则是包装后的 dispatch(不是原始的store.dispatch)
& && &似乎是为了确保, 在每个middleware里调用 dispatch(action), 最终都是 用原始的 store.dispatch(action)
& && &避免 store.dispatch 被覆盖, 导致middleware 顺序调用的过程中, store.dispatch的值变化 --& store.dispatch 返回的值可能会有不同
& && &违背 redux 的设计理念
& && &这里的 next 则为 原始的 store.dispatch (见下面 compose(...chain)(store.dispatch) )
& && &function (next) {
& && &&&return function (action) {
& & chain = middlewares.map(middleware =& middleware(middlewareAPI));
& & // compose(...chain)(store.dispatch) 返回了一个function
& & // 伪代码如下,
& & // function (action) {
& & //& &middleware(store)(store.dispatch);
& & dispatch = compose(...chain)(store.dispatch);&&// 从右到左, middleware1( middleware2( middleware3(dispatch) ) )
& & // 于是,最终调用 applyMiddleware(...middlewares)(Redux.createStore)
& & // 返回的 store, getState,subscribe 方法都是原始的那个 store.getState, store.subscribe
& & // 至于dispatch是封装过的
& & return {
& && &...store,
& && &dispatch
电话:010-
地址:北京市海淀区北清路68号
移动客户端下载
微信公众号:yonyouudn
扫描右侧二维码关注我们
专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041
京公网网备安4
Powered by Discuz!

我要回帖

更多关于 redux reselect使用 的文章

 

随机推荐