Vuex

它是一个程序里面的状态管理模式,它是集中式存储所有组件的状态的小仓库,并且保持我们存储的状态以一种可以预测的方式发生变化。

state

类似于vue中的data

  1. import Vue from 'vue';
  2. import Vuex from 'vuex';
  3. Vue.use(Vuex);
  4. const store = new Vuex.Store({
  5. state: {
  6. // 定义一个name,以供全局使用
  7. name: '张三',
  8. // 定义一个number,以供全局使用
  9. number: 0,
  10. // 定义一个list,以供全局使用
  11. list: [
  12. { id: 1, name: '111' },
  13. { id: 2, name: '222' },
  14. { id: 3, name: '333' },
  15. ]
  16. },
  17. });
  18. export default store;

在项目文件中通过 this.$store.state.XXX 来取值。
建议将取值放在computed属性中

  1. export default {
  2. mounted() {
  3. console.log(this.getName);
  4. },
  5. computed:{
  6. getName() {
  7. return this.$store.state.name;
  8. }
  9. },
  10. }

修饰器 :getters

对state数据进行修饰,类似computed属性。

  1. state: {
  2. name: '张三',
  3. number: 0,
  4. list: [
  5. { id: 1, name: '111' },
  6. { id: 2, name: '222' },
  7. { id: 3, name: '333' },
  8. ]
  9. },
  10. // 在store对象中增加getters属性
  11. getters: {
  12. getMessage(state) { // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上
  13. return `hello${state.name}`;
  14. }
  15. }

在项目中通过 this.$store.getters.getMessage来进行取值。
每次通过this.$store.xxx 来取state 和getters 的值很繁琐,可在computed中进行解构

  1. <script>
  2. import { mapState, mapGetters } from 'vuex';
  3. export default {
  4. mounted() {
  5. console.log(this.name);
  6. console.log(this.getMessage);
  7. },
  8. computed: {
  9. ...mapState(['name']),
  10. ...mapGetters(['getMessage']),
  11. },
  12. }
  13. </script>

也可以取别名
...mapGetters({ aliasName: 'getMessage' }), // 赋别名的话,这里接收对象,而不是数组

Mutation

类似于项目中的methods
对state中的数据进行修改。
错误做法:this.$stroe.state.xxx = xxxx

  1. const store = new Vuex.Store({
  2. state: {
  3. name: '张三',
  4. number: 0,
  5. },
  6. mutations: { // 增加nutations属性
  7. setNumber(state) { // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数
  8. state.number = 5;
  9. }
  10. },
  11. });

在项目中通过this.$store.commit('setNumber'); 来执行。

Mutation传多个参数

  1. mutations: {
  2. setNumber(state) {
  3. state.number = 5;
  4. },
  5. setNumberIsWhat(state, payload) { // 增加一个带参数的mutations方法,并且官方建议payload为一个对象
  6. state.number = payload.number;
  7. },
  8. },

在项目中调用:this.$store.commit('setNumberIsWhat', { number: 666 }); _// 调用的时候也需要传递一个对象_Mutation中的函数不能有异步函数。

我们在组件中可以使用mapMutations以代替this.$store.commit(‘XXX’) 但是要写在methods中

  1. <script>
  2. import { mapMutations } from 'vuex';
  3. export default {
  4. mounted() {
  5. this.setNumberIsWhat({ number: 999 });
  6. },
  7. methods: { // 注意,mapMutations是解构到methods里面的,而不是计算属性了
  8. ...mapMutations(['setNumberIsWhat']),
  9. },
  10. }
  11. </script>

Actions 异步操作

  1. const store = new Vuex.Store({
  2. state: {
  3. name: '张三',
  4. number: 0,
  5. },
  6. mutations: {
  7. setNumberIsWhat(state, payload) {
  8. state.number = payload.number;
  9. },
  10. },
  11. actions: { // 增加actions属性
  12. setNum(content) { // 增加setNum方法,默认第一个参数是content,其值是复制的一份store
  13. return new Promise(resolve => { // 我们模拟一个异步操作,1秒后修改number为888
  14. setTimeout(() => {
  15. content.commit('setNumberIsWhat', { number: 888 });
  16. resolve();
  17. }, 1000);
  18. });
  19. }
  20. }
  21. });

在actions中就是异步取提交mutations
在项目中用this.$store.dispatch('setNum'); dispatch来调用。
在项目methods中...mapActions({ setNumAlias: 'setNum' }),解构
actions里面,方法的形参可以直接将commit解构出来,这样可以方便后续操作:

  1. actions: {
  2. setNum({ commit }) { // 直接将content结构掉,解构出commit,下面就可以直接调用了
  3. return new Promise(resolve => {
  4. setTimeout(() => {
  5. commit('XXXX'); // 直接调用
  6. resolve();
  7. }, 1000);
  8. });
  9. },
  10. },

nextTick

nexttick是一个微任务回调 只有watcher更新才会
在下次 DOM 更新循环结束之后执行延迟回调。

原理:

定义一个callbacks变量,将通过参数cb传入的函数用另一个函数进行封装,这个过程中会执行传入的函数以及处理异常或者失败的场景,之后添加callbacks中。调用timeFunc函数,会遍历callbacks中的函数并依次执行,因为timeFunc函数是异步函数,且通过pending变量布尔值转换来控制一次事件循环中只调用一次

什么叫下次 DOM 更新循环结束?

个人理解:下一轮eventloop执行完毕,GUI渲染完毕
Vue的dom更新是异步的(v-for 一百次不异步就卡死)

Watch

image.png
Watcher内的方法
当触发监听属性时,旧的值在this.value上,新的值通过this.get()重新求值,再将新值赋值给this.value

  1. getAndInvoke(cb) {
  2. const value = this.get() // 重新求值
  3. if(value !== this.value || isObject(value) || this.deep) {
  4. const oldValue = this.value // 缓存之前的值
  5. this.value = value // 新值
  6. if(this.user) { // 如果是user-watcher
  7. cb.call(this.vm, value, oldValue) // 在回调内传入新值和旧值
  8. }
  9. }
  10. }

watch总结:

为需要观察的数据创建并收集user-watcher,当数据改变时通知到user-watcher将新值和旧值传递给用户自己定义的回调函数。定义watch时会被使用到的三个参数:sync、immediate、deep。它们的实现原理是:sync是不将watcher加入到nextTick队列而同步的更新、immediate是立即以得到的值执行一次回调函数、deep是递归的对它的子值进行依赖收集。

Computed

image.png

部分实现过程

  1. //将computed计算的值变成响应式数据
  2. function defineComputed(target, key) {
  3. ...
  4. Object.defineProperty(target, key, {
  5. enumerable: true,
  6. configurable: true,
  7. get: createComputedGetter(key),
  8. set: noop
  9. })
  10. }
  11. //响应式数据的get
  12. function createComputedGetter (key) { // 高阶函数
  13. return function () { // 返回函数
  14. const watcher = this._computedWatchers && this._computedWatchers[key]
  15. // 原来this还可以这样用,得到key对应的computed-watcher
  16. if (watcher) {
  17. if (watcher.dirty) { // 在实例化watcher时为true,表示需要计算
  18. watcher.evaluate() // 进行计算属性的求值
  19. }
  20. if (Dep.target) { // 当前的watcher,这里是页面渲染触发的这个方法,所以为render-watcher
  21. watcher.depend() // 收集当前watcher
  22. }
  23. return watcher.value // 返回求到的值或之前缓存的值
  24. }
  25. }
  26. }
  27. //---------------------------
  28. class Watcher {
  29. ...
  30. evaluate () {
  31. this.value = this.get() // 计算属性求值
  32. this.dirty = false // 表示计算属性已经计算,不需要再计算
  33. }
  34. depend () {
  35. let i = this.deps.length // deps内是计算属性内能访问到的响应式数据的dep的数组集合
  36. while (i--) {
  37. this.deps[i].depend() // 让每个dep收集当前的render-watcher
  38. }
  39. }
  40. }

conputed总结:

为什么计算属性有缓存功能?因为当计算属性经过计算后,内部的标志位会表明已经计算过了,再次访问时会直接读取计算后的值;
为什么计算属性内的响应式数据发生变更后,计算属性会重新计算?
因为内部的响应式数据会收集computed-watcher,变更后通知计算属性要进行计算,也会通知页面重新渲染,渲染时会读取到重新计算后的值。