• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • state

    单一状态树

    Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个store实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

    单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。

    存储在 Vuex 中的数据和 Vue 实例中的data遵循相同的规则,例如状态对象必须是纯粹(plain)的。。


    在 Vue 组件中获得 Vuex 状态

    那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从store实例中读取状态最简单的方法就是在计算属性中返回某个状态:

    // 创建一个 Counter 组件
    const Counter = {
      template: `<div>{{ count }}</div>`,
      computed: {
        count () {
          return store.state.count
        }
      }
    }
    

    每当store.state.count变化的时候,都会重新求取计算属性,并且触发更新相关联的 DOM。

    然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用state的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

    Vuex 通过 Vue 的插件系统将store实例,从根组件中“注入”到所有的子组件里。且子组件能通过this.$store访问到。让我们更新下 Counter 的实现:

    const Counter = {
      template: `<div>{{ count }}</div>`,
      computed: {
        count () {
          return this.$store.state.count
        }
      }
    }
    


    mapState辅助函数

    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用mapState()辅助函数帮助我们生成计算属性,让你少按几次键:

    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
    
    export default {
      // ...
      computed: mapState({
        // 箭头函数可使代码更简练
        count: state => state.count,
    
        // 传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',
    
        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    当映射的计算属性的名称state 的子节点名称相同时,我们也可以给mapState传一个字符串数组。

    computed: mapState([
      // 映射 this.count 为 store.state.count
      'count'
    ])
    


    对象展开运算符

    mapState函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给computed属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

    computed: {
      localComputed () { /* ... */ },
      // 使用对象展开运算符将此对象混入到外部对象中
      ...mapState({
        // ...
      })
    }
    


    组件仍然保有局部状态

    使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。



    扩展运算符

    ES2015(ES6)新增了一种基本运算符——展开运算符,使用三个点...表示。

    可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;还可以在构造字面量对象时,将对象表达式按key-value的方式展开。(译者注:字面量一般指[1, 2, 3]或者{name:"mdn"}这种简洁的构造方式)

    下面我们来看看它的基本用法:

    // 展开基本的数组
    const arr = ['apple','orange','banana'];
    console.log(...arr);  // 'apple' 'orange' 'banana'
    

    实时上上面的语句只是在语法层面展开了数组arr,让每一个元素作为log函数的参数。


    语法

    函数调用:

    myFunction(...iterableObj);

    字面量数组构造或字符串:

    [...iterableObj,'4',...'hello', 6];

    构造字面量对象时,进行克隆或者属性拷贝(ECMAScript 2018规范新增特性):

    let objClone ={...obj};


    在函数调用时使用

    等价于apply的方式。如果想将数组元素迭代为函数参数,一般使用Function.prototype.apply 的方式进行调用。

    function myFunction(x, y, z) { }
    var args = [0, 1, 2];
    myFunction.apply(null, args);
    
    
    //有了展开语法,可以这样写:
    
    function myFunction(x, y, z) { }
    var args = [0, 1, 2];
    myFunction(...args);
    

    所有参数都可以通过展开语法来传值,也不限制多次使用展开语法。

    function myFunction(v, w, x, y, z) { }
    var args = [0, 1];
    myFunction(-1, ...args, 2, ...[3]);
    


    function push(array, ...items) {
        array.push(...items);
    }
    
    function add(x, y) {
        return x + y;
    }
    
    var numbers = [4, 38];
    add(...numbers)	// 42
    

    上面代码中,array.push(...items)和add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

    扩展运算符与正常的函数参数可以结合使用,非常灵活

    function f(v, w, x, y, z) { }
    var args = [0, 1];
    f(-1, ...args, 2, ...[3]);
    



    在 new 表达式中应用

    使用 new 关键字来调用构造函数时,不能直接使用数组+ apply 的方式(apply 执行的是调用[[Call]],而不是构造[[Construct]])。当然,有了展开语法,将数组展开为构造函数的参数就很简单了:

    var dateFields = [1970, 0, 1];	// 1970年1月1日
    var d = new Date(...dateFields);
    


    构造字面量数组时使用

    构造字面量数组时更给力!通常情况下构建字面量结构的数组我们会使用如pushspliceunshiftconcat等函数将现有数组作为新数组的一部分。展开运算符可以更简单快速的完成这项工作。

    var parts = ['shoulders', 'knees'];
    var lyrics = ['head', ...parts, 'and', 'toes'];		// ["head", "shoulders", "knees", "and", "toes"]
    

    和参数列表的展开类似,...在构造字面量数组时,可以在任意位置多次使用。


    数组拷贝(copy)

    var arr = [1, 2, 3];
    var arr2 = [...arr]; // like arr.slice()
    arr2.push(4);
    
    // arr2 此时变成 [1, 2, 3, 4]
    // arr 不受影响
    

    提示:实际上,展开语法和 Object.assign()行为一致,执行的都是浅拷贝(只遍历一层)。如果想对多维数组进行深拷贝,下面的示例就有些问题了。

    var a = [[1], [2], [3]];
    var b = [...a];
    b.shift().shift(); // 1
    // Now array a is affected as well: [[2], [3]]
    


    连接多个数组

    Array.concat 函数常用于将一个数组连接到另一个数组的后面。如果不使用展开语法, 代码可能是下面这样的:
    
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    var arr3 = arr1.concat(arr2);
    
    使用展开语法:
    
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    var arr3 = [...arr1, ...arr2];
    
    Array.unshift 方法常用于在数组的开头插入新元素/数组.  不使用展开语法, 示例如下:
    
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    Array.prototype.unshift.apply(arr1, arr2) // arr1 现在是 [3, 4, 5, 0, 1, 2]
    
    
    如果使用展开语法, 代码如下:  [请注意, 这里使用展开语法创建了一个新的 arr1 数组,  Array.unshift 方法则是修改了原本存在的 arr1 数组]:
    
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    arr1 = [...arr2, ...arr1]; // arr1 现在为 [3, 4, 5, 0, 1, 2]
    



    构造字面量对象时使用展开语法

    Rest/Spread Properties for ECMAScript 提议(stage 4)对字面量对象增加了展开特性。其行为是,将已有对象的所有可枚举(enumerable)属性拷贝到新构造的对象中.

    浅拷贝(Shallow-cloning,不包含 prototype)和对象合并,可以使用更简短的展开语法。而不必再使用 Object.assign()方式.

    var obj1 = { foo: 'bar', x: 42 };
    var obj2 = { foo: 'baz', y: 13 };
    
    var clonedObj = { ...obj1 };	// 克隆后的对象: { foo: "bar", x: 42 }
    
    var mergedObj = { ...obj1, ...obj2 };	// 合并后的对象: { foo: "baz", x: 42, y: 13 }
    

    提示: Object.assign()函数会触发 setters,而展开语法则不会。

    提示:不能替换或者模拟 Object.assign()函数:

    var obj1 = { foo: 'bar', x: 42 };
    var obj2 = { foo: 'baz', y: 13 };
    const merge = ( ...objects ) => ( { ...objects } );
    
    var mergedObj = merge ( obj1, obj2);	// Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
    
    var mergedObj = merge ( {}, obj1, obj2);	// Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
    

    在这段代码中,展开操作符(spread operator)并没有按预期的方式执行:而是先将多个解构变为剩余参数(rest parameter),然后再将剩余参数展开为字面量对象.

    只能用于可迭代对象

    在数组或函数参数中使用展开语法时,该语法只能用于可迭代对象:

    var obj = {'key1': 'value1'};
    var array = [...obj]; // TypeError: obj is not iterable
    



    合并数组

    扩展运算符提供了数组合并的新写法。

    // ES5
    [1, 2].concat(more)
    
    // ES6
    [1, 2, ...more]
    
    var arr1 = ['a', 'b'];
    var arr2 = ['c'];
    var arr3 = ['d', 'e'];
    
    // ES5 的合并数组
    arr1.concat(arr2, arr3);	// [ 'a', 'b', 'c', 'd', 'e' ]
    
    // ES6 的合并数组
    [...arr1, ...arr2, ...arr3]	// [ 'a', 'b', 'c', 'd', 'e' ]
    


    与解构赋值结合

    扩展运算符可以与解构赋值结合起来,用于生成数组。

    const arr1 = ['welcome','to'];
    const arr2 = ['hello',...arr1,'here'];   // ["hello", "welcome", "to", "here"]
    
    // ES5
    a = list[0], rest = list.slice(1)
    
    // ES6
    [a, ...rest] = list
    
    const [first, ...rest] = [1, 2, 3, 4, 5];
    first // 1
    rest // [2, 3, 4, 5]
    
    const [first, ...rest] = [];
    first // undefined
    rest // []:
    
    const [first, ...rest] = ["foo"];
    first // "foo"
    rest // []
    

    如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    const [...butLast, last] = [1, 2, 3, 4, 5];	//  报错
    const [first, ...middle, last] = [1, 2, 3, 4, 5];	//  报错
    


    函数的返回值

    JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

    var dateFields = readDateFields(database);
    var d = new Date(...dateFields);
    

    上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。


    字符串

    扩展运算符还可以将字符串转为真正的数组。

    [...'hello']	// [ "h", "e", "l", "l", "o" ]
    

    JavaScript 会将 32 位 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题


    实现了 Iterator 接口的对象

    任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组

    var nodeList = document.querySelectorAll('div');
    var array = [...nodeList];
    

    上面代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 接口。


    Map 和 Set

    扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

    let map = new Map([
        [1, 'one'],
        [2, 'two'],
        [3, 'three'],
    ]);
    let arr = [...map.keys()];	// [1, 2, 3]
    

    上篇:vuex 使用示例

    下篇:getter