Vue-Vuex的状态管理-nexttick

365 阅读5分钟

在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理

在Vue开发中,我们使用组件化的开发方式;而在组件中我们定义data或者在setup中返回使用的数据, 这些数据我们称之为state

在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View

在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions,解决异步操作。

image.png

image.png

在组件中dispatch调用store中的Action,在storeAction通过context.commit去调用Mutation,在Mutation里调用state.xxx来获取存储在state里的值,对其改变状态。

创建Store

每一个Vuex应用的核心就是store(仓库): store本质上是一个容器,它包含着你的应用中大部分的状态(state);

Vuex和单纯的全局对象有什么区别呢?

  • 第一:Vuex的状态存储是响应式的: 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;

  • 第二:你不能直接改变store中的状态: 改变store中的状态的唯一途径就显示提交 (commit) mutation

methods: { 
    increment() { 
        this.$store.commit('increment') 
    }, 
    decrement() { 
        this.$store.commit('decrement') 
    }
 }

这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;

使用步骤:

  • 创建Store对象;

  • 在app中通过插件安装;

组件中使用store

<template>
  <div>
  //模板中拿到数据 $store.state.xxxx
    <h1>{{ $store.state.counter }}</h1>
    <button @click="increment">++</button>
    <button @click="decrement">--</button>
  </div>
</template>

<script>
export default {
//mutation里的方法
  methods: {
    increment() {
    //调用mutation中的方法对数据进行修改
      this.$store.commit('increment')
    },
    decrement() {
      this.$store.commit('decrement')
    }
  }
}
</script>
//composition API
import { useStore } from 'vuex' 
setup() {
    const store = useStore()  // 使用useStore方法 
    console.log(store);
    return{
        store
    }
 };

image.png

单一状态树

  • Vuex 使用单一状态树:用一个对象就包含了全部的应用层级的状态;采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源;这也意味着,每个应用将仅仅包含一个 store 实例;单一状态树和模块化并不冲突,后面我们会讲到module的概念;

  • 单一状态树的优势:

如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难; 所以Vuex也使用了单一状态树来管理应用层级的全部状态;

单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便 的管理和维护;

mapState()

返回的是一个对象

computed: {
    //数组写法 ... 展开运算符
    ...mapState(['counter', 'age', 'name'])
    ...mapState({
        //返回Scounter
        Scounter:state => state.counter
    })
  },

setup中使用mapState()

 setup() {
    //单个取数据
    const sCounter = computed(() => store.state.counter)
    return {
      sCounter
    }
  }

默认情况下,Vuex并没有提供非常方便的使用mapState的方式,所以进行了一个函数的封装(来自于王红元老师的封装方法)

getter()

某些属性我们可能需要经过变化后来使用,使用getter()

//获取
    <h2>总价值:{{ $store.getters.totalPice }}</h2>

getters: {
    totalPice(state) {
      let totalPice = 0
      //遍历拿到里面的所有item
      for (const book of state.books) {
        totalPice += book.pice
      }
      return totalPice
    }
  },

mapGetters()

computed: {
    //数组写法 ... 展开运算符
    ...mapGetters(['counter', 'age', 'name'])
  },
 setup() {
    //单个取数据
    const stotalPice = computed(() => store.getters.totalPice)
    
    return {
      stotalPice
    }
  }

Mutation()

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

 mutations: {
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    }
  },

接收多个参数


 methods: {
    increment() {
      this.$store.commit('increment', { name: 'why', age: 18 })
    },
 }

  mutations: {
    //传多个参数 对象类型 payload
    increment(state, payload) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    }
  },

mapMutations()

我们也可以借助于辅助函数,帮助我们快速映射到对应的方法中:

methods: {
    //数组方式
    ...mapMutations(['increment', 'decrement'])
 }
import {mapMutations} from 'vuex

 setup() {
    const mutation = mapMutations(['increment', 'decrement'])
    
    return {
        ...mutation
    }
  }

mutation重要原则

开发者工具会记录mutation

  • 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;

  • 但是在mutation中执行异步操作,就无法追踪到数据的变化;

  • 所以Vuex的重要原则中要求 mutation必须是同步函数

actions()

完成异步操作,再通过mutation进行状态的修改

Action提交的是mutation,而不是直接变更状态;

Action可以包含任意异步操作;

context是一个和store实例均有相同方法和属性的context对象;

我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state context.getters 来获取 stategetters

在组件中利用dispatch分发store中的操作

methods: {
//组件中通过dispatch分发操作
    increment() {
    //携带参数
      this.$store.dispatch('incrementAction',{name:'why'})
    }
  }
//store中,payload接收参数,context 上下文 获取mutation里面的同步操作
 actions: {
    incrementAction(context,payload) {
      context.commit('increment')
    }
  },
  mutations: {
    increment(state) {
      state.counter++
    }
  },

mapAction()

methods: {
//数组写法
    ...mapActions(['increment','decrement'])
 }
    <button @click="incrementAction">++</button>

//setup
import { mapActions } from 'vuex'
 setup() {
    const action = mapActions(['incrementAction'])
    return { ...action }
  }

module的基本使用

Vuex 允许我们将 store 分割成模块(module); 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;

//创建home模块
const homeModule = {
  state() {
    return {
      homeCounter: 0
    }
  },
  getters: {},
  mutations: {},
  actions: {}
}
//导出
export default homeModule

//主仓库
import { createStore } from 'vuex'
//导入仓库模块
import homeModule from './home'
export default createStore({
  state() {
    return {
      rootCounter: 0
    }
  },
  getters: {},
  mutations: {},
  actions: {},
  //模块化
  modules: {
    home: homeModule
  }
})

//组件中使用home模块中的数据
<h2>{{ $store.state.home.homeCounter }}</h2>

module的命名空间

默认情况下,模块内部的actionmutation仍然是注册在全局的命名空间中的:这样使得多个模块能够对同一个actionmutation 作出响应;Getter 同样也默认注册在全局命名空间;

如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true的方式使其成为带命名空间的模块:

当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;

const homeModule = {
  //开启命名空间
  namespaced: true,
}

访问

//取state里的值,加上模块名称
    <h2>{{ $store.state.home.homeCounter }}</h2>
  //取getter数据,加上模块名称
    <h2>Home:{{ $store.getters['home/doubleHomeCounter'] }}</h2>

export default {
  methods: {
    homeAdd() {
    //mutation里面的方法,'模块名称/里面的方法'
      this.$store.commit('home/increment')
    }
  }
}

module修改或派发根组件

image.png 模块的辅助函数

<script>
import { mapMutations, mapState ,mapMutations , mapActions } from 'vuex'

export default {
  computed: {
    //模块名称:[数据]
    ...mapState('home', ['homeCounter'])
    ...mapGetter('home', ['homeCounter'])
    
  },
  methods: {
    ...mapMutations('home', ['increment'])
    ...mapActions('home', ['increment'])
    
  }
}
//composition API
setup() {
    const state = mapState(['homeCounter'])
    const getter = mapGetter('home', ['homeCounter'])
    const mutations = mapMutations(['increment'])
    const actions  =   mapActions('home', ['increment'])

    return {
      ...state,
      ...getter,
      ...mutations,
      ...actions
    }
  }
</script>

nexttick()

官方解释:将回调推迟到下一个 DOM 更新周期之后执行。

当数据更新了,dom渲染完成后,自动执行该函数,

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个tick才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

nexttick将回调函数推入微任务中,排在所有的微任务后面,当前面的微任务执行完毕,执行该条微任务

 const addRef = () => {
      message.value += 'hhhhhhh'
      nextTick(() => {
        console.log(titleRef)
      })
    }

nextTick和$nextTick区别

1.nextTick(callback):当数据发生变化,更新后执行回调。在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

2.$nextTick(callback):当DOM 发生变化,更新后执行的回调。将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。

  • nextTick(callback) 是全局的方法;
  • $nextTick(callback) 是回调的 this 自动绑定到调用它的组件实例上;