notebook's Introduction
notebook's People
notebook's Issues
Redux踩坑系列--Reducer
Reducer简介
reducer
实际上是真正作用于redux中state部分的纯函数,用于接收dispatch传过来的action对象,action对象通常是这种形式:
{
type: ADD_TODO,
payload: “balabala...”
}
其中payload
可以放入任何类型数据。当dispatch(action)
之后,就轮到reducer大展神威了,它会根据ADD_TODO进行匹配(一般通过switch/case
),执行相应的纯函数。
对于纯函数网上有很多的详解,这里简单说一下我的理解,其实就一个公式:f(x)===f(x)
;任意函数只要在任意情况下满足该公式,即纯函数。
那么reducer作为纯函数一般形式是什么样呢?如下:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
其实现的功能主要有:
- 接收原始
state
和dispatch
的参数action
- 根据
action
的type
属性找到对应的执行函数 - 若
action.type
未匹配,则原样返回接收的state
没错,这就是reducer的所有功能 :)
combineReducers
reducer本身没有什么问题,但随着项目的复杂度急剧增加,此时将所有action.type
的执行函数都写在一个文件里明显会带来维护上的困难,有没有什么方法能够使得各自模块的reducer能够各自维护,再统一合并到最终传给createStore的rootReducer中去呢?这个时候就到combineReducers
登场的时候了。
先来看一下combineReducers
的调用方式:
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
其中todos和counter为已定义的分片reducer。
它主要实现的功能如下:
- 将不同模块的reducer函数合并到rootReducer
- 将reducer执行结果返回到该reducer传入combineReducers时对应的key下如此处的todos和counter
这里有一些坑需要着重提示一下:
所有传入的reducer会依次执行
这也就意味着不同reducer之间的执行结果会对之后的reducer执行造成影响,例如当todos中的reducer未匹配到对应的action时,如果在default中未返回传入state,或返回了错误的值,则在counter中将发生错误。因此我们在定义reducer时需要遵守如下约定:
- 入参中的state设置默认值
- 必须在switch/case最后定义default并返回传入的state
- 尽量避免不同reducer中使用相同的actionType
以上
Promise-stepbystep
对于Promise用法就还不是很清楚的同学可以先看一下这里,此外官方给出的Promise/A+规范,中文版也需要事先进行了解。事实上在Promise/A+规范推行之前,社区已经有了实际使用的Promise类库,Q就是其中使用最广泛的类库之一,本文也着重对Q作者的设计思路做一些翻译和补充,供大家共同学习。
本文旨在渐进的对Promise的设计思路进行阐述,从而帮助读者更深刻的理解promise的原理和使用,并从中得到启发。
Step1
想象一下,当你需要实现一个不需要立即返回结果的函数时,一个最为自然的想法就是使用回掉函数,这也是JS事件轮询机制的精髓。具体实现可能会是下面的形式:
var oneOneSecondLater = function (callback) {
setTimeout(function () {
callback(1);
}, 1000);
};
这是解决以上问题的一个简单方法,但是我们还有很多的改进空间。更加通用的方案是加入异常校验,同时提供callback和errback.
var maybeOneOneSecondLater = function (callback, errback) {
setTimeout(function () {
if (Math.random() < .5) {
callback(1);
} else {
errback(new Error("Can't provide one."));
}
}, 1000);
};
以上方法通过显式的错误处理逻辑new Error()
来实现异常处理,但是这种错误处理方式太过机械化,同时也很难灵活的对错误信息进行处理。
Promise
下面我们来思考一个更为一般的解决方案,让函数本身返回一个对象来表示最终的结果(successful或failed),而非之前的返回values或是直接抛出异常。这个返回的结果就是Promise
,正如其名字(承诺)显示的那样,最终需要被resolve(执行)。我们需要可以通过调用promise上的一个函数来观察其处于完成态(fulfillment)还是拒绝态(rejection)。如果一个promise被rejected,并且此rejection未能被明确的观察,则所有衍生出来的promises将隐式的执行相同原因的reject。
接下来我们构建一个简单的‘promise’,其实就是一个包含key为then的一个对象,且在then上挂载了callback:
var maybeOneOneSecondLater(){
var callback;
setTimeout(function(){
callback(1);
},1000);
return {
then : function(_callback){
callback = _callback;
}
}
}
maybeOneOneSecondLater().then(callback);
该版本有两个缺点:
- then方法只有最后挂载的callback生效,如果我们能够将所有的callback保存下来,并依次调用也许会更加实用;
- 如果我们的callback注册时间超过1秒,此时promise已经构建完毕,那么这个callback也永远不会调用。
一个更通用的解决方案需要能够接收任意数量的callback,并且能够保证其能够在任何情况下注册成功。我们将通过构建一个包含两种状态(two-state)的对象来实现promise。
一个promise的初始状态为unresolved,并且此时所有callbacks均被添加到一个名为pending的观察者(observer)数组中。当该promise执行resolve时,所有的observers被唤醒并执行。当promise处于resolved状态后,新注册的callbacks将立即被调用。我们通过pending数组中的callbacks是否存在来判断state的变化,并且在resolution之后将所有callbacks清空。
var maybeOneOneSecondLater = function () {
var pending = [], value;
setTimeout(function () {
value = 1;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}, 1000);
return {
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
};
};
这个函数已经很好的解决了问题,下面我们将其改写为工具函数使其更便于使用。定义一个deferred对象,主要包含两个部分:
- 注册观察者队列(observers)
- 通知observers并执行
var defer = function(){
var pending = [], value;
return{
resolve: function(_value){
value = _value;
var len = pending.length
for (var i = 0; i < len; i++){
pending[i](value);
}
pending = undefined;
},
then: function(callback){
if(pending){
pending.push(callback);
} else {
callback(value)
}
}
}
};
var oneOneSecondLater = function(){
var result = defer();
setTimeout(function(){
result.resolve(1)
},1000);
};
oneOneSecondLater().then(callback);
这里resolve函数有一个缺陷:可以被多次调用,并不断改变result的值。因此我们需要保护value值不被意外或恶意篡改,解决方案就是只允许resolve执行一次。
var defer = function(){
var pending = [], value;
return{
resolve: function(_value){
if(pending){
value = _value;
var len = pending.length
for (var i = 0; i < len; i++){
pending[i](value);
}
pending = undefined;
} else {
throw new Error("A promise can only be resolved once.")
}
},
then: function(callback){
if(pending){
pending.push(callback);
} else {
callback(value);
}
}
}
};
var oneOneSecondLater = function(){
var result = defer();
setTimeout(function(){
result.resolve(1)
},1000);
return result;
};
oneOneSecondLater().then(callback);
发散一下思维,我们除了以上功能外,还可以定制一些特有的处理逻辑,例如:
- 传递一个参数来作为抛出的错误信息或者忽略后续callback的执行;
- 可以在resolve时,实现callbacks的竞争(race)机制,忽略之后的resolutions;
当然可能还有很多的场景可以思考,这些就留给读者自行发散思维吧;
JS判断各数据类型
typeof
首先需要强调typeof并非函数,而是一个一元运算符,和+,++等无本质区别,下面看一下其对不同类型数据的执行结果:
typeof 1 === "number"
typeof true === "boolean"
typeof undefined === "undefined"
typeof "hello" === "string"
typeof Symbol() === "symbol"
typeof function(){} === "function"
typeof {} === "object"
// -----------------分割线----------------------
typeof [] === "object"
typeof null === "object"
typeof new Promise((resolve)=>resolve(1)) === "object"
typeof async function(){} === "function"
typeof class {} === "function"
可以看到,typeof对基本类型的判断基本没有问题,但对于引用类型则基本无能为力
instanceof
instanceof主要基于原型链来判断,在实践过程中我们会发现很多有趣的现象,直接上例子:
(1) instanceof Number === false
(new Number(1)) instanceof Number === true
"hello" instanceof String === false
new String("hello") instanceof String === true
({}) instanceof Object === true
(new Object()) instanceof Object === true
Object.create(null) instanceof Object === false //该方法可用于创建不需要继承任何Object属性的对象,如:字典
null instanceof Object === false //这里需要注意
(function(){}) instanceof Function === true
(function(){}) instanceof Object === true
通过上述栗子可以发现,instanceof在进行类型判断时,存在较多“意外”情况,主要有:
- 对于字面量方式和构造函数方式创建的实例类型无法进行准确判断
- 对于实例对象原型链上的所有构造函数都会返回true
因此instance用于类型判断时缺陷较为明显
toString方法
toString方法为Object的原型方法,可以返回当前实例的[[class]],因此也是最为精确的类型判断方法,使用时结果如下:
Object.prototype.toString.call(1) === "[object Number]"
Object.prototype.toString.call(true) === "[object Boolean]"
Object.prototype.toString.call(undefined) === "[object Undefined]"
Object.prototype.toString.call("hello") === "[object String]"
Object.prototype.toString.call(Symbol()) === "[object Symbol]"
Object.prototype.toString.call({}) === "[object Object]"
Object.prototype.toString.call([]) === "[object Array]"
Object.prototype.toString.call(null) === "[object Null]"
Object.prototype.toString.call(function(){}) === "[object Function]"
Object.prototype.toString.call(async function(){}) === "[object AsyncFunction]"
Object.prototype.toString.call(new Promise(resolve=>resolve())) === "[object Promise]"
Object.prototype.toString.call(new Date()) === "[object Date]"
Object.prototype.toString.call(new RegExp()) === "[object RegExp]"
Object.prototype.toString.call(new Error()) === "[object Error]"
Object.prototype.toString.call(class {}) === "[object Function]" // toString方法对class类型也无能为力,也从侧面说明class只是构造函数的一个语法糖而已
总体来看,虽然仍无法对class类型无能为力,但其余所有已知类型均可实现准确判断,所以大家尽量用它吧
Tips:实际使用时可将类型判断方法封装为utils方法,减少劳动量 :)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.