Git Product home page Git Product logo

notebook's Introduction

notebook's People

Contributors

wfatec avatar

Watchers

James Cloos avatar  avatar

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
  }
}

其实现的功能主要有:

  1. 接收原始statedispatch的参数action
  2. 根据actiontype属性找到对应的执行函数
  3. 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。

它主要实现的功能如下:

  1. 将不同模块的reducer函数合并到rootReducer
  2. 将reducer执行结果返回到该reducer传入combineReducers时对应的key下如此处的todos和counter

这里有一些需要着重提示一下:

所有传入的reducer会依次执行

这也就意味着不同reducer之间的执行结果会对之后的reducer执行造成影响,例如当todos中的reducer未匹配到对应的action时,如果在default中未返回传入state,或返回了错误的值,则在counter中将发生错误。因此我们在定义reducer时需要遵守如下约定:

  1. 入参中的state设置默认值
  2. 必须在switch/case最后定义default并返回传入的state
  3. 尽量避免不同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);

该版本有两个缺点:

  1. then方法只有最后挂载的callback生效,如果我们能够将所有的callback保存下来,并依次调用也许会更加实用;
  2. 如果我们的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对象,主要包含两个部分:

  1. 注册观察者队列(observers)
  2. 通知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);

发散一下思维,我们除了以上功能外,还可以定制一些特有的处理逻辑,例如:

  1. 传递一个参数来作为抛出的错误信息或者忽略后续callback的执行;
  2. 可以在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在进行类型判断时,存在较多“意外”情况,主要有:

  1. 对于字面量方式和构造函数方式创建的实例类型无法进行准确判断
  2. 对于实例对象原型链上的所有构造函数都会返回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 photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.