Vuex 总结一下

vuex状态管理


简单介绍

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

​ 在不使用 vuex 时,管理状态方式:

  • 在 vue 开发中,我们使用组件化的开发方式。
  • 在组件中我们定义 data 或 setup 中返回使用的数据,这些数据为 state。
  • 在模块 template 中我们可以使用这些数据,模块会最终渲染成 DOM,成为 view。
  • 在模块中产生一些行为事件,可能会修改state,我们称为 actions

快速开始

安装

npm install vuex

创建 store/index.js

import { createStore } from 'vuex';

const store = createStore({
  state: () => ({
    counter: 0
  })
})

export default store

在 main.js 中使用 store

import { createApp } from 'vue'
import App from './App.vue'
// 注册并使用 store
import store from './store/index'

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

在 template 中拿到 状态值

<template>
  <div class="home">
    <h2>Home计数: {{ $store.state.counter }}</h2>
  </div>
</template>

修改 store 中的状态

使用 commit-mutation。在index.js中使用mutations 字段配置 mutations, 然后在vue组件中使用 useStore 使用状态。

// 在 composition API 中
// ./store/index.js
const store = createStore({
  state: () => ({
    counter: 0
  }),
  mutations: {
    increment(state) {
      state.counter++;
    }
  }
})

// Home.vue
import { useStore } from 'vuex';

const store = useStore();
function increment() {
  store.commit('increment');
}

在 OptionApi 中使用


<script>
// 在options API 中 computed中
export default {
  computed: {
    storeCounter() {
      return this.$store.state.counter;
    }
  }
}
</script>

单一状态树

  • VueX 使用单一状态树

    • 用一个对象就包含了全部应用层级的状态
    • 采用的是 SSOT, Single Source of Truth, 单一数据源
  • 一个应用程序仅仅包含一个 store 实例

    • 单一状态树与模块化不冲突。

mapState - state 映射

<template>
  <div class="home">
    <h2>name: {{ $store.state.name }}</h2>
    <h2>age: {{ $store.state.age }}</h2>
    <hr>
    <h2>name: {{ name }}</h2>
    <h2>age: {{ age }}</h2>
    <hr>
    <h2>name: {{ sName }}</h2>
    <h2>age: {{ sAge }}</h2>
  </div>
</template>

optionApi 中的 mapState

方式一:数组类型

// 映射函数
 ...mapState(['name', 'age']),

方式二:对象类型

...mapState({
   sName: state => state.name,
   sAge: state => state.age
})

compositionApi 中的 mapState

import { computed, toRefs } from 'vue';
import { mapState, useState } from 'vuex';

// 正常使用
const { name, age } = mapState();
const store = useStore();
const cName = computed(name.bind({ $store: store }));
const cAge = computed(age.bind({ $store: store }));

// 直接对 store.state 进行解构
const store = useStore();
const { name, age } = toRefs(store.state);

封装 useState.js

// 封装 mapState、useStore()
import { computed } from 'vue';
import { useStore, mapState } from 'vuex'

export default function useState(mapper) {
  const store = useStore();
  const stateFnsObj = mapState(mapper);
  const newState = {};
  Object.keys(stateFnsObj).forEach(key => {
    newState[key] = computed(stateFnsObj[key].bind({ $store: store }));
  })

  return newState;
}

使用 useState.js

// 解构 state
const { name, age } = useState(['name', 'age']);

getters 字段

vuex 给予 state 的计算属性,类似于 computed.起到过滤数据的作用

const store = createStore({
  state: () => ({
    counter: 100,
    name: 'codewhy',
    age: 12,
    users: [
      { id: 111, name: 'why', age: 20 },
      { id: 112, name: 'kobe', age: 30 },
      { id: 113, name: 'james', age: 21 }
    ]
  }),
  // getters 字段
  getters: {
    doubleCounter(state) {
      return state.counter * 2;
    },
    totalAge(state) {
      return state.users.reduce((preValue, item) => {
        return preValue + item.age;
      }, 0);
    },
    message(state, getters) {
      // 在 getter 中拿到别的 getter
      return `name: ${state.name} age: ${state.age} totalAge: ${getters.totalAge}`;
    },
    getFriend(state) {
      return function (id) {
        return state.users.find(item => item.id === id)
      }
    }
  },
  mutations: {
    increment(state) {
      state.counter++;
    }
  }
})

在组件中使用

<template>
  <div class="home">
    <!-- <button @click="incrementAge">+1</button> -->
    <h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2>
    <h2>totalAge: {{ $store.getters.totalAge }}</h2>
    <h2>message: {{ $store.getters.message }}</h2>
    <h2>111的信息: {{ $store.getters.getFriend(112) }}</h2>
  </div>
</template>

mapGetters - getters 映射

optionApi 中的 mapGetters

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters(['doubleCounter', 'totalAge']),
    ...mapGetters(['getFriend'])
  }
}
</script>

compositionApi 中的 mapGetters

<script setup>
import { computed, toRefs } from 'vue';
import { mapGetters, useStore } from 'vuex';

const store = useStore();
// 基本使用,方式一
// const { message: messageFn } = mapGetters(['message'])
// const message = computed(messageFn.bind({ $store: store }));
// const message = computed(() => store.getters.message);
// 解构使用,方式二
const { message } = toRefs(store.getters);
function changeName() {
  store.state.name = 'kobe';
}

</script>

mutations 字段

修改 state 的唯一方法

const store = createStore({
  state: () => ({
    counter: 100,
    name: 'codewhy',
    age: 12,
    users: [
      { id: 111, name: 'why', age: 20 },
      { id: 112, name: 'kobe', age: 30 },
      { id: 113, name: 'james', age: 21 }
    ]
  }),
  // mutations 字段
  mutations: {
    increment(state) {
      state.counter++;
    },
    // mutation 的第二参数
    changeName(state, payload) {
      state.name = payload
    },
    incrementAge(state, payload) {
      state.age++;
    }
  }
})

在 optionApi 中使用,使用 使用 commit

<script>
export default {
  computed: {
  },
  methods: {
    changeName() {
      console.log('修改name');
      // this.$store.state.name = 'hai';
      this.$store.commit('changeName', '王五');
    },
    incrementAge() {
      this.$store.commit('incrementAge');
    }
  }
}
</script>

定义 mutation-type.js 常量类型

export const INCREMENT_AGE = "incrementAge" 

在 store 中引入使用

// mutations 字段  
mutations: {
    increment(state) {
      state.counter++;
    },
    changeName(state, payload) {
      state.name = payload
    },
    // 使用
    [INCREMENT_AGE](state, payload) {
      state.age++;
    }
  }

在组件中也统一使用

<script>
import { INCREMENT_AGE } from '../store/mutations'
export default {
  computed: {
  },
  methods: {
    changeName() {
      console.log('修改name');
      // this.$store.state.name = 'hai';
      this.$store.commit('changeName', '王五');
    },
    // 统一使用
    [INCREMENT_AGE]() {
      this.$store.commit('incrementAge');
    }
  }
}
</script>

optionApi 中的 mapMutations

<script>
import { mapMutations } from 'vuex';
import { INCREMENT_AGE } from '../store/mutations'

export default {
  computed: {
  },
  methods: {
    btnClick() {
      console.log("btnClick")
    },
    ...mapMutations(["increment", "changeName", INCREMENT_AGE])
  }
}
</script>

compositionApi 中的 mapMutations

<script setup>
import { useStore, mapMutations } from 'vuex';
import { INCREMENT_AGE } from '../store/mutations'

const store = useStore();
const mutations = mapMutations(["changeName", INCREMENT_AGE]);
const newMutations = {};
Object.keys(mutations).forEach(key => {
  newMutations[key] = mutations[key].bind({ $store: store });
})
const { incrementAge, changeName } = newMutations;
</script>

注意:mutations 中必须为同步代码。

actions 字段

action 类似于 mutattion, 但 action 提交 mutation, 而不直接改变状态。action 可以包含异步操作。

  • 包含一个 context 参数
    • context 与 store 具有相同的实例属性与方法
    • 从 context 获取一个 commit 方法来提交 mutation,也可以通过 context.state 和 context.getters 来获取 state 和 getters
const store = createStore({
  state: () => ({
    counter: 100,
    name: 'codewhy',
    age: 12,
    users: [
      { id: 111, name: 'why', age: 20 },
      { id: 112, name: 'kobe', age: 30 },
      { id: 113, name: 'james', age: 21 }
    ]
  }),
  mutations: {
    increment(state) {
      state.counter++;
    },
  },
  actions: {
    incrementAction(context) {
      context.commit('increment');
    }
  }
})

在 optionApi 中使用 dispatch函数调用 incrementAction

<script>
export default {
  methods: {
    actionBtnClick() {
      this.$store.dispatch("incrementAction");
    }
  }
}
</script>

optionsApi 中的 mapActions

<script>
import { mapActions } from 'vuex';
export default {
  methods: {
    // actionBtnClick() {
    //   this.$store.dispatch("incrementAction");
    // },
    // changeName() {
    //   this.$store.dispatch("changeNameAction", "aaa");
    // }
    ...mapActions(['incrementAction', 'changeNameAction'])
  }
}
</script>

compositionApi 中的 mapActions

<script setup>
import { useStore, mapActions } from 'vuex'

const store = useStore();
const actions = mapActions(['incrementAction', 'changeNameAction']);
const newActions = {};
Object.keys(actions).forEach(key => {
  newActions[key] = actions[key].bind({ $store: store });
})
const { incrementAction, changeNameAction } = newActions;
    
// 默认做法,派发事件
function increment() {
  store.dispatch('incrementAction');
}
</script>

actions 异步操作

基本步骤在组件中发起请求派发 -> 在actions发起真正的请求 -> 将请求的数据存入 state -> 再在组件 template 中使用 state;

<script setup>
import { useStore } from 'vuex';
// 告诉 vuex 发起请求
const store = useStore();
store.dispatch('fetchHomeMultidataAction')
</script>

actions 中发起请求

const store = createStore({
  state: () => ({
    // 模拟数据
    counter: 100,
    name: 'codewhy',
    age: 12,
    users: [
      { id: 111, name: 'why', age: 20 },
      { id: 112, name: 'kobe', age: 30 },
      { id: 113, name: 'james', age: 21 }
    ],
    // server 数据
    banners: [],
    recommends: []
  }),
  getters: {},
  mutations: {
    changeBanners(state, banners) {
      state.banners = banners;
    },
    changeRecommends(state, recommends) {
      state.recommends = recommends;
    }
  },
  actions: {
    async fetchHomeMultidataAction(context) {
      // fetch("http://123.207.32.32:8000/home/multidata").then(res => {
      //   res.json().then(data => {
      //     console.log(data);
      //   })
      // })

      // fetch("http://123.207.32.32:8000/home/multidata").then(res => {
      //   return res.json();
      // }).then(data => {
      //   console.log(data);
      // })

      // await async
      const res = await fetch("http://123.207.32.32:8000/home/multidata");
      const data = await res.json();
      console.log(data);
      // 存入 state
      context.commit("changeBanners", data.data.banner.list);
      context.commit("changeRecommends", data.data.recommend.list);
    }
  }
})

在 组件模板中使用

<template>
  <div class="home">
    <h2>Home Page</h2>
    <ul>
      <template v-for="item in $store.state.banners" :key="item.acm">
        <li>{{ item.title }}</li>
      </template>
    </ul>
  </div>
</template>

Modules-模块

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

const store = createStore({
    modules: {
        module_A: moduleA
    }
})

module命名空间

  • 默认情况下,模块内部的 actions 和 mutations 仍然是注册在全局命名空间中。
  • 使用 namespace: true; 可以让模块拥有自己命名空间
const counter = {
  namespaced: true,
  state: () => ({
    counter: 99
  }),
  mutations: {
    incrementCount(state) {
      state.counter++;
    }
  },
  getters: {
    doubleCount(state, getters, rootState) {
      return state.counter + rootState.rootCounter;
    }
  },
  actions: {
    incrementCountAction(context) {
      context.commit("incrementCount");
    }
  }
}

在 template 中使用时

<template>
  <div class="home">
    <h2>Home Page</h2>
    <!-- 使用 state 时,需要 state.moduleName.xxx -->
    <h2>Counter模块的counter: {{ $store.state.counter.counter }}</h2>
    <!-- 使用 getters 时,直接 state.xxx -->
    <h2>Counter模块的doubleCounter: {{ $store.getters["counter/doubleCount"] }}</h2>
    <button @click="incrementCount">counter模块+1</button>
  </div>
</template>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hairy377

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值