vue3-Vuex v4.x

一、Vuex基本使用

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

不能直接改变store中的状态,改变store中的状态的唯一途径就显示提交 (commit) mutation;这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;

Vuex由五大部分组成:state,getters,mutations,modules,actions

1.使用步骤

  • 创建Store对象
import { createStore } from "vuex"

const store = createStore({
  state() {
    return {
      counter: 0,
      name: 'tjx',
      age: 18,
      height: 180
    }
  },
  mutations: {
    increment(state) {
      state.counter++
    }
  },
  getters: {
  },
  actions: {
  }
});


export default store
  • 在app中通过插件安装
import { createApp } from 'vue'
import App from './App.vue'
import store from "./store"

createApp(App).use(store).mount('#app')

2.组件中使用store

  • 在模板中使用
<h2>Home: {{$store.state.counter}}</h2>
  • 在option API中使用,如computed
computed: {
	sCounter() {
		return this.$store.state.counter
	}
}
  • 在setup中使用
<script>
import {useStore,mapState} from "vuex"
import {computed} from 'vue'
  export default {
    setup() {
      const store = useStore()
      const sCounter = computed(() => store.state.counter)
      
      return {
        sCounter
      }
    }
  }
</script>

二、state

1.mapState辅助函数

如果我们有很多个状态都需要获取话,可以使用mapState的辅助函数:

  • mapState的方式一:对象类型
  • mapState的方式二:数组类型
  • 也可以使用展开运算符和来原有的computed混合在一起;

在optionAPI中使用辅助函数:

<template>
  <div>
    <h2>Home: {{$store.state.counter}}</h2>
    <h2>Home: {{sCounter}}</h2>
    <h2>Home: {{sNamge}}</h2>
    <h2>Home: {{age}}</h2>
    <h2>Home: {{height}}</h2>
  </div>
</template>

<script>
import {mapState} from "vuex"
  export default {
    computed: {
      // 辅助函数mapState返回的是一个对象
      // 1.数组写法
      ...mapState(["counter", "name", "age", "height"])
      // 2.对象写法:可以重命名
      ...mapState({
        sCounter: state => state.counter,
        sNamge: state => state.name
      })
    }
  }
</script>

<style scoped>

</style>

2.在setup中使用mapState

<template>
  <div>
    <h2>Home: {{sCounter}}</h2>
    <hr>
    <h2>Home: {{counter}}</h2>
    <h2>Home: {{name}}</h2>
    <h2>Home: {{age}}</h2>
    <h2>Home: {{height}}</h2>
  </div>
</template>

<script>
import {useStore,mapState} from "vuex"
import {computed} from 'vue'
  export default {
    setup() {
      const store = useStore()
      const sCounter = computed(() => store.state.counter)

      // 因为mapState返回的是一个对象,如果在return中直接通过 ...storeStateFns 返回的是一个个的函数对象,不能直接使用,所以需要进行进一步的封装
      const storeStateFns = mapState(['counter', 'name', 'age', 'height'])
      const storeState = {}
      // Object.keys用于获取 storeStateFns 所有的key,再通过forEach遍历给 storeStateFns每一个key对应的返回的函数绑定store,
      Object.keys(storeStateFns).forEach(fnKey => {
        const fn = storeStateFns[fnKey].bind({$store: store});
        // 再通过computed获取每一个key对应的ref值保存到新的对象 storeState 中,此时 storeState 中都是computed转化后的值
        storeState[fnKey] = computed(fn)
      })
        
      return {
        sCounter,
        // 再通过拓展运算符的方式
        ...storeState
      }
    }
  }
</script>

<style scoped>

</style>

**Vuex并没有提供非常方便的使用mapState的方式,这里我们进行了一个函数的封装:**再创建一个hooks文件夹用于存放封装的函数,在其中创建一个useState.js的文件

import { mapState, useStore } from "vuex";
import {computed} from 'vue'

export function useState(mapper) {
  // 获取store对象
  const store = useStore()

  // 获取到对应的对象的function:{name: function, age:function,...}
  const storeStateFns = mapState(mapper)

  // 对数据进行转换
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({ $store: store });
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

封装后使用:

<script>
// 引入useState
import {useState} from '../hooks/useState'
  export default {
    setup() {
      const storeState = useState(['counter', 'name', 'age', 'height'])
      
      return {
        ...storeState
      }
    }
  }
</script>

三、getters

1.基本使用

  • 定义:
getters: {
  totalPrice(state, getters) {
    let totalPrice = 0;
    for (const book of state.books) {
      totalPrice += book.count * book.price
    }
    return totalPrice * getters.currentDiscount
  },
}
  • 使用:
<div>
    <h2>{{ $store.getters.totalPrice}} </h2>
</div>

2.getters第二个参数

第二个参数用于获取getters中定义的其他函数。

getters: {
    totalPrice(state, getters) {
        let totalPrice = 0;
        for (const book of state.books) {
            totalPrice += book.count * book.price;
        }
        return totalPrice + ',' + getters.myName;
    },
    myName(state) {
        return state.name
    }
}

3.getters的返回函数

getters中的函数本身,可以返回一个函数,那么在使用的地方相当于可以调用这个函数.

  • 定义:
// 为了getter接受参数,可以return一个函数,此时传递参数
    totalPriceCountGreaterN(state, getters) {
      return function (n) {
        let totalPrice = 0;
        for (const book of state.books) {
          if (book.count <= n) continue
          totalPrice += book.count * book.price
        }
        return totalPrice * getters.currentDiscount
      }
    }
  • 使用:这样相当于就可以传递参数了
<h2>总价值:{{ $store.getters.totalPriceCountGreaterN(2) }}</h2>

4.mapGetters辅助函数

  • 在options API中使用:
computed: {
    // 数组类型
    ...mapGetters(["nameInfo", "ageInfo", "heightInfo"]),
    // 对象类型
    ...mapGetters({
      sNameInfo: "nameInfo",
      sAgeInfo: "ageInfo",
      sHeight: 'heightInfo'
    })
  },
  • 在setup中使用:也是可以先进行一个封装,再只用

封装:

import { mapGetters, useStore } from "vuex";
import {computed} from 'vue'

export function useGetters(mapper) {
  // 获取store对象
  const store = useStore()

  // 获取到对应的对象的function:{name: function, age:function,...}
  const storeStateFns = mapGetters(mapper)

  // 对数据进行转换
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({ $store: store });
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

使用:

<template>
  <div>
    <h2>总价值:{{ $store.getters.totalPrice }}</h2>
    <h2>总价值:{{ $store.getters.totalPriceCountGreaterN(2) }}</h2>
    <hr />
    <h2>{{ $store.getters.nameInfo }}</h2>
    <h2>{{ ageInfo }}</h2>
    <h2>{{ heightInfo }}</h2>
    <hr />
    <h2>{{ sNameInfo }}</h2>
    <h2>{{ sAgeInfo }}</h2>
    <h2>{{ sHeightInfo }}</h2>
    <hr>
    <h2>{{ sNameInfoa }}</h2>
  </div>
</template>

<script>
import {useGetters} from '../hooks/useGetters'
export default {
  setup() {
    // const store = useStore();
    // const sNameInfoa = computed(() => store.getters.nameInfo)
    const storeGetters = useGetters(["nameInfo", "ageInfo", "heightInfo"])

    return {
      ...storeGetters
    }
  }
};
</script>

<style scoped>
</style>

四、mutations

只能mutations中进行对state状态的修改

1.基本使用

  • 定义:
mutations: {
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    },
    [INCREMENT_N](state, payload) {
      state.counter += payload.n
    }
  },
  • 提交:
<template>
  <div>
    <h2>当前计数:{{ $store.state.counter }}</h2>
    <button @click="$store.commit('increment')">+1</button>
    <button @click="$store.commit('decrement')">-1</button>
    <hr>
  </div>
</template>

2.mutations携带参数

第二个参数payload为携带的参数。

  • 定义:
mutations: {
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    },
    [INCREMENT_N](state, payload) {
      state.counter += payload.n
    },
    addBannerData(state, payload) {
      state.banners = payload
    }
  }
  • 提交:两种提交风格
<template>
  <div>
    <h2>当前计数:{{ $store.state.counter }}</h2>
    <button @click="$store.commit('increment')">+1</button>
    <button @click="$store.commit('decrement')">-1</button>
    <button @click="addTen">+10</button>
    <hr>
  </div>
</template>

<script>
import {INCREMENT_N} from '../store/mutation-types'
export default {
  methods: {
    addTen() {
      // this.$store.commit('incrementN', 10)
      // this.$store.commit('incrementN', {n: 10, name: 'yn'})
      // 另一种提交风格
      this.$store.commit({
        type: INCREMENT_N,
        n: 10,
        name: 'yn',
        age: 18
      })
    }
  }
};
</script>

<style scoped>
</style>

3.mutations常量类型

  • 定义常量:mutation-type.js

export const ADD_NUMBER = 'add_number'

  • 定义mutations
[INCREMENT_N](state, payload) {
   state.counter += payload.n
}
  • 提交
$store.commit({
    type: ADD_NUMBER,
    count: 100
})

4.mapMutations辅助函数

  • 在option API中使用:
methods: {
    ...mapMutations(['increment', 'decrement', INCREMENT_N]),
    addTen() {
      this.increment_n({n:10})
    }
}
  • 在setup中使用:由于mutation本身就是函数,所以可以直接解构
setup() {
    const stateMutations = mapMutations(['increment', 'decrement', INCREMENT_N])

    return {
      ...stateMutations
    }
}

5.mutation重要原则

  • 一条重要的原则就是要记住 mutation 必须是同步函数
    • 这是因为devtool工具会记录mutation的日记;
    • 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;
    • 但是在mutation中执行异步操作,就无法追踪到数据的变化
    • 所以Vuex的重要原则中要求 mutation必须是同步函数;

五、actions

Action类似于mutation,不同在于: Action提交的是mutation,而不是直接变更状态; Action可以包含任意异步操作;

1.基本使用

  • 参数context: context是一个和store实例均有相同方法和属性的context对象; 可以从其中获取到commit方法来提交一个mutation,或者通过 context.statecontext.getters 来 获取 state 和 getters;
incrementAction(context) {
   setTimeout(() => {
     context.commit("increment")
   }, 1000)
}
  • 进行action的分发使用的是 store 上的dispatch函数

  • 携带参数

increment() {
      // this.$store.dispatch('incrementAction', {count: 100})
   // 另外一种提交方式
   this.$store.dispatch({
     type: 'incrementAction',
     count: 100
   })
},

2.mapActions辅助函数

<template>
  <div>
    <h2>当前计数:{{ $store.state.counter }}</h2>
    <button @click="incrementAction">+1</button>
    <button @click="decrementAction">-1</button>
    <hr>
  </div>
</template>

<script>
import {mapActions} from 'vuex'
export default {
  methods: {
    // 数组写法
    ...mapActions(['incrementAction', 'decrementAction']),
    // 对象写法
    ...mapActions({
      add: 'incrementAction',
      sub: 'decrementAction'
    })
  },
  setup() {
    const actions = mapActions(['incrementAction', 'decrementAction'])
    const actions2 = mapActions({
      add: 'incrementAction',
      sub: 'decrementAction'
    })
    return {
      ...actions,
      ...actions2
    }
  }
};
</script>

<style scoped>
</style>

3.actions异步操作

Action 通常是异步的,那么如何知道 action 什么时候结束呢?我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;

actions: {
    // 异步请求
    getHomeMultidata(context) {
      return new Promise((resolve, reject) => {
        axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
          context.commit("addBannerData", res.data.data.banner.list)
          resolve(res)
        }).catch(err => {
          reject(err)
        })
      })
    }
}
setup() {
    const store = useStore()

    onMounted(() => {
      const promise = store.dispatch("getHomeMultidata")
      promise.then(res => {
        console.log(res);
      }).catch(err => {
        console.log(err);
      })

    })
    return {
    }
  }

六、modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store对象就有可能变得相当臃肿;为了解决以上问题,Vuex 允许我们将 store 分割成模块(module);每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块

1.基本使用

一般会在store文件夹下再创建一个modules文件夹用于存放子模块,这里我们在modules文件夹下创建home.js文件。

store中index.js:

import { createStore } from "vuex"
import home from "./modules/home";
import user from "./modules/user";

const store = createStore({
  state() {
    return {
      rootCounter: 0
    }
  },
  mutations: {
    increment(state) {
      state.rootCounter++
    }
  },
  modules: {
    home,
    user
  }
});

export default store

home.js:

const homeModule = {
  namespaced: true,
  state() {
    return {
      homeCounter: 100
    }
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  }
}

export default homeModule

使用:

<template>
  <div>
    <h2>{{ $store.state.rootCounter }}</h2>
    <h2>{{ $store.state.home.homeCounter }}</h2>
    <h2>{{ $store.state.user.userCounter }}</h2>
    <hr>
  </div>
</template>

2.modules的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数(state)是模块的局部状态对象,不是根模块中的状态对象。所以他们会有额外的参数rootState, rootGetters用于获取根模块中的状态。

3.modules的命名空间

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

如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true 的方式使其成为带命名空间的模块:当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;

home.js:

const homeModule = {
  namespaced: true,
  state() {
    return {
      homeCounter: 100
    }
  },
  getters: {
    doubleHomeCounter(state) {
      // doubleHomeCounter(state, getters, rootState, rootGetters) {
      return state.homeCounter * 2
    }
  },
  mutations: {
      increment(state) {
        state.homeCounter++
      }
  },
  actions: {
    incrementAction({context}) {
      // incrementAction({context, dispatch, state, rootState, getters, rootGetters}) {
      context.commit("increment")
    }
  }
}

export default homeModule

使用:

<template>
  <div>
    <h2>{{ $store.state.rootCounter }}</h2>
    <h2>{{ $store.state.home.homeCounter }}</h2>
    <h2>{{ $store.state.user.userCounter }}</h2>
    <hr>
    <h2>{{ $store.getters["home/doubleHomeCounter"] }}</h2>

    <button @click="homeIncrement">home+1</button>
    <button @click="homeIncrementAction">home+1</button>
    <hr>
  </div>
</template>

<script>
export default {
  methods: {
    homeIncrement() {
      this.$store.commit("home/increment")
    },
    homeIncrementAction() {
      this.$store.dispatch("home/incrementAction")
    }
  }
};
</script>

<style scoped>
</style>

4.modules修改或派发根组件

如果我们希望在action中修改root中的state,那么有如下的方式:

actions: {
    incrementAction({context}) {
      // incrementAction({context, dispatch, state, rootState, getters, rootGetters}) {
      context.commit("increment")
      // context.commit("increment", null, {root: true}) // 表示此次的提交是对根进行的提交
      // dispatch("increment", null, {root: true})
    }
}

5.modules的辅助函数

如果辅助函数有三种使用方法:

  • 方式一:通过完整的模块空间名称来查找;
  • 方式二:第一个参数传入模块空间名称,后面写上要使用的属性;
  • 方式三:通过 createNamespacedHelpers 生成一个模块的辅助函数;
<template>
  <div>
    <h2>{{ homeCounter }}</h2>
    <h2>{{ doubleHomeCounter }}</h2>
    <button @click="increment">home+1</button>
    <button @click="incrementAction">home+1</button>
    <hr>
  </div>
</template>

<script>
// import {mapState, mapGetters, mapMutations, mapActions, createNamespacedHelpers} from 'vuex'
import {createNamespacedHelpers} from 'vuex'
// const hyMapState = createNamespacedHelpers('home').mapState
const {mapState, mapGetters, mapMutations, mapActions} = createNamespacedHelpers('home')
export default {
  computed: {
    // 1.写法一:
    // ...mapState({
    //   homeCounter: state => state.home.homeCounter
    // }),
    // ...mapGetters({
    //   doubleHomeCounter: 'home/doubleHomeCounter'
    // }),

    // 写法二:
    // ...mapState('home', ["homeCounter"]),
    // ...mapGetters('home', ['doubleHomeCounter'])

    // 写法三:
    ...mapState(["homeCounter"]),
    ...mapGetters(['doubleHomeCounter'])
  },
  methods: {
    // 写法一:
    // ...mapMutations({
    //   increment: 'home/increment'
    // }),
    // ...mapActions({
    //   incrementAction: 'home/incrementAction'
    // })

    // 写法二:
    // ...mapMutations('home', ['increment']),
    // ...mapActions('home', ['incrementAction'])

    // 写法三:
    ...mapMutations(['increment']),
    ...mapActions(['incrementAction'])
  },
  setup() {
    

    return {
      // 02:08:53
    }
  }
};
</script>

<style scoped>
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值