vuex状态管理

149 阅读3分钟

vue最核心的功能

  • 数据驱动和组件化

  • 使用组件化开发可以提高开发效率 带来更好的维护性

状态管理

  • state,驱动应用的数据源;

  • view,以声明方式将 state 映射到视图;

  • actions,响应在 view 上的用户输入导致的状态变化。

组件传值的三种方法

父传子

<blog-post title="My journey with Vue"></blog-post>
Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

子传父

在子组件中使用 $emit 发布一个自定义事件:

<button v-on:click="$emit('enlargeText', 0.1)">
  Enlarge text
</button>

在使用这个组件的时候,使用 v-on 监听这个自定义事件

使用事件抛出一个值

<blog-post v-on:enlargeText="hFontSize += $event"></blog-post>

非父子传值(event Bus)

我们可以使用一个非常简单的 Event Bus 来解决这个问题:

eventbus.js :

export default new Vue()

然后在需要通信的两端: 使用 $on 订阅:

// 没有参数
bus.$on('自定义事件名称', () => {
  // 执行操作
})
// 有参数
bus.$on('自定义事件名称', data => {
  // 执行操作
})

使用 $emit 发布:

// 没有自定义传参
bus.$emit('自定义事件名称');
// 有自定义传参
bus.$emit('自定义事件名称', 数据);

父组件直接访问子组件:通过ref获取子组件(了解即可 慎用会导致数据混乱)

ref 有两个作用:

  • 如果你把它作用到普通 HTML 标签上,则获取到的是 DOM
  • 如果你把它作用到组件标签上,则获取到的是组件实例

创建 base-input 组件

<template>
  <input ref="input">
</template>
<script>
  export default {
    methods: {
      // 用来从父级组件聚焦输入框
      focus: function () {
        this.$refs.input.focus()
      }
    }
  }
</script>

在使用子组件的时候,添加 ref 属性:

<base-input ref="usernameInput"></base-input>

然后在父组件等渲染完毕后使用 $refs 访问:

mounted () {
  this.$refs.usernameInput.focus()
}

refs只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组 件的“逃生舱”——你应该避免在模板或计算属性中访问 refs 。

vuex回顾

什么是 Vuex

Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件 的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调 试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调 试功能。

  • Vuex 是专门为 Vue.js 设计的状态管理库
  • 它采用集中式的方式存储需要共享的数据
  • 从使用角度,它就是一个 JavaScript 库
  • 它的作用是进行状态管理,解决复杂组件通信,数据共享

vuex的核心概念

  • store 一个容器(vuex)
    • state 单一状态数(响应式)
    • getter 相当于计算属性
    • mutation 同步
    • action 异步
    • module 模块

vuex的使用

state

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  //重点
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  //类似于vue的计算属性
  getters: {},
  mutations: {},
  actions: {},
})

about.vue

<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <!-- 第一种写法 -->
    <!-- count:{{ $store.state.count }} <br>
    msg: {{ $store.state.msg }} -->

    <!-- 第二种写法 -->
    <!-- count:{{ count }} <br>
    msg: {{ msg }} -->
    <!-- 第三种写法 避免有重复 -->
    count:{{ num }} <br>
    msg: {{ message }}

    
  </div>
</template>
<script>
// 使用计算属性简化模板代码
import { mapState} from 'vuex'
export default {
  computed: {
    // 第二种写法 (接收一个数组作为参数[数组里就是我们要映射的属性'count', 'msg'])
    // count等于state.count
    // ...mapState(['count', 'msg'])


    
    // 第三种写法 避免重复 (进行重命名)
    ...mapState({ num: 'count', message: 'msg' }),
    
  },
  
}
</script>

Getter(类似于vue的计算属性)

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  //重点
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  //类似于vue的计算属性
  getters: {
	reverseMsg (state) {
      return state.msg.split('').reverse().join('')
    }
  },
  mutations: {

  },
  actions: {

  },

})

about.vue

<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
   
    count:{{ num }} <br>
    msg: {{ message }}

    
  </div>
</template>
<script>
import { mapState, mapGetters, } from 'vuex'

export default {
  computed: {
  	// 重点开始 使用计算属性
      ...mapGetters(['reverseMsg']),
    //重点结束
    
	  ...mapState({ num: 'count', message: 'msg' }),

    
  },
  
}
</script>

store.js

Mutation(不要用Mutation执行异步操作 commit是属于同步)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  // 重点
  mutations: {
    // 将state.count+3
    increate (state, payload) {
      state.count += payload
    }
  },
  
  
})

about.vue

<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count:{{ num }} <br>
    msg: {{ message }}
    <h2>Mutation</h2>
    <button @click="increate(3)">Mutation</button>
  </div>
</template>
<script>
import { mapState, mapMutations, } from 'vuex'
export default {
  computed: {
    ...mapState({ num: 'count', message: 'msg' }),
   
  },
  methods: {
    // 触发increate方法
    ...mapMutations(['increate']),
  }
}
</script>

store.js

Action(dispatch属于异步)

export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  //重点 (异步)
  actions: {
    increateAsync (context, payload) {
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)
    }
  },
  
})

about.vue

<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count:{{ num }} <br>
    msg: {{ message }}

    <h2>Action</h2>
    <!-- <button @click="$store.dispatch('increateAsync', 5)">Action</button> -->
    <button @click="increateAsync(6)">Action</button>
  </div>
</template>
<script>
import { mapState,  mapActions } from 'vuex'
export default {
  computed: {
    ...mapState({ num: 'count', message: 'msg' }),
  },
  methods: {
    ...mapActions(['increateAsync']),
  }
}
</script>

Module 将单一状态数拆分为模块

store-》modules-》cart.js

const state = {}
const getters = {}
const mutations = {}
const actions = {}

export default {
  namespaced: true, // 开启命名空间(具有复用性)
  state,
  getters,
  mutations,
  actions
}

store-》modules-》cart.js

const state = {
  products: [
    { id: 1, title: 'iPhone 11', price: 8000 },
    { id: 2, title: 'iPhone 12', price: 10000 }
  ]
}
const getters = {}
const mutations = {
  setProducts (state, payload) {
    state.products = payload
  }
}
const actions = {}

export default {
  namespaced: true,// 开启命名空间
  state,
  getters,
  mutations,
  actions
}

store-index.js

import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  // 重点
  modules: {
    products,
    cart
  }
})

about.vue

<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <h2>Module</h2>
    products: {{ products }} <br>
    <button @click="setProducts([])">Mutation</button>
  </div>
</template>
<script>
import { mapState,  mapMutations  } from 'vuex'
export default {
  computed: {
      // 开启命名空间 
      // 命名空间名称 (在store->modules的名称)
      // products 拿到的是在store->modules->products.js->state.products
    ...mapState('products', ['products'])
  },
  methods: {
      // products是文件
    ...mapMutations('products', ['setProducts'])
  }
}
</script>

vuex的严格模式

store-》index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  // 重点
  // 严格模式 在开发环境使用 在生产环境 不要启用严格模式 否则会影响性能
  strict: process.env.NODE_ENV !== 'production',
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  getters: { },
  
  actions: {},
  
})

about.vue

<template>
  <div id="app">
    <h2>strict</h2>
    <button @click="$store.state.msg = 'Lagou'">strict</button>
  </div>
</template>

购物车案例拆解(vuex)

模块1 效果图

实现效果(获取红框框数据)

image-20220308181956425.png

在store>创建modules>products.js


import axios from 'axios'
// eslint-disable-next-line no-unused-vars
const state = {
  products: [] // 记录所有商品
}
const sgetters = {}
const mutations = {
  setProducts (state, payload) {
    console.log(payload)
    // payload 拿到当前id
    state.products = payload
  }
}

const actions = {
  // 异步获取数据
  async getProducts ({ commit }) {
    // eslint-disable-next-line no-unused-vars
    const { data } = await axios({
      method: 'GET',
      url: 'http://127.0.0.1:3000/products'
    })
    commit('setProducts', data)
  }
}
export default {
  namespaced: true, // 开启命名空间(具有复用性)
  state,
  sgetters,
  mutations,
  actions
}

在store>index.js引入

import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'

Vue.use(Vuex)

export default new Vuex.Store({
  
  modules: {
    products
  },
})

模板

<template>
  <div>
    <el-breadcrumb separator="/">
      <el-breadcrumb-item><a href="#/">首页</a></el-breadcrumb-item>
      <el-breadcrumb-item><a href="#/">商品列表</a></el-breadcrumb-item>
    </el-breadcrumb>
    <!-- products 重点 -->
    <el-table
      :data="products"
      style="width: 100%">
      <el-table-column
        prop="title"
        label="商品">
      </el-table-column>
      <el-table-column
        prop="price"
        label="价格">
      </el-table-column>
      <el-table-column
        prop="address"
        label="操作">
        <template>
          <el-button>加入购物车</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'
export default {
  name: 'ProductList',
  computed: {
    // 拿到数据(红框框的数据)
    ...mapState('products', ['products'])
  },
  methods: {
    // 异步请求 渲染到页面上
    ...mapActions('products', ['getProducts']) // (理解为加载不是点击)
  },
  created () {
    // 调用getProducts方法
    this.getProducts()
  }
}
</script>

模块二

点击加入购物车

image-20220308202348195.png

在store>创建modules>cart.js

// eslint-disable-next-line no-unused-vars
const state = {
  cartProducts: [] // 记录购物车数据
}
const getters = {

}
// mutations所有方法都是更改状态的
const mutations = {
  addToCart (state, product) {
    // cartProducts中没有该商品 把商品添加到数组中,并增加count,isChecked,totalPrice
    // cartProducts 有商品,让商品数量加1,选中,计算小计
    const prod = state.cartProducts.find(item => item.id === product.id)
    // 如果数组有该商品
    if (prod) {
      prod.count++
      prod.isChecked = true
      // eslint-disable-next-line no-undef
      prod.totalPrice = prod + count * prod.price
    } else {
      state.cartProducts.push({
        ...product,
        count: 1,
        isChecked: true,
        totalPrice: product.price
      })
    }
  }
}

const actions = {}
export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

模板

<template>
  <div>
    <el-breadcrumb separator="/">
      <el-breadcrumb-item><a href="#/">首页</a></el-breadcrumb-item>
      <el-breadcrumb-item><a href="#/">商品列表</a></el-breadcrumb-item>
    </el-breadcrumb>
    <el-table
      :data="products"
      style="width: 100%">
      <el-table-column
        prop="title"
        label="商品">
      </el-table-column>
      <el-table-column
        prop="price"
        label="价格">
      </el-table-column>
      <el-table-column
        prop="address"
        label="操作">
        <!-- 重点 -->
        <template v-slot='scope'>
          <el-button @click="addToCart(scope.row)">加入购物车</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import { mapActions, mapState, mapMutations } from 'vuex'
export default {
  name: 'ProductList',
  computed: {
    ...mapState('products', ['products'])
  },
  methods: {
    ...mapActions('products', ['getProducts']), // (理解为加载不是点击)
    // cart(指的是文件名称)
    // addToCart 指的是mutations下面的addToCart方法
    ...mapMutations('cart', ['addToCart']) // 加入购物车(理解点击)
  },
  created () {
    // 调用getProducts方法
    this.getProducts()
  }
}
</script>


模拟veux

新建myvuex->index.js

let _Vue = null
class Store {
  constructor (options) {
    const {
      // eslint-disable-next-line no-unused-vars
      state = {},
      // eslint-disable-next-line no-unused-vars
      getters = {},
      // eslint-disable-next-line no-unused-vars
      mutations = {},
      // eslint-disable-next-line no-unused-vars
      actions = {}
    } = options
    this.state = _Vue.Observable(state)
    this.getters = Object.create(null)
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](state)
      })
    })
    this._mutations = mutations
    this._actions = actions
  }

  conmit (type, payload) {
    this._mutations[type](this.statem, payload)
  }

  dispatch (type, payload) {
    this._actions[type](this, payload)
  }
}
function install (Vue) {
  _Vue = Vue
  _Vue.mixin({
    beforeCreate () {
      //   this 指的是vue的实例
      if (this.$options.store) {
        // 在vue原型上挂载store
        _Vue.prototype.$store = this.$options.store
      }
    }
  })
}
export default {
  Store,
  install
}

store>index.js

import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from '../myvuex/index' //重点

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    
  },
})