这些知识最好的文档就是官方文档
生命周期
所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对 property 和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法
例如 created: () => this.fetchTodos()。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。
-
beforeCreate 在数据观测和初始化事件(event/watcher)还未开始之前
-
created 完成数据观测,属性和方法的运算。但 $el 属性还不可用
-
beforeMount 在挂载前调用,相关的 render 函数第一次调用。实例已完成编译模板,把 data 的数据和模板生成 html。但还未挂载 html 到页面上。
该钩子在服务器端渲染期间不被调用。
-
mounted 实例被挂载后调用,这时 el 被新创建的 vm.el替换了。可以在mounted内部使用vm.el 替换了。可以在 mounted 内部使用 vm.el替换了。可以在mounted内部使用vm.nextTick。
该钩子在服务器端渲染期间不被调用。
-
beforeUpdate 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。
-
updated 虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
该钩子在服务器端渲染期间不被调用。
-
activated 被 keep-alive 缓存的组件激活时调用。
该钩子在服务器端渲染期间不被调用。
-
deactivated 被 keep-alive 缓存的组件停用时调用。
该钩子在服务器端渲染期间不被调用。
-
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
该钩子在服务器端渲染期间不被调用。
-
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 为什么是函数
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。如果是对象的话,一个组件被多次引用,就会被多个地方更改数据,这是不合理的。
watch 和 computed 和 methods
重点, computed 当且仅当其依赖的数据改变时,会重新计算。
computed 和 methods 对比
同一函数也可定义为一个方法。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。而方法是每次都要重新计算。
计算属性是基于响应性依赖缓存的,方式没有。
调用时方式必须是函数,计算属性是属性,并可以定义成 get/set变成可读写属性
watch的使用场景是:当在data中的某个数据发生变化时, 我们需要做一些操作, 或者当需要在数据变化时执行异步或开销较大的操作时. 我们就可以使用watch来进行监听。
watch 和 computed的区别是:
相同点:他们两者都是观察页面数据变化的。
不同点:computed 只有当依赖的数据变化时才会计算, 当数据没有变化时, 它会读取缓存数据。
watch 没有缓存,每次都需要执行函数。当需要在数据变化时执行异步或开销较大的操作时,watch 最有用的。
vue 响应式原理,依赖收集
Dep 对象用于依赖收集,它实现了一个发布订阅模式,完成了数据 Data 和渲染视图 Watcher 的订阅
Vue 采用数据劫持和发布订阅模式
-
Vue2 在初始化时,首先通过 Object.defineProperty 对 Data 每个属性绑定 get 和 set ,监听所有的 Data 中的数据变化。同时 Observer模块 会创建 Dep 用来搜集使用该 Data 的 Watcher。Dep 对象基于发布订阅模式实现的,用于依赖收集。Watcher 是在 beforeMount生命周期 创建 (并将 Dep.target 标识为当前 Watcher。) Watcher 创建时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。
-
编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后触发 Dep 的 depend 方法,最终触发 Dep 的 addSub 将当前的 Watcher 对象加入到依赖收集池 Dep 中。
-
数据更新时,会触发 Data 的 set 方法,继而触发 Dep 的 notify方法,notify方法会通知所有使用到该 Data 的 Watcher对象 调用其 update 方法去更新试图,所有使用到这个 Data 的 Watcher 会加入一个队列,并开启一个异步队列进行更新,最终执行 _render 方法完成页面更新。
响应式原理
juejin.im/post/685766…
3.0 之前是使用 Object,defineProperty 劫持数据,3.0 使用 Proxy
object.defineProperty 缺点
-
不能监听数组;因为数组没有 getter 和 setter,因为数组的长度不确定,太长性能负担很大
-
只能监听属性,而不是整个对象,需要遍历对象。
-
只能监听属性的变化,不能监听属性的删减。也正因如此,vue文档要求,给数组或对象新增属性时,需要用 vm.$set 才能保证新增的属性也是响应式的。
proxy
优点
-
可以监听数组
-
可以监听整个对象,而不是属性
-
13种拦截方法,强大很多
-
返回新对象,而不是直接修改原对象。
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 父组件 emit触发自定义事件,父组件v−on监听2父组件children 获取子组件
3 父组件 $refs 获取子组件
3. 祖先组件
1 attrs和attrs 和 attrs和listeners
情形: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,v−bind绑定attrs;
这样 C 组件就可以获取 A组件调用B组件写的属性和事件了
listeners包含的是事件,listeners 包含的是事件, listeners包含的是事件,attrs 是属性
C 组件使用: attrs.name或者触发事件了this.attrs.name 或者触发事件了 this.attrs.name或者触发事件了this.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。
属性:
- state:vuex的基本数据,用来存储变量
- getter:state 的读取方法,相当于state的计算属性
- mutation:提交更新数据的方法,必须是同步的。它会接受 state 作为第一个参数,提交载荷作为第二个参数。
- action:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
- 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 } }