knowledge

Vue.js学习笔记

针对Vue 2版本。

目录

  1. vue

    1. 模板插值
    2. 指令 && 特殊attribute
    3. Vue实例的属性
    4. 组件
    5. 单文件组件
    6. 过渡/动画
    7. 插件(plugin)
    8. 特性
    9. vue风格指南
    10. 响应式系统
    11. 虚拟DOM系统
    12. SSR
    13. Vue实现原理
    14. 例子
    15. Vue 3 与 Vue 2 区别
  2. vue-router
  3. vuex
  4. vue-cli
  5. nuxt
  6. jQuery与Vue.js对比

约定:vm(ViewModel)变量名表示:Vue实例。

vue

模板插值

  1. 支持JS表达式(单个),不支持语句流控制

    e.g. ```html <div :id="'list-' + id"/> ```
  2. 只能访问部分全局变量(白名单);不允许访问自定义的全局变量(引入的其他库变量仅在JS代码中使用)。
  3. 作用域在所属Vue实例范围内。

    在模板中使用Vue实例的属性/方法时,省去 this

    父级模板里的所有内容都是在父级作用域中编译的;子级模板里的所有内容都是在子级作用域中编译的。

    特例:父级通过v-slot="临时变量"去使用子级<slot>给定的属性对应的值。

  4. v-slot<slot>

    用于父级(v-slot:某名字)向子组件(<slot name="某名字">)插入内容。

  5. 所有渲染结果不包含<template>
  6. 注意直接用.html写模板在DOM上的,不能用大写字母命名组件。

指令 && 特殊attribute

指令(directives)是带有v-前缀的DOM的特殊属性。

JS表达式的值改变时,将响应式地改变DOM。

  1. :参数

    用于添加指令后的参数。

    • 某指令:[表达式]动态参数

      由表达式计算的结果为最终:后跟的值。

      e.g. v-bind:[表达式]='xx'v-on:[表达式]='xx'v-slot:[表达式]

  2. v-ifv-elsev-else-if

    DOM或组件判定为false,则完全销毁(组件会调用destroyed);判定为true,则新建。除非使用<keep-alive/>包裹。

    • 若切换的DOM或组件是相同的,则根据key属性是否相同判断是否复用(未设置key则也认为是相同)。

      e.g. ```vue ```
  3. v-for="值/(值, 键)/(值, 键, 索引) in/of JS表达式/整数"

    处于同一节点时,v-forv-if优先级更高(v-if将分别重复运行于每个v-for循环中) ```html
  4. v-if在v-for循环的每一次都运行判断
  5. ``` 避免`v-if`和`v-for`同时用在同一个元素上。
    • 在组件中使用v-for时,必须添加key属性
    • 尽可能在使用v-for时提供key属性(除非输出的DOM非常简单,或刻意重用DOM以获取性能提升)

      e.g. ```html <div v-for="(item, key) in items" :key="item.id || key"> </div> ```
    • v-for子组件时,最好都用props传入参数,而不用全局数据(如:vuex)

(相同标签名的DOM或相同组件切换展示时,)没有提供key属性:若数据项的展示/顺序被改变,则Vue将不会销毁再新建DOM/移动DOM来匹配数据项的顺序,而是保持原DOM尽量不变化,尽量仅在原DOM上修改属性和内部内容,以确保渲染正确。

  1. v-bindv-bind:xx缩写::xx)绑定DOM属性与JS表达式的结果

    此DOM属性随表达式最终值改变而改变,直接修改此DOM属性值不改变表达式的值。

    1. 绑定修饰符:

      1. .sync >仅为语法糖 `<my-component :foo.sync="bar"/>` >监听`foo`值改变:可以用`@update:foo="方法"`(会传入新值)、或`watch` `bar`。 等价于: `<my-component :foo="bar" @update:foo="val => bar = val"/>` - 若要达到效果(同步更新bar),还需要在组件中添加: ```javascript Vue.component('myComponent', { props: ['foo'], template: '<p @click="doIt"></p>', methods: { doIt () { this.$emit('update:foo', 'new value') // 触发自己的事件(调用父级的回调函数),父级事件改变值,再传入子组件 } } }) ``` >不能和表达式一起使用,仅能绑定属性名:错误:~~`v-bind:title.sync="属性名 + '!'"`~~。
      2. .prop(绑定到DOM的property而不是HTML标签的 attribute
      3. .camel(小驼峰式camelCase转换为大驼峰式PascalCase)
    2. 特殊的DOM属性:

      1. 绑定class

        1. :class="'a b'"class="a b"
        2. :class="{ 'a': true, 'b': false }"class="a"
        3. :class="['a', 'b']"class="a b"
        4. :class="[{ 'a': true, 'b': false }, { 'c': true }, 'd', ['e']]"class="a c d e"
      2. 绑定style

        1. 自动添加样式前缀。
        2. CSS属性名可以用小驼峰式(camelCase)或-短横线隔开式(kebab-case,需用单引号包裹)命名。
        1. :style="{ '属性名1': '属性值1', '属性名2': '属性值2' }"
        2. :style="[{ '属性名1': '属性值1' }, { '属性名2': '属性值2', '属性名3': '属性值3' }]"
        • 多重值:渲染数组中最后一个被浏览器支持的值

          e.g. :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"

    3. 传递给子组件DOM属性的值类型

      e.g. ```html 传递字符串'1' 传递字符串'a' <my-component :some-prop="1">传递表达式:1(Number)</my-component> <my-component :some-prop="'1'">传递表达式:'1'(String)</my-component> <my-component :some-prop="a">传递表达式:a(变量是什么类型就是什么类型)</my-component> ```
    4. 若不带参数的v-bind="表达式",则绑定表达式的所有属性到DOM。

      e.g. ```html
      绑定了title和href属性
      绑定了alt和href属性
      ```
  2. v-onv-on:xx缩写:@xx)事件监听

    1. 事件修饰符:

      1. .stop(阻止冒泡)、.prevent(阻止默认行为)、.capture(捕获事件流)、.self(只当事件在该元素本身而不是子元素触发时才触发)、.once(事件将只触发一次)、.passive(滚动事件的默认滚动行为将立即触发,而不等待scroll事件完成)

        • 特殊:

          .stop/prevent:不带方法名的修饰符也有效果。@click.stop所有该节点的点击事件阻止冒泡,@click.prevent所有该节点的点击事件阻止默认行为。

      2. .enter.tab.delete.esc.space.up.down.left.right.数字键值KeyboardEvent.key的短横线形式Vue.config.keyCodes自定义的键位别名

        键盘。

      3. .left.right.middle

        鼠标。

      4. .native

        监听组件根元素的原生事件,仅在父级引用子组件处添加。

      5. .exact

        精确匹配(有其他按键则失败)。

      • 可同时使用,但改变顺序会产生不同效果。

        e.g. ```html <input @keyup.alt.67="clear"> <div @click.ctrl="doSomething">...</div> <div @click.prevent.self="doThat">...</div> <div @click.self.prevent="doThat">...</div> ```
    2. $event原生DOM事件的变量,仅能由HTML内联传入

      e.g. ```html
      <a href="#" @click="a($event)">click(第一个)</a> <a href="#" @click="a(1, $event)">click(第二个)</a>
      ```
    3. 自定义事件

      仅定义在父组件对子组件的引用上,只能由子组件内部vm.$emit触发,然后触发父级方法,再通过改变父级属性去改变子组件的props(或置换组件)。

    4. 支持不带参数绑定(值为事件-监听器的键-值的对象)

      不支持修饰符。

      e.g. <button v-on="{ mousedown: doThis, mouseup: doThat }"/>

  3. v-slotv-slot:xx缩写:#xx)插槽

    只允许添加在template上(特例见下),且不能嵌套使用。子组件使用<slot>在内部插入父级引入的内容。

    1. 只允许添加在template上的特例:被引用的子组件只使用默认插槽时,可以简写在引用的子组件上(若要使用多个插槽,则必须始终为所有的插槽使用完整的基于<template>的语法)。

      e.g. <子组件 v-slot:default><子组件 v-slot><子组件 #default>

    2. 若子组件没有包含<slot name="某名字">,则父组件引用子组件时的v-slot:某名字"的DOM会被抛弃。

    1. 后备内容

      1. 子组件中没有name属性或name="default"<slot>,匹配父级中去除所有包含v-slot:某名字的DOM的内容(即:匹配没有v-slotv-slot值为空的DOM和内容)。

        <slot>默认name为:default

      2. 子组件中<slot>的DOM内容,当且仅当没有父级匹配时显示。

    2. 具名插槽

      父级引用子组件,在元素内部添加的标签的DOM属性v-slot:某名字;会匹配子组件模板的<slot name="某名字">

    3. 作用域插槽

      父级引用子组件时,使用子组件<slot>上的属性对应的值(除了name属性)。

      1. 子组件的模板:

        <slot 组件属性1="字符串" :组件属性2="表达式">

        <slot>内部是子组件的作用域,和在其上添加的属性内容无关。

      2. 父级使用子组件<slot>上显性提供的属性对应值:

        <template/子组件 v-slot="临时变量"></template/子组件>

        临时变量 === { 组件属性1: '字符串'. 组件属性2: 表达式 }

        临时变量支持解构。

    e.g. ```vue // 父级 // 子级 ```
  4. v-model表单的双向绑定

    忽略表单元素上的valuecheckedselected等初始值,而仅通过Vue实例赋值。

    1. 表单修饰符:

      .lazychange而不是input事件触发)、.number(输入值转为Number类型)、.trim(过滤首尾空格)

    2. 仅针对部分元素:<input><textarea><select>、组件
    3. 仅为语法糖 1. 普通DOM `` 等价于: `<input :value="bar" @input="bar = $event.target.value">` 2. 组件 `` 等价于(默认:属性绑定为`value`、事件绑定为`input`。可由组件属性`model`修改): `<my-input :value="bar" @input="bar = arguments[0]"/>` - 若要达到效果(双向数据绑定),还需要在组件中添加: ```javascript Vue.component('myInput', { props: ['value'], template: '<input :value="value" @input="updateValue($event)">', methods: { updateValue (e) { this.$emit('input', e.target.value) // 触发自己的事件(调用父级的回调函数),父级事件改变值,再传入子组件 } } }) ```
    4. 绑定的Vue实例的值:

      1. DOM的value属性的值;
      2. 若是type="checkbox",则为true/false
      3. 若要获得Vue实例的动态属性值,则:

        1. :value="表达式"
        2. type="checkbox",则用:true-value="表达式" :false-value="表达式"

          不能和表达式一起使用,仅能绑定属性名:错误:v-model="属性名 + '!'"

  5. v-once只渲染元素和组件一次

    随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

  6. v-text等价于:``

    • 模板对空格HTML的字符实体的不同输入有所不同输出

      e.g. ```vue ```
  7. v-html输入真正HTML

    与其他插值(如:模板插值)的区别 1. `v-html`:`innerHTML`。 2. `v-text`、``及其他插值:`innerText(或textContent)`。 >`innerText`与`textContent`的区别:[MDN:textContent](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/textContent#与innerText的区别)。
  8. v-pre不编译

    e.g. <p v-pre></p>

  9. v-show

    总是渲染出DOM,根据值切换display值。

    不支持<template>、不支持v-else

  10. v-cloak指令保持在元素上直到关联实例结束编译

    与CSS一起使用时,在编译完成前使用样式,编译完成后去除样式。

  11. .修饰符

    用于指出一个指令应该以特殊方式绑定。

    使用在v-onv-bindv-model后添加。

  12. |使用filters过滤器,参数带入函数运行出结果(支持过滤器串联)

    仅可以在``和v-bind中使用(其他如:v-html 无效、无法在Vue实例中使用)。

    e.g. ```html

    1

    <p :title="num | a | b"/>
    ```
  13. 自定义指令(在钩子函数中进行业务)

    v-自定义指令名(或v-自定义指令名="表达式"v-自定义指令名:参数v-自定义指令名.修饰符

    同一个节点可以添加多个不同参数/修饰符的同一个自定义指令名 e.g. `<div v-observer:10="{ show: xx }" v-observer:20="{ show: yy }" />`:添加2个独立的自定义指令。
    // 局部
    new Vue({
      // 在局部的模板内使用
      directives: {
        自定义指令名: 钩子对象,
      }
    })
    
    
    // 全局
    Vue.directive('自定义指令名', 钩子对象)
    
    钩子对象 ```javascript { // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置 bind (el, binding, vnode) {}, // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中) inserted (el, binding, vnode) {}, // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 update (el, binding, vnode, oldVnode) {}, // 指令所在组件的 VNode 及其子 VNode 全部更新后调用 componentUpdated (el, binding, vnode, oldVnode) {}, // 只调用一次,指令与元素解绑时调用 unbind (el, binding, vnode) {} } // 可以把副作用的内容绑定到`el`,方便钩子函数之间传递信息 // 钩子对象的函数的第二个参数 binding: { name // 指令名,不包括`v-`前缀 value // 指令的绑定值,例如:`v-my-directive="1 + 1"`中,绑定值为`2` oldValue // 指令绑定的前一个值,仅在`update`和`componentUpdated`钩子中可用。无论值是否改变都可用 expression // 字符串形式的指令表达式。例如:`v-my-directive="1 + 1"`中,表达式为`"1 + 1"` arg // 传给指令的参数,可选。例如:`v-my-directive:foo`中,参数为`"foo"` modifiers // 一个包含修饰符的对象。例如:`v-my-directive.foo.bar`中,修饰符对象为`{ foo: true, bar: true }` } ```
  14. 特殊attribute

    1. key

      决定是否重用DOM(无keykey相同:保持原DOM尽量不变化,尽量仅在原DOM上修改属性和内部内容;有key且变化:销毁再新建DOM 或 移动DOM来匹配数据项的顺序) `key`主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNode。 1. 若不使用`key`,则Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。 2. 使用`key`,它会基于`key`的变化重新排列元素顺序,并且会移除`key`不存在的元素。 1. 切换的DOM的没有`key`属性或`key`属性相同时:最大化重用DOM。 2. 切换的DOM的`key`属性不同:不重用DOM(就算DOM完全相同,也会重新渲染)。 >对于仅有的一个元素(不是数组),`key`的变化会导致销毁再新建DOM。

      针对:有相同父元素的子元素必须有独特的key(重复的key会造成渲染错误)。相同标签名的DOM切换展示、或相同组件间切换展示。v-forv-if<transition/><transition-group/>

      • key的取值:

        1. 不要用v-for生成的键或索引,因为重新渲染且项的顺序变化时会发生错误(每一个项的key都各自等于重新渲染之后的每一个项的key)。
        2. 尽量不要用随机数(如:Math.random()),除了小概率会碰撞之外,框架无法优化性能。
        3. 每项数据需要一个唯一值作为key(如:id),若原数据项没提供,则可在拉到数据时由前端额外加入。但是要对数据来源进行去重,避免渲染相同key导致报错。

          e.g. ```javascript let localCounter = 1 function getLocalCounter () { return localCounter++ } // 拉到的每个数据加入唯一id const item1 = {} item1._id = getLocalCounter() ```
        4. id + 键或索引,能复用没有改变位置的DOM。
    2. ref

      被用来给原生DOM元素或子组件注册引用信息。引用信息注册在vm.$refs对象上。

      1. 初始渲染时无法访问。
      2. vm.$refs不是响应式的,因此不应该在templatecomputed做数据绑定。
      3. 多个DOM或子组件命名为同一个ref值,最后的会覆盖之前的,ref值只能取到最后的一个。
      1. 添加在原生DOM:指向DOM元素

        代替原生JS获取DOM,如:document.getElementById

      2. 添加在子组件:指向子组件Vue实例。
      3. 当与v-for一起使用时:指向包含 DOM元素或子组件Vue实例 的数组。
    3. is

      有效标签或动态组件。

    4. slot(已废弃,用v-slot代替)

      父级向子组件引入内容。

    5. slot-scopescope(已废弃,用v-slot代替)

      父级使用子组件字符串的作用域名字。 -

    官方建议的顺序:元素特性的顺序
    1. 定义(提供组件的选项)

      • is
    2. 列表渲染(创建多个变化的相同元素)

      • v-for
    3. 条件渲染(元素是否渲染/显示)

      • v-if
      • v-else-if
      • v-else
      • v-show
      • v-cloak
    4. 渲染方式(改变元素的渲染方式)

      • v-pre
      • v-once
    5. 全局感知(需要超越组件的知识)

      • id
    6. 唯一的特性(需要唯一值的特性)

      • ref
      • key
      • v-slot
      • slot
      • slot-scope
      • scope
    7. 双向绑定(把绑定和事件结合起来)

      • v-model
    8. 其它特性(所有普通的绑定或未绑定的特性)
    9. 事件(组件事件监听器)

      • v-on
    10. 内容(覆写元素的内容)

      • v-html
      • v-text

    </details>

Vue实例的属性

new Vue(对象)

  1. el(字符串):选择器

    限制:只在由new创建的Vue实例中。

  2. data(对象或方法):数据

    限制:组件的data是方法且返回一个数据对象。

    _$开头的属性不会被Vue实例代理,但可以使用vm.$data访问(e.g. vm.$data._property)。

    Vue内置的属性、API方法会以_$开头,因此若看到不带这些前缀的Vue实例的属性时,则一般可认为是Vue实例代理的属性(propsdatacomputedmethodsprovide/inject的属性,或mixins传入的属性)。

  3. computed(对象):依赖其他值(propsdatacomputed)的改变而执行,最后return

    默认:get(初始化时会调用一次);显式设置:set(被赋值时执行)和get 当未设置`set`时,不能主动去设置`computed`的值(~~`this.计算属性 = 值`~~);设置了`set`也不能改变自己的值(`set`函数里不能再循环设置自己的值)。 ```javascript // e.g. const vm = new Vue({ data: { firstName: 'firstname', lastName: 'lastname' }, computed: { fullName: { // getter get: function () { // 初始化和this.firstName或this.lastName改变时调用;this.fullName被赋值时不会直接调用 return this.firstName + ' ' + this.lastName }, // setter set: function (newValue) { // this.fullName被赋值时调用 const names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] // 不允许`this.fullName = 值`会导致死循环 } } } }) // 运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新 ```

    return的内容不涉及Vue实例的响应式内容(非Vue实例的属性、或已经被Object.freeze处理而非响应式的Vue实例的属性),则computed的内容就不是响应式的(不进行Vue的响应式绑定处理)。

在(props、)datacomputed先定义再使用,而不要对未使用过的变量进行this.新变量名 = 值(不先定义就直接使用无法绑定到响应式系统,无法触发视图更新渲染)。

  1. watch(对象):被watch的值改变而执行函数(观察的值必须是propsdatacomputed的属性)

    1. 可以设置immediate参数(侦听开始后立即调用一次)
    2. 可以设置deep参数(监听的对象的属性修改时调用一次,无论嵌套多深的属性,属性的属性也触发)
    3. 键名可以是属性1.子属性2,来观察嵌套的属性值

    还可以用vm.$watch来观察属性改变。

执行顺序是:(props -> )data -> computed -> watch

  1. filters(对象):过滤器方法

    方法内部没有代理 this 到Vue实例。

    因为不会被Vue实例代理,所以可以和Vue实例代理的属性同名(propsdatacomputedmethodsprovide/inject的属性,或mixins传入的属性`)。

  2. components(对象):局部注册组件(仅在此Vue实例中可用)
  3. methods(对象):可调用方法

    1. newmethods里的方法,方法体内的this指向这个实例,而非Vue实例。建议不要在methods中添加构造函数,而改用import方式引入构造函数。
    2. template的每次改变,都会导致VNode重新渲染,也会导致methods重新调用(无论methods使用的值是否变化)。

      若在template里调用methods中的方法从而绑定了数据(因为template最终是成为render函数,且每一次的数据改变都会导致整个组件的VNode重新渲染(VNode在differ之后的nextTick才会真的在DOM中重新渲染),其他方式的数据都有缓存,而调用methods的值没有缓存),则数据由调用methods而绑定的值,会随着任意模板数据改变而重新执行求值(无论methods使用的值是否变化)。

methods、生命周期钩子都能使用async-await e.g. ```javascript async mounted () { await this.login() console.log('等待上面await的Promise完成或失败后才往下继续执行') }, methods: { login () { return new Promise((resolve, reject) => { shouldLogin(err => { if (err) { reject(err) } resolve() }) }) }, async openQuestion () { await this.login() console.log('等待上面await的Promise完成或失败后才往下继续执行') } } ```
  1. 生命周期钩子

    async-await会阻止继续向下执行生命周期。e.g. beforeCreate(); created(); ...

    1. beforeCreate

    实例的(props、)datacomputedwatch等实例内容的创建。

    1. created

    渲染模板。

    1. beforeMount
    2. mounted

      不判断子组件是否挂载完毕。若希望整个视图都渲染完毕,可以用vm.$nextTick

    页面加载后就要展现的数据,可以在createdbeforeMountmounted请求(vue-routernuxt等封装了更方便的钩子,可控制更多执行时机,如:路由切换前后、数据加载完毕前后)。

    1. beforeUpdate
    2. updated

      不判断子组件是否更新完毕。若希望整个视图都渲染完毕,可以用vm.$nextTick

    3. activated

      <keep-alive/>组件特有,内部组件激活时在内部组件调用。

    4. deactivated

      <keep-alive/>组件特有,内部组件停用时在内部组件调用。

    5. beforeDestroy

      可将大部分内存清理工作放在beforeDestroy

    6. destroyed

      调用后,Vue实例指示的所有内容都会解绑,所有Vue事件监听器会被移除,所有子实例也会被销毁。

    7. errorCaptured
    生命周期图示 ![vue生命周期图](/knowledge/%E7%BD%91%E7%AB%99%E5%89%8D%E7%AB%AF/Vue.js%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/images/vue-lifecycle-1.png)
  2. mixins(数组,每一项为Vue实例的属性):混入

    mixin数组每一项中的属性,都会合并到组件本身的选项(如:mixin的methods合并到组件的methods、mixin的data合并到组件的data)。

    1. 钩子函数都会调用:混入对象的钩子优先调用,组件自身的钩子之后调用。
    2. 非钩子函数属性,若有同名内容,则合并之后,组件自身内容覆盖mixin:

      methodscomponentsdirectives等,合并为同一个对象;对象内部键名冲突时(如:methods都有某同名方法),使用组件对象的内容、丢弃mixin的内容。

    • 作用域:

      1. 局部:组件局部注册,仅在本组件内起作用,对子组件无效。
      2. 全局:Vue.mixin全局注册,将会影响之后创建的(之前的不受影响)Vue实例,包括第三方模板。
  3. extends(组件对象):扩展、继承一个组件

组件

所有Vue组件同时也都是Vue实例。

  1. 组件属性:

    1. template(字符串):组件的字符串模板
    2. props(数组或对象):接受父级传递内容

      1. 数组:接受的DOM属性名
      2. 对象:{ 接受的DOM属性名: 验证方式, }

        • 验证方式:

          1. 原生构造器(StringNumberBooleanFunctionObjectArray)、或SymbolBigInt、或null(允许任何类型)
          2. 上面类型组成的数组
          3. 对象

            1. type:原生构造器、或null、或原生构造器的数组
            2. required:是否必须(默认:false
            3. default:基本数据类型的值;对象或数组必须从工厂函数返回默认值(当且仅当没有传入时才使用或调用)

            requireddefault二选一。

            1. validator:验证方法(对子组件的任何修改包括v-show修改以及自身default,都会触发所有prop的验证方法)

            props会在一个组件实例创建之前进行验证,所以实例的属性(如:datacomputedmethods等)在defaultvalidator函数中不可用。

    3. data(方法):return数据对象

       data () {   // 组件多个实例间不共享数据对象
         return {
           a: 0,
           b: ''
         }
       }
      
      1. v-for循环的每个实例都调用创建一份。
      2. 仅执行一次,父组件传进来的props改变也不再触发执行。
    4. model(对象,包含propevent):修改v-model默认使用的属性和事件
    5. name(字符串)

      默认:undefined。 - 其他与Vue实例的属性相同(除了一些根级特有的选项)

  2. 注册组件方式:

    要确保在初始化Vue实例之前注册了组件。

     // 局部
     new Vue({
         components: {
             '组件元素名': 对象 // 仅在此Vue实例中可用
         }
     })
    
    
     // 全局
     Vue.component('组件元素名', 对象)
    
  3. 组件命名:

    W3C规定的组件命名要求:小写,且包含一个-

    1. JS注册组件或props

      -短横线隔开式(kebab-case)、小驼峰式(camelCase)、大驼峰式(PascalCase)。

    2. HTML中:

      1. 仅能使用-短横线隔开式(把大/小驼峰式用-隔开并小写单词代替)
      2. 在JS字符串模版、.vue组件,可以使用额外方式

        e.g. ```html 1. 2. 3. 1. 无 2. 3. 3. ```
  4. 使用组件:

    1. <组件名/>

      不会导致渲染出错的方式:JS字符串模版、.vue组件。

    2. <有效标签 is="组件名"/>
    3. 动态组件:<component :is="表达式"/>

      <component is="组件名字符串"/> 也可执行,只不过写死了某个组件,没有了「动态」的意义。

      <div id="test">
        <component :is="current1"/>
        <component :is="current2"/>
      </div>
      
      <script>
        const vm = new Vue({
          el: '#test',
          data: {
            current1: { template: '<p>111</p>' }, // 对象,则当作组件对象渲染
            current2: 'component2'                // 字符串,则搜索已注册的components
          },
          components: {
            component2: { template: '<p>222</p>' }
          }
        })
      </script>
      
      • <keep-alive/>包裹,保留它的状态或避免重新渲染。
    4. <keep-alive/>

      <keep-alive>组件</keep-alive>,会缓存不活动的组件实例,而不是销毁、重建。当组件在其内被切换时,组件的activateddeactivated被对应执行。

  5. 通信

    组件(Vue实例)有自己独立的作用域,虽然可以访问到互相依赖关系(vm.$parentvm.$childrenvm.$refs),但是不推荐(不允许)通过依赖获取、修改数据。

    1. 父子组件间的通信

      1. 父(props) -> 子:传入属性值;子(vm.$emit) -> 父:触发外部环境事件,外部事件再改变传进组件的props值。

        1. 父 -> 子:通过props向下传递初始化数据给子组件实例(不出现在DOM中)

          (当inheritAttrs默认true时,)添加在DOM上而不在props的声明,则仅添加到子组件最外层的DOM属性,不传入子组件。其中classstyle属性会合并,其他属性会覆盖。

          1. props是单向传递的:当父级的属性变化时,将传导给子组件,不会反过来

            每次父组件更新时,子组件的所有prop都会更新为最新值。

          2. 不应该 在子组件内部改变props(只能vm.$emit到父级再由父级传props进子组件来改变)。

            1. 仅展示:直接在模板引用props
            2. 一次性传值(仅首次传值有效,后续传值无法修改data。故不推荐):props -> data
            3. 每次对传值内容进行修改后使用:props -> computed
            4. 每次根据传值内容进行其他逻辑:props -> watch
            e.g. ```javascript Vue.component( 'myComponent', { props: ['father'], // 可以直接用在子组件内展示 data () { return { son1: this.father // 仅接受首次传值,后续的props变化不再改变(不推荐) } }, computed: { son2() { return this.father // 每次对传值内容进行修改后使用 } }, watch: { father(data) { // 每次根据传值内容进行其他逻辑 console.log(data) } }, template: '
            --
            ' } ) ```

          注意:避免引用数据类型导致的子组件改变父级。

          传递Function数据类型 ```vue ```
        2. 子 -> 父:通过vm.$emit向上传递事件、参数

          vm.$listeners能获得父级的所有能够从子级向上传递的事件。

          1. 在父级引用子组件处添加@自定义事件1="父方法"监听;

            自定义事件1是原生事件,可以添加.native修饰符,监听组件根元素的原生事件(不再接收子组件的 vm.$emit)。

          2. 在子组件方法体内添加vm.$emit('自定义事件1', 参数)向上传递。

      (不推荐)可以传递组件的Vue实例,提供给父级或子级调用。

      1. 父 -> 子:触发子组件的方法

        1. 父级引用子组件通过$refs直接调用子组件的方法(vm.$refs.子组件引用名.子组件的methods方法())。
        2. 父级通过传递props,子组件watch而触发子组件方法(或其他方式让子组件能够watch而执行的方式,如:vuex)。
    2. 非父子组件通信

      1. 通用

        1. 在简单的场景下,可以使用一个空的Vue实例作为中央事件总线。

           const bus = new Vue()   // vm.$emit只能向自己的Vue实例发送触发事件通知
          
           // 触发组件 A 中的事件
           bus.$emit('事件名', 传参)
          
           // 在组件 B 创建的钩子中监听事件
           bus.$on('事件名', function (para) {})
          
        2. 或专门状态管理模式,如:vuex

      2. 祖孙组件间的通信

        1. 通过provide/inject从祖辈向所有孙辈传递(不是响应式的,除非传递一个观察对象)数据

          (不推荐)可以传递组件的Vue实例,提供给祖辈或孙辈调用。

        2. 祖(props) -> 孙:逐层往下传递
        3. 孙(vm.$emit) -> 祖:逐层往上传递

          e.g. 1. 祖辈 ```html ``` 2. 中间传递的子级 1. 新增方法名(传递参数列表) ```vue ``` 2. 不增加方法名(传递参数列表组成的数组) ```vue ``` 3. 孙辈 ```vue ```
        4. v-slot<slot>:逐层插槽

          e.g. ```vue // 祖辈 // 中间子级 Son.vue // 孙辈 SonSon.vue ```
          • 组件的API来自三部分

      <my-component :子属性="父属性" @父事件="父方法"><template v-slot:某名字="临时变量">插槽内容</template></my-component>

      1. props:允许外部环境传递数据给组件。
      2. events:允许从组件内触发外部环境的副作用。
      3. slots:允许外部环境将额外的内容组合在组件中。
  6. 内置组件

    1. <component/>
    2. <transition/>
    3. <transition-group/>
    4. <keep-alive/>
    5. <slot/>
      • 杂项
    6. 父级引用组件时添加属性ref="字符串",可以在Vue实例的$refs中访问子组件。
    7. 内联模板:

      引用组件时,添加inline-templateDOM属性。组件的内容当作模板,而不是分发内容。

    8. <script type=text/x-template id="id名">

       Vue.component('组件名', {
         template: '#id名'
       })
      
    9. 当组件中包含大量静态内容时,可以考虑使用v-once将渲染结果在组件注册的template字段里缓存起来。
    10. 若组件内有多个if-else的展示逻辑,则尽量创建新的子组件,把每个子组件的逻辑放到子组件内部。
    11. 异步组件。
    12. 高级异步组件。
    13. 递归组件。
    14. 循环组件。

单文件组件

  1. (有导出的)组件内部可以直接引用自身组件(小心无止境的循环引用)
  2. 大部分都用局部注册。除非是大范围的统一功能,才用全局方式,才用插件方式。
  3. 样式引入

    1. 全局起作用(在任意地方引入样式,导致所有页面都使用样式)

      1. 在任意地方引入CSS文件(非 scoped

        在多个组件多次引入同一个CSS文件(允许同时使用JS和CSS两种方式混合引入),最终只会引入同一个CSS文件一次。

        1. <script>import '@/assets/文件名.css'</script>
        2. <style src="@/assets/文件名.css"/>
      2. 在任意地方引入没有 scoped<style>

        <style>样式内容</style>

      3. public/index.html中添加静态样式

    2. 局部样式

      1. scopedvue-loader的Scoped CSS

        1. scoped方式引用整个样式文件:

          <style scoped src="@/assets/文件名.css"/>

          全局起作用、无法识别@<style scoped>@import "../assets/文件名.css";</style><style>内只能用相对路径,不能用@/~

        2. scoped<style>

          <style scoped>样式内容</style>

        • 使用scoped单文件组件内所有Element(包括其引用的子组件),都会添加自定义attributes(如:data-v-2185bf6b);非Vue内部生成的节点(手动添加)不会添加自定义attributes

          若已添加scoped的子组件也添加scoped,则增加一份子组件的自定义attributes

          深度作用选择器:若希望scoped样式中的一个选择器能够作用得「更深」(如:影响子组件),则可使用>>>/deep/::v-deep操作符(不支持预处理器,如:scss)。

          e.g.

          <style scoped>
           .a >>> .b { /* 等价于:`.a /deep/ .b`或`.a ::v-deep .b` */ }
           .c > .d {}
          
           // 编译为
           .a[data-v-039c5b43] .b {}
           .c > .d[data-v-039c5b43] {}
          </style>
          
      2. modulevue-loader的CSS Modules

        <style>添加module,即在Vue实例内加入$style对象,访问单文件组件内<style module>的选择器。

  4. 可以在组件内部(或Vue实例内部)或外部,再创建另一个Vue实例,并且可以互相通信 >不推荐、反模式。 主要为了解决:要挂载到不在组件操作范围内的DOM、或组件外生成的DOM(如:富文本内要嵌入Vue实例)。 ```javascript // 某单文件组件One.vue import Vue from 'vue' import store from '@/store' // 若是nuxt,则直接使用实例上:vm.$store import router from '@/router' // 若是nuxt,则直接使用实例上:vm.$router import OtherComponent from '@/components/Other.vue' export default { data () { return { text: '123', vm: null } }, mounted () { // 新建一个Vue实例 const OtherVue = Vue.extend(OtherComponent) // 创建Vue子类,继续创建Vue实例需要再`new`创建好的子类 this.vm = new OtherVue({ // 后定义,类似于mixin的合并逻辑(钩子:先定义执行 -> 后定义执行;非钩子:后定义执行、先定义忽略) store, // 共享store。若是nuxt,则直接:store: this.$store router, // 共享router。若是nuxt,则直接:router: this.$router beforeDestroy () { // 合并。先定义的钩子先执行,后定义的钩子后执行 this.$el.remove() // 删除dom自己 }, methods: { // 合并。若属性名相同则后定义的覆盖先定义的 changeFatherText: () => { this.text = Math.random() // this为当前的组件One.vue } }, computed: { // 合并。若属性名相同则后定义的覆盖先定义的 myText: () => { return this.text // // this为当前的组件One.vue } }, propsData: { 传入的props属性: 值 } }).$mount() document.getElementById('other').appendChild(this.vm.$el) }, beforeDestroy () { this.vm.$destroy() } } ```

过渡/动画

此处描述的「帧」是requestAnimationFrame(在浏览器下一次重绘之前执行),而不是Vue的nextTick(在Vue控制的DOM下次更新循环结束之后执行)。

  1. <transition/>(仅针对第一个子元素)

    1. 过渡/动画效果仅加在第一个子元素上(子元素内部不会触发)。
    2. <transition/>内仅能展示唯一一个子元素(多个元素用<transition-group/>)。
    3. 不添加真实的DOM,只有逻辑(<transition-group/>会添加真实的DOM)。
    4. 组件最外层是<transition/>无法触发动画,要在外部嵌套一层DOM。

    提供给包裹的第一个子元素(任何DOM或组件)进入/离开的过渡/动画效果。

    1. 触发条件:

      ①在<transition/>内嵌套子元素,在子元素上进行以下操作;或②引用根节点是<transition/>的组件,在父级引用上进行以下操作、或在此组件根节点<transition/>内嵌套的子元素进行以下操作:

      1. v-ifv-elsev-else-if
      2. v-show
      3. <component :is="表达式"/>
    2. 触发机制:

      1. 自动嗅探目标元素是否应用了CSS过渡/动画。若是,则在恰当的时机添加/删除CSS类名。
      2. <transition/>添加了过渡钩子,则这些钩子函数在恰当的时机被调用。
      • 若以上都没有,则DOM操作在下一帧执行。
    3. 特殊的props(在<transition/>组件引用上添加):

      1. name:用于自动生成CSS过渡/动画类名的前缀(默认:'v'

        在进入/离开的过渡/动画中,class切换(若监测到已手动添加了相关类的样式,才会切换) >1. 以`name`默认为`'v'`为例。 >2. 过渡:CSS的`transition`;动画:CSS的`animation-@keyframes`。 1. 进入过程 1. `v-enter`: >定义具体进入过渡的样式(若`v-enter-active`使用`animation-@keyframes`,则可以不设置`v-enter`的样式)。 定义进入过渡的开始状态。存在时间:在元素插入之前开始;在元素插入之后的下一帧结束(与此同时`v-enter-to`被添加)。 2. `v-enter-active`: >定义进入过渡/动画的过程时间、延迟、曲线函数(CSS的`transition`或`animation-@keyframes`)。 定义进入过渡/动画的整个状态。存在时间:在`v-enter`存在之后、且元素插入之前开始;在过渡/动画完成之后结束。 3. `v-enter-to`: >定义元素的初始状态(若和元素本身样式相同,则可以不设置`v-enter-to`的样式)。 定义进入过渡的结束状态。存在时间:在元素插入之后下一帧开始 (与此同时`v-enter`被移除);在过渡/动画完成之后结束。 2. 离开过程 1. `v-leave`: >定义元素的初始状态(若和元素本身样式相同,则可以不设置`v-leave`的样式)。 定义离开过渡的开始状态。存在时间:在离开过程触发时开始;下一帧结束(与此同时`v-leave-to`被添加)。 2. `v-leave-active`: >定义离开过渡/动画的过程时间、延迟、曲线函数(CSS的`transition`或`animation-@keyframes`)。 定义离开过渡/动画的整个状态。存在时间:在`v-leave`存在之后开始;在过渡/动画完成之后结束。 3. `v-leave-to`: >定义具体离开过渡的样式(若`v-leave-active`使用`animation-@keyframes`,则可以不设置`v-leave-to`的样式)。 定义离开过渡的结束状态。存在时间:在离开过程触发的下一帧开始(与此同时`v-leave`被移除);在过渡/动画完成之后结束(与此同时元素移除)。
      2. 自定义过渡/动画的类名

        优先级高于普通的类名,主要用于和第三方CSS动画库配合,如:animate.css

        1. enter-class
        2. enter-active-class
        3. enter-to-class
        4. leave-class
        5. leave-active-class
        6. leave-to-class
      3. type:指定过渡事件类型('transition''animation'。默认:自动检测出持续时间长的)
      4. duration:不根据过渡/动画的JS事件,而手动设置过渡/动画时间(毫秒)

        e.g. :duration="1000":duration="{ enter: 500, leave: 800 }"

      5. css:是否使用CSS过渡/动画的class(默认:true。若false,则只触发过渡钩子)
      6. 过渡钩子事件:

        建议对于仅使用JS过渡的<transition/>添加:css="false"

        1. 进入过程

          1. before-enter
          2. enter(与CSS结合使用。当只用JS过渡时,在enter中必须调用函数的第二个参数done
          3. after-enter
          4. enter-cancelled
        2. 离开过程

          1. before-leave
          2. leave(与CSS结合使用。当只用JS过渡时,在leave中必须调用函数的第二个参数done
          3. after-leave
          4. leave-cancelled
      7. appear:是否在初始渲染时使用过渡/动画(默认:false

        • (可选)初始渲染时使用额外的类名、钩子事件(:appear="true"

          1. 初始渲染的自定义过渡/动画的类名:

            1. appear-class
            2. appear-active-class
            3. appear-to-class
          2. 初始渲染的过渡钩子事件:

            1. before-appear
            2. appear
            3. after-appear
            4. appear-cancelled
      8. mode:(当有子元素切换时,)控制进入/离开的过渡顺序('out-in':先离开后进入;'in-out':先进入后离开。默认:同时进行)
    4. <transition/>内嵌的子元素切换:

      1. 若是原生DOM子元素用v-ifv-elsev-else-if)作为切换,则必须在这些子元素上添加 key 属性。
      2. 若是组件子元素,则不需要额外添加 key 属性。

        1. v-ifv-elsev-else-if)作为切换。
        2. 用动态组件<component :is="表达式"/>进行切换。

选择:仅渲染一个节点:<transition/>;渲染多个节点:<transition-group/>

  1. <transition-group/>

    子元素必须添加 key 属性。

    1. 触发条件、触发机制与<transition/>类似。
    2. 特殊的props(在<transition-group/>组件引用上添加):

      1. tag:子级外层嵌套的标签名(默认:'span'
      2. move-class:用于自动改变定位时引用的类名(默认:name属性的值 + '-move'
      • 除了 mode 无效之外,拥有与<transition/>相同的props

自制可复用的过渡组件:把<transition/><transition-group/>作为根组件。

插件(plugin)

// 插件是.js文件,应当有一个公开的install方法
const MyPlugin = {}
MyPlugin.install = function (Vue, options) { // 第一个参数是Vue构造器,第二个参数是Vue.use时传入的可选参数对象
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源(Vue.directive、Vue.filter、Vue.component)

  // 3. 全局混入
  Vue.mixin({
    created: function () {
      // 逻辑...
    },
  })

  // 4. 添加实例方法(约定用`$`开头来命名原型自定义属性)
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}


// 安装插件处使用
Vue.use(MyPlugin, { /* 向MyPlugin传入的参数 */ })  // Vue.use会自动阻止多次注册相同插件,届时只会注册一次该插件。

特性

  1. Vue实例代理的属性(propsdatacomputedmethodsprovide/inject的属性,或mixins传入的属性),在内部vm.名字访问,可以直接使用,所有属性名字都不能重复(filters不属于同一类);也有以$开头的Vue实例属性(如:vm.$elvm.$propsvm.$datavm.$watch)。

    只有已经被代理的内容是响应的(Vue实例被创建时传入的属性),值的改变(可能)会触发视图的重新渲染。

  2. Vue实例的属性Vue实例的属性的属性,慎用箭头函数,因为this的指向无法按预期指向Vue实例。
  3. 因为HTML不区分大小写,所以大/小驼峰式命名的JS内容,在HTML使用时要转换为相应的-短横线隔开式。

    不受限制、不需要转换:JS字符串模版、.vue组件。

    JS字符串模版:<script type="text/x-template">、JS内联模板字符串。

  4. Vue实例(Vue)或Vue组件实例(VueComponent)挂载在DOM,会在这个DOM上增加__vue__属性指向这个Vue实例或Vue组件实例(生产环境也行)。

vue风格指南

响应式系统

  1. 当把对象传给Vue实例的data和vuex的store时,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter(访问器属性)。

    • Object.defineProperty

      类似的:Object.defineProperties

      数据劫持,直接修改数据触发、或事件监听触发。

      • 针对数组,修改了数组的某些mutator方法,调用这些方法后也触发。
        1. 针对不需要响应式变化的属性(但要展示到template),可以用Object.freeze处理,就不会进行Object.defineProperty响应式处理。

          e.g. `x: Object.freeze({ y: 1 })`: 1. `x`是响应式的。 2. `x`指向的对象不是响应式的,无法通过`x.y = 2`去响应式修改。 3. 若`x = { y: 2 }`指向新的响应式对象,之后`x.y = 3`会响应式触发视图更新。
        2. 针对不需要展示到template的属性,可以不把属性初始化在data中,而是直接vm.属性 = 值在JS中使用(这个属性值无法绑定到template中显示,意味着给这个属性值赋值之后,``也总是为空)。
        3. 针对所有这个组件的实例全部共用同一个值、不响应式更新、不能放在template内的值,可以放在组件外部(类似上面的那用用法)。
  2. 每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新(虚拟DOM系统)。
  3. 响应式操作:Vue实例的data的属性值、vuex的store的属性值

    约定:原对象、原数组、原属性均为已存在的、响应式的数据。

    1. 导致视图更新:

      1. 原数组的某些mutator方法:pushpopunshiftshiftreversesortsplice
      2. 原属性赋值给任意类型的新值(=

        • 原属性的属性值是原对象/原数组:

          1. 扩展原对象:原对象 = Object.assign({}, 原对象, { 键: 新值 })原对象 = { ...原对象, 键: 新值 }
          2. 扩展原数组:原数组 = [ 新值, ...原数组, 新值 ]concat
            • 空对象/空数组赋值(= []= {})也是一次重新赋值操作
      3. Vue.set(原对象/原数组, 键/索引, 新值):原对象/原数组添加属性或修改原属性
      4. Vue.delete(原对象/原数组, 键/索引):原对象/原数组删除原属性
    2. 无法检测原对象/原数组变动:

      Vue不能检测到原对象属性的新增或删除,因此添加Vue.set/delete

      1. 原对象的属性赋值(=)。

        替代方法:Vue.set(原对象, 键, 新值)原对象 = Object.assign({}, 原对象, { 键: 新值 })原对象 = { ...原对象, 键: 新值 }

      2. 原数组

        1. 原数组索引赋值(=),已存在索引或新增索引都不行。

          替代方法:Vue.set(原数组, 索引, 新值)原数组.splice(索引, 1, 新值)

        2. 直接修改原数组长度。

          替代方法:原数组.splice(新长度)

        3. 原数组的最新mutator方法:copyWithinfill

虚拟DOM系统

Vue.js参考:snabbdom的虚拟DOM实现。

约定 1. 虚拟节点 (virtual node,VNode):Vue通过建立一个VNode反映真实DOM结构。 2. 虚拟DOM(virtual DOM):由Vue组件树建立起来的整个**VNode树**。
  1. 在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。

    e.g. ```javascript // 虚拟DOM的伪代码(对象实现的树形结构) let domNode = { tag: 'ul', attributes: { id: 'myId' }, children: [ // where the LI's would go ] } ```
  2. 结合响应式系统,Vue能够智能地计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少。

虚拟DOM系统的辩证思考:Why Virtual DOM is slower 根据定义,**虚拟DOM** 比 **精细地直接更新DOM(`innerHTML`)** 更慢。虚拟DOM是执行DOM更新的一种折衷方式、一种权衡,尽管没有提升性能,但带来很多好处,可以提升开发人员的开发效率(提供了开发人员:「用数据驱动视图」代替「频繁操作DOM」的能力)。
  1. (响应式系统和)虚拟DOM系统只能单向操作DOM结构。

    若使用非Vue控制的方法直接修改DOM结构,无法反向对响应式系统和虚拟DOM系统起效果,将导致虚拟DOM系统与DOM结构不匹配。因此若使用了虚拟DOM系统,必须严格控制仅用此系统操作DOM结构。

SSR

  1. 若在mounted之前改变DOM,会导致「客户端激活」(client-side hydration)时,客户端的虚拟DOM(从服务端渲染完毕传递来的JSON字符串,在客户端解析而成。如:nuxt的window.__NUXT__)和服务端传来的DOM(服务端渲染完毕传输来的HTML)不同而出问题。

    客户端激活时 1. 在开发模式下,Vue将判断(从服务端渲染完毕传递来的JSON字符串,在客户端解析而成的)虚拟DOM(virtual DOM),是否与从服务端渲染完毕传输来的DOM结构(DOM structure)匹配。若无法匹配,则将退出混合模式,丢弃现有的DOM并从头开始渲染。 2. 在生产模式下,此检测会被跳过,以避免性能损耗。 3. 重新渲染可能导致页面抖动、闪屏。

    建议SSR的应用,不要在mounted之前进行能导致模板变化的数据修改。

    服务端默认禁用模板数据的响应式,因此响应式操作仅会在客户端进行。但是如:watchimmediate: true的方法,在服务端也会执行,因此就需要process.client/process.server来判断是否允许服务端执行。

    注意SSR与v-html中的有可能修改html的功能(比如:lazysizes会尽可能快地处理标记图片),会导致重新渲染。

  2. 服务端也会调用的钩子:beforeCreatecreated

    将全局副作用代码移动到服务端不会运行的生命周期钩子中(除了 beforeCreatecreated 之外的其他钩子)

(nuxt)把直出内容放在asyncData/fetch;把客户端异步请求放在mounted;不使用 beforeCreate/created/beforeMount

  1. 接口依赖

    e.g.

     function apiFetch () {      // 所有接口都返回Promise。错误处理:每个接口、每个Promise.all/race后添加`.then/catch`
       return Promise.all([      // 互不依赖的接口:apiA、apiB、apiC
         apiA.then(() => {
           return Promise.all([  // 依赖apiA的互不依赖的接口apiAA、apiAB、apiAC
             apiAA,
             apiAB,
             apiAC
           ])
         }),
         apiB,
         apiC
       ])
     }
    

Vue实现原理

组件 -> h函数 -> VNode -> HTML

  1. h函数

    创建VNode的方法封装。

  2. VNode

    用对象来反映真实DOM的情况。

    1. 种类

      1. html/svg元素
      2. 组件
      3. 纯文本
      4. Fragment
      5. Portal
  3. 渲染器(render)

    将VNode渲染成特定平台下真实DOM的函数。

    1. mount(原本没有VNode)
    2. patch(原本有VNode)

      相同类型的VNode才需要比较,不同类型直接完全替换;相同HTML标签才需要比较,不同标签直接完全替换。

      1. Diff

例子

  1. 使用debounce或throttle ```vue ``` ```vue ```

Vue 3 与 Vue 2 区别

  1. 双向数据绑定原理不同

    1. 2

      利用ES5的Object.defineProperty对数据进行劫持,结合发布订阅模式的方式来实现。

    2. 3

      使用了ES6的Proxy对数据代理。

      • 相比vue2.x的Object.defineProperty,使用Proxy的优势:

        1. Object.defineProperty只能监听某个属性,不能对全对象监听
        2. 可以省去for-in,闭包等内容来提升效率(直接绑定整个对象即可)
        3. 可以监听数组,不用再去单独的对数组做特异性操作,Proxy可以检测到数组内部数据的变化
  2. 虚拟DOM的diff算法不同

    1. 2

      递归遍历两个虚拟DOM树,并比较每个节点上的每个属性(有点暴力算法),来确定实际DOM的哪些部分需要更新。

    2. 3

      采用block tree的做法。

  3. 是否支持碎片(Fragment)

    1. 2

      不支持,必须:<template><div>多个节点</div></template>

    2. 3

      支持,允许:<template>多个节点</template>

  4. API类型不同

    1. 2

      使用选项类型api(Options API),在代码里分割了不同的属性:data、computed、methods、等。声明选项的方式书写Vue组件。

    2. 3

      使用合成型api(Composition API),使用方法来分割,相比于旧的api使用属性来分组,这样代码会更加简便和整洁。使用函数的方式书写组件。

  5. 定义数据变量和方法、生命周期钩子函数、父子通信 不同

    1. 2

      定义数据变量要在data(){},创建的方法要在methods:{}中,计算属性要在computed:{}中,生命周期钩子要在组件属性中调用,父子通信用this.props/emit

      e.g. ```javascript export default { props: { title: String }, data () { return { username: '', password: '' } }, computed: { lowerCaseUsername () { return this.username.toLowerCase() } }, methods: { login () { // this.props // this.emit } }, mounted () {} } ```
    2. 3

      需要使用一个新的setup属性方法,此方法在组件初始化构造的时候触发。

      1. 使用以下三个步骤来建立反应性数据:

        1. 从vue引入reactive
        2. 使用reactive()方法来声明数据为响应性数据;
        3. 使用setup()方法来返回我们的响应性数据,从而template可以获取这些响应性数据。
      e.g. ```javascript import { reactive, computed, onMounted } from 'vue' export default { props: { title: String }, setup (props, content) { const state = reactive({ username: '', password: '', lowerCaseUsername: computed(() => state.username.toLowerCase()) }) const login = () => { // props // content.emit } onMounted(()=>{}) return { state, login } } } ```
  6. this不同

    1. 2

      指向当前组件

    2. 3

      undefined

  7. 指令与插槽不同

    1. 2

      使用slot可以直接使用slot;v-for优先级高于v-if,而且不建议一起使用。

    2. 3

      必须使用v-slot的形式;只会把当前v-if当做v-for中的一个判断语句,不会相互冲突;移除keyCode作为v-on的修饰符,当然也不支持config.keyCodes;移除v-on.native修饰符;移除过滤器filter。

  8. main.js文件不同

    1. 2

      可以使用prototype(原型)的形式去进行操作,引入的是构造函数。

    2. 3

      需要使用结构的形式进行操作,引入的是工厂函数;vue3中app组件中可以没有根标签。

  9. ts的支持不同

    1. 2

      支持Flow。

    2. 3

      支持ts。


vue-router

  1. 初始化

     new Vue({
       router: new VueRouter({
         mode: 'hash或history或abstract',    // 默认:hash
         base: '/...基路径/',           // 仅在history模式下,设置应用的基路径
         routes: [
           // 普通路由
           {
             path: '路由参数',       // 除了path,其他都是可选
             component: 组件,
             name: '路由名',
             redirect: 路由参数,     // 重定向(URL变化)
             alias: 路由参数,        // 别名(URL不变化)
             props: 布尔或对象或函数, // 传参进组件(布尔值决定vm.$route.params是否被设置为组件属性;对象或函数则传入具体属性)。针对components要再嵌套一层对象
             beforeEnter: 方法,
             meta: '额外信息参数',
             caseSensitive: 布尔值,       // 匹配规则是否大小写敏感(默认:false)
             pathToRegexpOptions: 对象,   // 编译正则的选项
           },
    
           // 动态路由
           {
             path: ':动态路由',
             component: 组件
           },
    
           // 命名视图
           {
             path: '路由参数',
             components: {
               'default': 组件,
               '视图名1': 组件,
             },
             props: {
               'default': 布尔或对象或函数,
               '视图名1': 布尔或对象或函数
             }
           },
    
           // 嵌套路由(可以嵌套所有类型的路由)
           {
             path: '路由参数',
             component: 组件,
             children: [
               路由配置,
             ]
           },
         ],
         linkActiveClass: 'router-link-active',              // 激活<router-link/>的CSS类名
         linkExactActiveClass: 'router-link-exact-active',   // 精确激活<router-link/>的CSS类名
         scrollBehavior: 方法,
         parseQuery/stringifyQuery: 方法,
         fallback: 方法,
       }),
     })
    
  2. 内置组件

    1. <router-link/>:导航

      1. to:目标地址
      2. replace:(默认:false)是否使用replace替换push
      3. tag:(默认:a)生成其他标签名
      4. append:(默认:false)是否是相对路径
      5. exact:(默认:false):是否精确激活
      6. event:(默认:'click'):触发导航的事件
      7. active-class:(默认:'router-link-active'):链接激活时使用的CSS类名
      8. exact-active-class:(默认:'router-link-exact-active'):被精确匹配时应该激活的CSS类名
    2. <router-view/>:渲染路由匹配的组件(可嵌套)

      name:(默认:'default')命名视图的名字

  3. 注入后在组件中增加

    1. $router:路由器实例对象

      1. app:Vue根实例
      2. mode:路由模式
      3. currentRoute$route
      4. beforeEach/beforeResolve/afterEach(方法):导航守卫
      5. go(数字)
      6. back()
      7. forward()
      8. push(路由字符串或对象[, 完成回调函数[, 失败回调函数]])
      9. replace(路由字符串或对象[, 完成回调函数[, 失败回调函数]])

      push/replace仅修改search值(或history模式的hash值),则不会改变路由也不刷新页面。如:vm.$router.push/replace('?a=xx')

      1. getMatchedComponents(location?)
      2. resolve(location, current?, append?)
      3. addRoutes(routes)
      4. onReady(callback, [errorCallback])
      5. onError(callback)
    2. $route:当前激活的路由的状态信息

      1. path:绝对路径
      2. params:动态路由的路由参数对象
      3. query:查询参数对象
      4. hash:hash值
      5. matched:匹配到的路由数组
      6. fullPath:完整路径
      7. name:路由名称
      8. redirectedFrom:若存在重定向,则为重定向来源的路由名字
      • 检测路由变化,可以在组件中watch注入的$route或使用额外的钩子(导航守卫) ```javascript new Vue({ watch: { '$route' (to, from) { // 对路由变化作出响应... } } }) ```
    3. 增加钩子:beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

  4. 导航守卫

    守卫接收三个参数to, from, next 1. `to`:即将进入的目标`$route` 2. `from`:正在离开的`$route` 3. `next`:`resolve`方法
    1. 全局(通过router对象)

      1. router.beforeEach((to, from, next) => {})
      2. router.beforeResolve((to, from, next) => {})
      3. router.afterEach((to, from) => {})
    2. 路由配置中

      beforeEnter (to, from, next) {}

    3. 组件内新增钩子

      1. beforeRouteEnter (to, from, next) {}
      2. beforeRouteUpdate (to, from, next) {}
      3. beforeRouteLeave (to, from, next) {}
    • 完整的导航解析流程

      1. 导航被触发;
      2. 在失活的组件里调用beforeRouteLeave守卫;
      3. 调用全局的beforeEach守卫;
      4. 在重用的组件里调用beforeRouteUpdate守卫;
      5. 在路由配置里调用beforeEnter
      6. 解析异步路由组件;
      7. 在被激活的组件里调用beforeRouteEnter
      8. 调用全局的beforeResolve守卫;
      9. 导航被确认;
      10. 调用全局的afterEach钩子;
      11. 触发DOM更新;
      12. 用创建好的实例调用beforeRouteEnter中的next回调函数。
  5. 注意

    1. 组件最大限度复用:路由切换时,若两个路由都渲染同个组件,则不会销毁重建,而是直接复用,因此被复用的组件的生命周期钩子不会再被调用。
    2. 匹配优先级:有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

vuex

store的概念:vuex提供的容器,state的集合。

一个专为Vue.js应用程序开发的状态管理模式。采用集中式存储管理应用的所有组件的状态(仅一个实例对象就能负责保存整个应用的状态,「唯一数据源」),并以相应的规则保证状态以一种可预测的方式发生变化。vuex的状态存储是响应式的,若store中的状态发生变化,则有读取状态的组件(computed依赖状态或直接输出状态)也会相应地得到高效更新。

vuex流程图

  1. state

    状态(仅能在mutation方法体内改变state)。

    1. 通过store的state.state名(或state.module名.state名)获取。
    2. 响应规则(和Vue的响应式系统一致)

      约定:原对象、原数组、原属性均为已存在的、响应式的数据。

      • 最好提前初始化所需的state(初始化的任何位置都被Object.defineProperty处理过,能够响应式更新)。
      1. 原数组的某些mutator方法:pushpopunshiftshiftreversesortsplice
      2. 原属性赋值给任意类型的新值(=
      3. 原对象/原数组添加属性或修改原属性:

        1. Vue.set(state或state.原对象/原数组, 键/索引, 新值)
        2. state.原对象 = Object.assign({}, state.原对象, { 键: 新值 })
        3. state.原对象 = { ...state.原对象, 键: 新值 }
        4. state.原数组 = 经过处理的新数组
      4. 原对象/原数组删除原属性:Vue.delete(state或state.原对象/原数组, 索引/键)
    • mapState 返回对象,用于组件`computed`属性的简写(`data1 () { return this.$store.state.state1 }`)。 ```javascript // 组件中 import { mapState } from 'vuex' new Vue({ computed: { ...mapState({ // 不需要使用组件属性 data1: 'state1', // 等价于:data1: state => state.state1, // 需要使用组件属性 data2 (state) { return state.state2 + this.组件属性 } }), // 或 ...mapState([ // computed使用与state属性相同名字可用数组 'state1', 'state2' ]) }, }) ```
  2. getters

    由state或其他getter派生的值。

    1. 通过store的getters['getter名'](或getters['module名/getter名'])获取。
    2. 根据方法返回值的依赖而改变(仅能在自己的getter中改变值,不能在其他getter或mutations或actions改变值),store的计算属性。
    3. (就像Vue的computed一样,)getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算;若getter返回一个函数,每次都会去进行调用,而不会缓存结果。
    • mapGetters 返回对象,用于组件`computed`属性的简写(`data1 () { return this.$store.getters['getters1'] }`)。 ```javascript // 组件中 import { mapGetters } from 'vuex' new Vue({ computed: { ...mapGetters({ data1: 'getters1', data2: 'getters2', }), // 或 ...mapGetters([ // computed使用与getters属性相同名字可用数组 'getters1', 'getters2' ]) } }) ```
  3. mutations

    改变state的唯一方式。

    1. 通过store调用commit('mutation方法名'[, 参数])commit({ type: 'mutation方法名'[, 参数的项: 值] })(或commit('module名/mutation方法名'[, 参数])commit({ type: 'module名/mutation方法名'[, 参数的项: 值] }))触发。
    2. 必须是同步函数。

      返回的内容无效,使用时不会有返回值。

    3. 改变state使其保持与视图的响应——和Vue的响应式系统一致。
    • mapMutations 返回对象,用于组件`methods`方法的简写(`method1 (data) { this.$store.commit('mutate1', data) }`)。 ```javascript // 组件中 import { mapMutations } from 'vuex' new Vue({ methods: { ...mapMutations({ // 方法都可传参数。e.g. this.method1(参数) method1: 'mutate1', method2: 'mutate2', }), // 或 ...mapMutations([ // methods使用与mutations方法相同名字可用数组 'mutate1', 'mutate2' ]) } }) ```
  4. actions

    仅触发mutations,而不直接改变state

    1. 通过store调用dispatch('action方法名'[, 参数])dispatch({ type: 'action方法名'[, 参数的项: 值] })(或dispatch('module名/action方法名'[, 参数])dispatch({ type: 'module名/action方法名'[, 参数的项: 值] }))触发。
    2. 可以进行异步操作。

      可以返回任何内容,包括Promise。

    3. 第一个参数对象包含:stategetterscommitdispatch(,模块模式还有:rootStaterootGetters)。
    • mapActions 返回对象,用于组件`methods`方法的简写(`method1 (data) { this.$store.dispatch('act1', data) }`)。 ```javascript // 组件中 import { mapActions } from 'vuex' new Vue({ methods: { ...mapActions({ // 方法都可传参数。e.g. this.method1(参数) method1: 'act1', method2: 'act2', }), // 或 ...mapActions([ // methods使用与actions方法相同名字可用数组 'act1', 'act2' ]) } }) ```
非模块模式使用vuex ```javascript import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) // 注入 export default new Vuex.Store({ // 导出Vuex实例 state: { // 暴露的state数据 state1: 0, state2: 0 }, getters: { // 暴露的state计算数据 getters1: (state, getters) => { // 第一个参数是state对象,第二个参数是getters对象(只读) return state.state1 }, getters2: (state, getters) => { return state.state2 + getters['getters1'] }, getters3: (state, getters) => { // 成员可以返回方法,若返回方法则不缓存,每次都需要调用执行 return () => state.state1 * 2 } }, mutations: { // 暴露的改变state的方法 mutate1 (state, 参数) { // 第一个参数是state对象,第二个参数是commit调用方法的第二个参数 // 仅同步操作 state.state1++ } }, actions: { // 暴露的触发mutations的方法(可异步操作,也可返回Promise实例,也可使用async-await,还可触发其他actions) act1 (context, 参数) { // 第一个参数是类似Vuex.Store实例对象(只读),第二个参数是dispatch调用方法的第二个参数 return new Promise((resolve, reject) => { setTimeout(() => { context.commit('mutate1', 参数) resolve() }, 0) }) }, async act2 (context) { await context.dispatch('act1', 123) // 等待 act1 完成 console.log('act1完成后才往下继续执行') } } }) // new Vue({ store: 上面导出的内容 })的实例中通过this.$store访问Vuex实例 this.$store.state.state1 // 输出state this.$store.getters['getters1'] // 输出getters this.$store.getters['getters3']() // 输出getters方法的值 this.$store.commit('mutate1', 参数) // 触发mutations this.$store.dispatch('act1', 参数) // 触发actions ```
  1. 模块方式

    1. 模块内部

      模块vuex对根vuex均是只读。

      1. getters的第三个参数rootState是根state、第四个参数rootGetters是根getters。
      2. actions的第一个参数context的:rootState属性是根state、rootGetters属性是根getters。

      使用其他模块的信息:rootState.模块名.state属性rootGetters.模块名.getters属性

      1. (组件使用) state需要增加路径:store的state.模块路径.state名获取。
      2. 命名空间namespaced(主要针对getters、mutations、actions)

        不使用mapState的直接使用states时,无论namespacedfalse/true,都是相同的引用方式:vm.$store.state.module名.state名

        1. false(默认)

          (组件使用) getters、mutations、actions是注册在全局命名空间,与非模块方式调用相同;无法针对某个模块而使用mapState/mapGetters/mapMutations/mapActions的第一个参数或createNamespacedHelpers

          此时若要用mapState,则只能:...mapState({ 名字: state => state.module名.state名 })

        2. true

          (组件使用) getters、mutations、actions调用要在名称中增加路径:模块路径/名称;可以针对某个模块而使用mapState/mapGetters/mapMutations/mapActions的第一个参数或createNamespacedHelpers

          • actions的定义内部

            1. 默认的commitdispatch只对本模块;
            2. 添加{ root: true }至第三参数,表示针对根vuex:

              1. commit('mutation方法名 或 模块名/mutation方法名', 参数, { root: true })
              2. dispatch('action方法名 或 模块名/action方法名', 参数, { root: true })
    2. 动态注册registerModule、动态卸载unregisterModule

vue-cli

快速构建Vue应用的脚手架,可以使用Vue官方或第三方模板来进行Vue应用的配置,主要包括webpack等工具的配置。

  1. 任何放置在public文件夹的静态资源都会被简单的复制,而不经过webpack。需要通过绝对路径来引用。

    1. 绝对路径:/images/foo.pnghttp://xxx
    2. 相对路径:以.~@开头。作为一个模块请求被webpack解析。
  2. 环境变量

    1. 构建时:根据命令额外加载环境变量文件

      构建时,Node.js环境中的process.env都会加入(但没有BASE_URL)。

      1. 所有情况 加载 .env
      2. vue-cli-service serve 加载 .env.development
      3. vue-cli-service build 加载 .env.production
    2. 客户端页面:自动添加环境变量文件中以VUE_APP_开头的环境变量

      除了VUE_APP_开头的环境变量之外,还有NODE_ENVBASE_URL会自动添加到客户端页面。

      1. 在.js访问:process.env.VUE_APP_名字/NODE_ENV/BASE_URL
      2. 在public的.html访问:<%= VUE_APP_名字/NODE_ENV/BASE_URL %>

nuxt

基于Vue的通用应用框架(SPA或SSR),把webpack、babel、vue-server-renderer、vue-router、vuex、vue-meta等工具整合在一起,并通过自带的nuxt.config.js统一配置,不需要对每个工具进行单独配置。

框架内的Vue组件都是以Vue单文件组件的形式,每一个pages目录下的组件都是一个页面路由。

  1. 目录结构

    大部分新增的方法都可以用async-await

    1. pages:页面目录

      Vue单文件组件。目录中的.vue文件自动生成对应的路由配置和页面。

      pages目录下组件新增几个属性(其他目录下无效) 1. `asyncData`(拥有上下文) >可在客户端**或**服务端渲染时调用(二选一)。 >`async-await`会阻止继续向下执行生命周期。e.g. `await asyncData(); beforeCreate(); ...` 页面组件被初始化前调用(组件还未创建,无法使用`this`引用组件实例)。`return`的数据与`data`方法返回的数据合并后返回当前页面组件,数据可以被修改(与`data`的数据一致)。 2. `fetch`(拥有上下文) >可在客户端**或**服务端渲染时调用(二选一)。 >`async-await`**不**会阻止继续向下执行生命周期。e.g. `...; fetch(); mounted(); ...` 页面组件被初始化前调用(组件还未创建,无法使用`this`引用组件实例)。在渲染页面前操作状态(store)。 >
      >实现fetch跳转到错误页面,错误页面能正常跳回 > >```javascript >// 某页面 >export default { > fetch ({ route, redirect, error }) { > return new Promise((resolve, reject) => { > // 某些原因触发重定向 > redirect(302, '/错误页面路由', { '指定query': route.fullPath }) // 浏览器路由会变化 > // 或:error({ 'statusCode': '302', message: '错误信息' }) // 浏览器路由不会变化 > > // resolve() // 没有重定向需要添加resolve() > }) > } >} > > >// 错误页面操作 >this.$router.replace(this.$route.query.指定query || '/') // 刷新 >this.$router.back() // 后退 >this.$router.replace('/') // 返回首页 >``` >
      >1. `asyncData/fetch`若未返回完成状态的Promise(方法体内`return new Promise((resolve, reject) => {...})`),则不会向下执行之后的钩子(页面渲染失败、不输出页面,可以设置未完成和失败状态的组件或行为)。 > > 若要使用`redirect`,不要再运行`reject` >2. 最佳实践: > > 1. 把页面展示所必须的请求放在`asyncData/fetch`,并返回Promise来控制完成后才继续执行代码,这样之后代码需要的异步数据就可以放心使用(否则store的数据可能还未初始化,`undefined.属性`会报错)。 > 2. 把客户端的异步请求以及其他操作模板的行为都放在`mounted`及之后(否则可能导致「客户端激活」时,客户端的虚拟DOM和服务端传来的DOM不同而出问题)。 >3. SSR的页面组件,可以在客户端用路由切换的方式在客户端触发`asyncData/fetch`(路由切换:`vm.$router.方法()`、或``、或``)。 3. `head`(`this`指向本组件vue实例) >在非页面组件也完全有效,如:`components`(组件目录)、`layouts`(布局目录)。 添加、覆盖`nuxt.config.js`的`head`属性(按加载顺序,后引用的覆盖先前的值)。 4. `layout`(拥有上下文) 引用`layout`目录的布局文件。 5. `middleware` 引用`middleware`目录的中间件JS文件。 6. `scrollToTop` 控制页面渲染前是否滚动至页面顶部。默认:`false`。 7. `transition` Vue的``组件配置。 8. `validate`(拥有上下文) 用于校验动态路由参数的有效性。`return false`则自动加载显示错误页面(加载layouts的error.vue,浏览器路由不会变化)。 9. `watchQuery` 1. (默认)search值修改时执行: `transition`、`middleware`、`layout`、`validate` 2. 监听对应的参数字符串更改,search值修改时执行: `transition`、`middleware`、`layout`、`validate`、**`asyncData`**、**`fetch`** 10. `loading`
    2. assets:待编译资源目录

      默认使用webpack的vue-loader、file-loader、url-loader加载器进行编译的资源,如:脚本(js、jsx、tsx、coffee)样式(css、scss、sass、less)模版(html、tpl)JSON图片字体文件。

      • 引用方式:组件中HTML、ES6引用~/assets/

      对于不需要编译的静态资源可以放在static目录。

    3. static:静态资源目录

      不需要webpack编译的静态资源。该目录下的文件会映射至项目根路径。

      • 引用方式:组件中HTML引用/

      生产环境不允许随便用第三方CDN,可以把不打包的资源放到static目录从而放到自己服务端里去引用。

    4. components:组件目录

      Vue单文件组件。提供给项目中所有组件使用。

      • 引用方式:组件中ES6引用~/components/
    5. plugins:Vue插件目录

      JS文件(可注入Vue,拥有上下文)。在Vue根应用的实例化前需要运行的Vue插件(Vue添加全局功能,仅执行一次。切换路由之后不再执行)。

      • 除了Vue原本就有的Vue.use(插件),还可用nuxt特有的export default 方法(操作context、inject)

        e.g. ```javascript // plugins/stat-plugin.js export default (context, inject) => { // 在Vue实例、组件、`pages`组件新增属性(`asyncData`、`fetch`、`layout`)的上下文.app、store的actions/mutations,创建`$stat`方法 inject('stat', () => {}) // 直接在context上新建属性(不推荐),之后所有用到context的地方都能使用这个属性 context.mydata = '值' } ```
      • 引用方式:nuxt.config.js中加入plugins属性

        nuxt.config.js文件引用plugins目录下插件的方式 ```javascript // nuxt.config.js module.exports = { plugins: [ // 一般也会配置在vendor.bundle.js中 '~/plugins/插件文件名', { src: '~/plugins/插件文件名', ssr: false } // ssr设置是否也在服务端运行。true(默认):在服务端和客户端都运行;false:仅在客户端运行 // 对于不支持ssr的插件,可以用ssr: false的方式引入,然后在plugins文件夹内配置安装 ], ... } ```
    6. store:状态目录

      store目录存在,则:引入vuex -> 增加vuex至vendor -> 设置Vue根实例的store属性。

      1. 创建

        1. 普通方式:

          store/index.js导出Vuex.Store实例对象,包含stategettersmutationsactions属性。

          e.g. ```javascript // store/index.js import Vuex from 'vuex'; export default () => { return new Vuex.Store({ state: { state1: 0 }, getters: { state2(state, getters) { return state.state1; }, state3(state, getters) { return state.state1 * getters['state2']; }, }, mutations: { mutate1(state, data) { state.state1 += data; } }, actions: { act1(context, data) { context.commit('mutate1', data); } } }); }; ```
        2. 模块方式:

          store目录下index.js为根vuex对象,其他每个.js文件被转换为以文件名命名的状态模块(开启namespaced)。都导出stategettersmutationsactions变量。

          e.g. ```javascript // store/index.js export const state = () => ({ num: 0 }); export const getters = { num2(state, getters) { return state.num; }, num3(state, getters) { return state.num * getters['num2']; } }; export const mutations = { mutateNum(state, data) { state.num += data; } }; export const actions = { actNum(context, data) { context.commit('mutateNum', data); } }; // store/module1.js export const state = () => ({ state1: 0 }); export const getters = { state1(state, getters, rootState, rootGetters) { return state.state1 + rootState.num + rootGetters['num2']; } }; export const mutations = { mutateState1(state, data) { state.state1 += data; } }; export const actions = { actState1(context, data) { context.commit('mutateState1', data + context.rootState.num + context.rootGetters['num2']); } }; ```
      2. 使用

        创建并自动设置Vue根实例的store属性后,即可在组件的实例内使用vm.$store。引入所有store文件夹内的.jsindex.js是非模块方式引入,其他文件均为模块方式引入)。 ```vue ```
    7. layouts:布局目录

      Vue单文件组件。扩展默认布局(default.vueerror.vue)或新增自定义布局,在布局文件中添加<nuxt/>指定页面主体内容。

      1. 可添加供所有页面使用的通用组件(另外:静态的自定义通用内容添加在根目录的app.html)。

        所有路径最后都会嵌套在default.vue内,设置了layout的也会再次嵌套,error.vue也会再次嵌套。

      2. layouts/error.vue被当做pages来使用(增加了/error的路由):在layouts中创建了error.vue后,就不要 在pages中创建error.vue

      • 引用方式:pages组件中加入layout属性

        pages目录下组件引用layouts目录下布局的方式 ```vue ... ``` ```javascript // pages/页面名.vue export default { layout: '布局文件名', ... } ```
    8. middleware:中间件目录

      JS文件(拥有上下文)。路由跳转之后,在页面渲染之前运行自定义函数(在加载plugins之前引入,但仅在首次路由加载和每次路由切换时执行)。执行顺序:nuxt.config.js -> layouts -> pages

      可以做权限、UA等判断后执行跳转、修改store或其他行为。

      • 引用方式:nuxt.config.js文件或layouts组件或pages组件中加入middleware属性

        nuxt.config.js文件或layoutspages目录下组件引用middleware目录下中间件的方式 ```javascript // middleware/中间件文件名.js export default function (context) { // 路由跳转之后,且在每页渲染前运行 } ``` ```javascript // nuxt.config.js module.exports = { router: { middleware: ['中间件文件名'] }, ... } // layouts/布局文件名.vue module.exports = { middleware: ['中间件文件名'], ... } // pages/页面名.vue module.exports = { middleware: ['中间件文件名'], ... } ```
    9. 根目录中创建自定义文件夹,放置JS模块,提供给其他文件引用(如:api存放接口请求、utils存放通用方法)
    10. .nuxt:nuxt构建过程资源目录(不要修改、加入.gitignore)

      nuxt.config.jsbuildDir可修改。

    别名 1. `srcDir`:`~`或`@` 2. `rootDir`:`~~`或`@@` - 默认的`srcDir`等于`rootDir`
  2. nuxt.config.js:配置文件 1. `alias` 配置引用别名。
    e.g. ```javascript import { resolve } from 'path' export default { alias: { 'images': resolve(__dirname, './assets/images'), 'style': resolve(__dirname, './assets/style'), 'data': resolve(__dirname, './assets/other/data') } } ``` JS直接使用,CSS或HTML(非JS的)等,需要加`~`前缀: ```vue ```
    1. `build` 配置webpack构建。 1. ~~`.vendor`~~(已废弃): >若在多个页面内都引用了某插件,默认情况下,在应用打包发布时,这个插件会被打包到每一个页面对应的JS。 添加进公用插件`vendor.bundle.js`,避免每个页面都打包一次插件,以减少项目bundle的体积。
    e.g. ```javascript module.exports = { build: { vendor: ['已安装插件名', '~/plugins/插件文件名'] }, ... } ```
    >应用代码比库文件修改频繁,应尽量将(第三方)库打包至单独的文件中去(`vendor.bundle.js`)。 2. `.analyze` 使用[webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)分析并可视化构建后的打包文件。 3. `.extend` 为客户端和服务端的构建配置进行手工的扩展处理。 4. `extractCSS` 使用[extract-css-chunks-webpack-plugin](https://github.com/faceyspacey/extract-css-chunks-webpack-plugin)将各样式内容提取到各自单独的CSS文件中。因此能够缓存样式文件。 1. `false`(默认) 样式由 初始化 或 每个路由的.js 插入`
  3. 上下文(nuxt特有) >来自:[nuxt: 上下文对象](https://nuxtjs.org/docs/internals-glossary/context/)。 上下文对象存在于nuxt的生命周期中(在`plugins`、`middleware`、pages的`asyncData/fetch/validate`、store的`actions`。不在Vue的 ~~`vm`~~ 中),包含属性: 1. `app` 2. `route` 3. `store` 4. `params` 5. `query` 6. `redirect` 浏览器路由会变化。(类似Vue的`vm.$router.push('/error')`) >若`redirect('/error')`,则先判断pages的error.vue,再判断layouts的error.vue。 7. `error` 跳转layouts的error.vue,浏览器路由不会变化。 8. `env` 9. `isStatic` 10. `isDev` 11. `isHMR` 12. `req` 13. `res` 14. `beforeNuxtRender` 15. `from` >可以在context出现的地方给context添加新属性(不能修改上面列出的已经存在的属性),但不推荐这么做。若要在context添加属性,则最好仅在`plugins`统一给context添加属性。
  4. 内置组件

    1. <nuxt/>

      仅在layouts组件中标识引入的pages组件。

    2. <nuxt-child/>

      仅在嵌套路由的pages组件中引入pages组件。

    3. <nuxt-link/>

      <router-link/>类似。

      • 区别

        1. <nuxt-link/>

          会预加载<nuxt-link/>指向的所有pages脚本(如:pages_路由1.js),这些脚本执行也会加载相应的样式内容(如:<style>link href

        2. <router-link/>

          不会预加载<router-link/>指向的pages脚本。

    4. <no-ssr/>

      设置组件内部内容不在服务器渲染中呈现。

  5. 流程

    流程图 ![nuxt流程图](/knowledge/%E7%BD%91%E7%AB%99%E5%89%8D%E7%AB%AF/Vue.js%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/images/nuxt-1.png)
    1. 页面加载时,先进行Vue、vue router、vuex的初始化,再初始化middleware,最后初始化plugins至Vue实例;随后在首次路由加载和每次路由切换时进行middleware导出方法的运行;最后进行页面的asyncData,然后进行组件的vue原本钩子和页面的fetchbeforeCreate -> props -> data -> computed -> watch -> created -> beforeMount(SSR没有) -> fetch -> mounted(SSR没有)。
    2. 服务端渲染进行步骤至created(包括)。
    3. Vue组件的生命周期钩子中,仅有beforeCreatecreated在客户端和服务端均被调用(服务端渲染),其他钩子仅在客户端被调用。
  6. 路由

    依据pages目录结构和文件自动生成vue-router模块的路由配置;在nuxt.config.jsgeneraterouter属性中修改默认路由配置。

    1. 动态路由:.vue文件或文件夹增加_前缀

       pages/
       --| users/
       -----| _id.vue         # <- /users/id动态值
       -----| index.vue       # <- /users/
       # 或
       --| _num/
       -----| comments.vue    # <- /num动态值/comments
       -----| index.vue       # <- /num动态值/
      
      针对动态路由进行静态化(nuxt generate),需要在服务端设置内部重定向 1. nginx: ```text location / { try_files $uri $uri/ /index.html; } ```
      • 验证动态路由的参数 ```javascript export default { validate({ params }) { return /^\d+$/.test(params.userid); // 校验为数字 }, ... } ```
    2. 嵌套路由:新增与.vue文件(父级)同名的文件夹存放子级,在父级内添加<nuxt-child/>展示子级

      总是显示父级内容,其中<nuxt-child/>根据路由选择子级组件。

       pages/
       --| users/
       -----| _id.vue         # 若路由为/users/id动态值,则父级内容的<nuxt-child/>指向此组件
       -----| index.vue       # 若路由为/users/,则父级内容的<nuxt-child/>指向此组件
       --| users.vue          # 父级内容
      
      • 父级、子级组件,根据vm.$route.name来判断在哪一个嵌套路由

        1. 路由/mine/mine/(没有 /mine/index):路由名字mine
        2. 路由/mine/post/mine/post/:路由名字mine-post
    3. 动态嵌套路由:动态的父级嵌套动态的子级

    • 在组件中使用<nuxt-link/>进行路由跳转(与vue-router的<router-link/>一致)

    • 自定义路由配置:

      1. nuxt.config.jsrouter.extendRoutes配置
      2. 或middleware设置重定向redirect
      3. 或pages下新建一个页面重定向redirect
  7. 视图

    1. layouts组件(可添加供所有页面使用的通用组件
    2. nuxt.config.jshead属性
    3. 定制HTML模板

      在根目录添加app.html,可添加供所有页面使用的静态的自定义通用内容。

      app.html ```html <!DOCTYPE html> 其他自定义HTML内容 ```
  8. 样式

    1. <style src="@/assets/文件名.css"/>多处引入会导致重复引入。

      Vue在多个组件多次引入同一个CSS文件(允许同时使用JS和CSS两种方式混合引入),最终只会引入同一个CSS文件一次。

    2. 样式加载规则:

      1. nuxt的开发模式:pages引入的样式会在vue router路由抵达时按需加载,vue router路由切换时依旧保留之前已加载过的样式。
      2. nuxt的发布模式:提前统一加载所有全局样式,而不是路由抵达时才加载。

      Vue提前统一加载所有全局样式,而不是路由抵达时才加载:在任意地方引入样式,导致所有页面都使用样式。

    • 策略:

      1. (除了使用scoped<style>之外)在单独使用样式的pages中,仅用<script>import '@/assets/文件名.css'</script>,不用 <style src="@/assets/文件名.css"/>
      2. 共用的样式加入nuxt.config.jscss
  9. 使用预处理器

    (nuxt内置依赖了vue-loader,因此若安装了对应的依赖包后,则能够)在组件中的<template><script><style>通过lang属性使用各种预处理器。

    e.g. ```vue ```
  10. 错误页面

    在layouts中创建error.vue可作为:页面报错后自动跳转、或调用上下文对象的error跳转、或路由/error的页面。

    在layouts中创建了error.vue后,就不要 在pages中创建error.vue

  11. nuxtCI

    1. nuxt:以开发模式启动一个基于vue-server-renderer的服务端

      1. 添加--spa:以SPA的方式打开服务,asyncData/fetch在客户端执行(因此所有请求都会在客户端发起),否则asyncData/fetch会在服务端执行。
      2. 添加--port 端口号:指定端口号。
    2. nuxt build:利用webpack编译应用,压缩JS、CSS(用于发布SSR或SPA)

      --spa:创建所有路由的.html文件,且这些.html都完全一致,加载时根据vue-router进行路由计算(可以刷新的SPA)。

      vue相关资源不会自动tree shaking,但是会去除注释掉的内容(开发模式不会去除);额外引用的文件会tree shaking+去除注释。

    3. nuxt start:以生产模式启动一个基于vue-server-renderer的服务端(依赖nuxt build生成的资源)
    4. nuxt generate:预渲染生成静态化文件(用于发布预渲染的静态页面)

      创建所有路由的.html文件(去除动态路由),每个文件都是预渲染完成的不同页面。

      会跑一遍nuxt的流程(Vue、vue router、vuex的初始化 -> middleware、plugins的初始化 -> middleware -> asyncData -> beforeCreate -> props->data->computed->watch -> created -> fetch),生成静态化文件。

      增加--help参数查看nuxt命令可带参数。

    • 部署发布

      1. SSR

        nuxt build -> nuxt start

      2. 预渲染

        nuxt generate

        1. 针对无动态数据的静态页面(没有服务端返回的动态数据)。
        2. 忽略动态路由。
        3. 构建时跑完一遍nuxt流程,所以客户端不会再进行asyncData、fetch的执行。
      3. SPA

        nuxt build --spa

  12. 输出至生产环境的方案

    1. SSR:服务端渲染(与开发模式的SSR相同)。
    2. 静态化页面

      1. 针对动态路由要设置服务端重定向至index.html,用history路由模式。
      2. 针对无法修改服务端设置本地文件打开形式的访问(file://,用hash路由模式。

        服务端是否设置重定向都可行。

jQuery与Vue.js对比

  1. 做的事情

    1. jQuery

      (旧时代到现在)相对于原生JS,更好的API,兼容性极好的DOM、AJAX操作。面向网页元素编程。

    2. Vue.js

      实现MVVM的vm和view的「双向绑定」(单向数据流),实现自己的组件系统。面向数据编程。

  2. 优劣势对比

    1. jQuery

      1. 兼容性好,兼容基本所有当今浏览器;出现早,学习、使用成本低。
      2. 程序员关注DOM,频繁操作DOM;代码量较多且不好维护、扩展,当页面需求变化之后代码改动难度大。
    2. Vue.js

      1. 程序员关注数据,DOM的操作交给框架;代码清晰、强制规范,利于维护;有自己的组件系统。
      2. 不兼容旧版本浏览器;需要一些学习成本。