Vue 自检

时间:2021-2-20 作者:admin

这些知识最好的文档就是官方文档

生命周期

官网

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对 property 和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法
例如 created: () => this.fetchTodos()。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

  1. beforeCreate 在数据观测和初始化事件(event/watcher)还未开始之前

  2. created 完成数据观测,属性和方法的运算。但 $el 属性还不可用

  3. beforeMount 在挂载前调用,相关的 render 函数第一次调用。实例已完成编译模板,把 data 的数据和模板生成 html。但还未挂载 html 到页面上。
    该钩子在服务器端渲染期间不被调用。

  4. mounted 实例被挂载后调用,这时 el 被新创建的 vm.el替换了。可以在mounted内部使用vm.el 替换了。可以在 mounted 内部使用 vm.elmounted使vm.nextTick。
    该钩子在服务器端渲染期间不被调用。

  5. beforeUpdate 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
    该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。

  6. updated 虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
    该钩子在服务器端渲染期间不被调用。

  7. activated 被 keep-alive 缓存的组件激活时调用。
    该钩子在服务器端渲染期间不被调用。

  8. deactivated 被 keep-alive 缓存的组件停用时调用。
    该钩子在服务器端渲染期间不被调用。

  9. beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
    该钩子在服务器端渲染期间不被调用。

  10. destroyed 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
    该钩子在服务器端渲染期间不被调用。

总计:
data 在 created 可获得。
el 在 mounted 获得, $nextTick
beforeCreated 和 created 可在服务端渲染使用,其余都不可以。
每个生命周期都不能用箭头函数

父子组件生命周期

加载渲染过程
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

更新过程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

v-for / v-if 唯一 key

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。key 不同就会被重新渲染。
Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

  • key会用在虚拟DOM算法(diff算法)中,用来辨别新旧节点。
  • 不带key的时候会最大限度减少元素的变动,尽可能用相同元素。(就地复用)
  • 带key的时候,会基于相同的key来进行排列。(相同的复用)
  • 带key还能触发过渡效果,以及触发组件的生命周期

参考文章 vue中的key

不要用 index 做 key index 为什么不要做 key

在 v-for 里,如果是中间插入数据,会导致期之后的 index 全部变化,都要重新渲染,开销很大。而且可能会发生两个不一样的东西却被误以为是一样的了,因为用了一样的 index。推荐使用唯一 id 做 key

v-show v-if

v-show 的元素始终会被渲染并保留在 DOM 中,只是简单的基于CSS进行切换。而 v-if 是在销毁和重建中的。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-if 中也会遇到 key 的问题
cn.vuejs.org/v2/guide/co…

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。

在项目遇到这样的情形,很多表单,用v-if v-else 区分不同状态下的显隐。因为不同状态用的模板一样,但状态改变时,输入框前的 label 改变了,但是输入框内的文字依然保留之前的文字。如果加了表达检验的话,这些校验都错乱了。

这就是因为这些表单并没有重新渲染,我们就要加上唯一key,让vue认为他们都是完全独立的。这又可以引出 diff算法了。

data 为什么是函数

cn.vuejs.org/v2/guide/co…

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。如果是对象的话,一个组件被多次引用,就会被多个地方更改数据,这是不合理的。

watch 和 computed 和 methods

重点, computed 当且仅当其依赖的数据改变时,会重新计算。

computed 和 methods 对比
同一函数也可定义为一个方法。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。而方法是每次都要重新计算。
计算属性是基于响应性依赖缓存的,方式没有。
调用时方式必须是函数,计算属性是属性,并可以定义成 get/set变成可读写属性

watch的使用场景是:当在data中的某个数据发生变化时, 我们需要做一些操作, 或者当需要在数据变化时执行异步或开销较大的操作时. 我们就可以使用watch来进行监听。

watch 和 computed的区别是:

相同点:他们两者都是观察页面数据变化的。

不同点:computed 只有当依赖的数据变化时才会计算, 当数据没有变化时, 它会读取缓存数据。
watch 没有缓存,每次都需要执行函数。当需要在数据变化时执行异步或开销较大的操作时,watch 最有用的。

vue 响应式原理,依赖收集

Dep 对象用于依赖收集,它实现了一个发布订阅模式,完成了数据 Data 和渲染视图 Watcher 的订阅

Vue 采用数据劫持和发布订阅模式

  1. Vue2 在初始化时,首先通过 Object.defineProperty 对 Data 每个属性绑定 get 和 set ,监听所有的 Data 中的数据变化。同时 Observer模块 会创建 Dep 用来搜集使用该 Data 的 Watcher。Dep 对象基于发布订阅模式实现的,用于依赖收集。Watcher 是在 beforeMount生命周期 创建 (并将 Dep.target 标识为当前 Watcher。) Watcher 创建时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。

  2. 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后触发 Dep 的 depend 方法,最终触发 Dep 的 addSub 将当前的 Watcher 对象加入到依赖收集池 Dep 中。

  3. 数据更新时,会触发 Data 的 set 方法,继而触发 Dep 的 notify方法,notify方法会通知所有使用到该 Data 的 Watcher对象 调用其 update 方法去更新试图,所有使用到这个 Data 的 Watcher 会加入一个队列,并开启一个异步队列进行更新,最终执行 _render 方法完成页面更新。

响应式原理
juejin.im/post/685766…

3.0 之前是使用 Object,defineProperty 劫持数据,3.0 使用 Proxy

object.defineProperty 缺点

  1. 不能监听数组;因为数组没有 getter 和 setter,因为数组的长度不确定,太长性能负担很大

  2. 只能监听属性,而不是整个对象,需要遍历对象。

  3. 只能监听属性的变化,不能监听属性的删减。也正因如此,vue文档要求,给数组或对象新增属性时,需要用 vm.$set 才能保证新增的属性也是响应式的。

proxy

优点

  1. 可以监听数组

  2. 可以监听整个对象,而不是属性

  3. 13种拦截方法,强大很多

  4. 返回新对象,而不是直接修改原对象。

Reflect 是内置对象,可以简单化内部操作
一些语言内置的方法 [[get]] [[set]] [[delete]] 不方便调用,用 Reflect 就可以,还是函数返回

缺点

不兼容 IE,且目前无法用 polyfill 磨平

发布订阅模式

class Observer {
  constructor() {
    this.caches = {}
  }

  on(event, fn) {
    this.caches[event] = this.caches[event] || [];
    this.caches[event].push(fn);
  }

  emit(event, data) {
    if (this.caches[event]) {
      this.caches[event].forEach(fn => fn(data))
    }
  }

  off(event, fn) {
    if (this.caches[event]) {
      const newCaches = fn ? this.caches[event].filter(e => e !== fn) : [];
      this.caches[event] = newCaches;
    }
  }
}

组件传值问题

1. 父子组件传值

1 最常用 props
2 子组件用 $parent 获取父组件
3 v-bind 配合 .sync

2. 子父组件传值

1 子组件 emit触发自定义事件,父组件v−on监听2父组件emit 触发自定义事件,父组件 v-on 监听 2 父组件 emitvon2children 获取子组件
3 父组件 $refs 获取子组件

3. 祖先组件

1 attrs和attrs 和 attrslisteners
情形:A 是 B 父组件,B 是 C 父组件。A 给 C 传值

<C v-bind="$attrs" v-on="$listeners"></C>
利用B组件为中介,B 中调用 C 组件时,使用 v-on 绑定 listeners,v−bind绑定listeners, v-bind 绑定 listeners,vbindattrs;
这样 C 组件就可以获取 A组件调用B组件写的属性和事件了

listeners包含的是事件,listeners 包含的是事件, listenersattrs 是属性
C 组件使用: attrs.name或者触发事件了this.attrs.name 或者触发事件了 this.attrs.namethis.emit(‘funA’)

举例 segmentfault.com/a/119000002…

2 provide 和 inject
父组件通过 provide 提供属性,不论子组件多深,都可调用 inject 获取数据

// 父组件
provide: {
  for: 'test';
},
data() {
  return ...
}

// 子组件
inject: ['for'],
data() {
  return {
    msg: this.for
  }
}

4. event bus 非父子组件,其实所有组件都可以

使用一个空的 Vue 实例作为中央事件总线,结合 emitemit emiton 使用

Bus 定义方式

1 抽离为独立模块,组件按需引入
2 将Bus挂载到Vue根实例的原型上
3 将Bus注入到Vue根对象上

// bus.js
import Vue from 'vue';
const Bus = new Vue();
export default Bus;

// 2
import Vue from 'vue';
Vue.prototype.$bus = new Vue();

// 3
import Vue from 'vue';
const Bus = new Vue();
new Vue({
  el: '#app',
  data: {
    Bus
  }
})

// 组件使用时
this.$Bus.$emit() // 触发事件
this.$Bus.$on() // 监听事件
// 注册的Bus要在组件销毁时卸载,否则会多次挂载,造成一次触发多次响应的问题。
beforeDestory() {
  this.$Bus.$off('方法名')
}

5. Vuex 状态管理

Vuex 是 Vue 的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单来说就是:应用遇到多个组件共享状态时,使用 vuex。

属性:

  1. state:vuex的基本数据,用来存储变量
  2. getter:state 的读取方法,相当于state的计算属性
  3. mutation:提交更新数据的方法,必须是同步的。它会接受 state 作为第一个参数,提交载荷作为第二个参数。
  4. action:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
  5. modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

vuex 的使用流程:
页面通过 dispatch 派发异步事件到 action。action 通过 commit 把对应参数同步提交到 mutation,mutation 会修改 state 中对应的值。最后通过 getter 把对应值跑出去。

异步代码只有 actions 来处理,不能使用 mutation。换句话说,mutation 必须是同步的,而action 里想干嘛就干嘛。
vuex 用 devtools 追踪状态变化,就是追踪 Mutation,如果 mutation 有异步操作就不能正常追踪了。(异步状态未知)

同步的意义在于每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。

尤雨溪自己说的 www.zhihu.com/question/48…

之前公司实际项目中。一般都是将 接口函数 统一封装起来在一个 api 文件夹下,通过 api字段 暴露出来。然后将会被多个组件公用的请求,写在 action 中。再通过改变 mutation 来改变 state 。组件 dispatch 派发即可。

简单实现:

原文:https://zhuanlan.zhihu.com/p/166087818

class Store {
  constructor(options) {
    this.vm = new Vue({
      data:{
        state: options.state
      }
    })

    let getters = options.getter || {}
    this.getters = {}
    Object.keys(getters).forEach(getterName => {
      Object.defineProperty(this.getters,getterName,{
        get:()=> {
          return getters[getterName](this.state)
        }
      })
    })

    let mutations = options.mutations || {}
    this.mutations = {}
    Object.keys(mutations).forEach(mutationName => {
      this.mutations[mutationName] = (arg) =>  {
        mutations[mutationName](this.state,arg)
      }
    })

    let actions = options.actions
    this.actions = {}
    Object.keys(actions).forEach(actionName => {
      this.actions[actionName] = (arg) => {
        actions[actionName](this,arg)
      }
    })
  }

  dispatch(method,arg){
    this.actions[method](arg)
  }
  // 修改代码
  commit = (method,arg) => {

    this.mutations[method](arg)
  }
  get state(){
    return this.vm.state
  }
}

vuex 与 redux 对比

juejin.cn/post/684490…

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。