重学vue(2, 3)及其生态+TypeScript 之 vuex4.x(上)

简介: 重学vue(2, 3)及其生态+TypeScript 之 vuex4.x

什么是状态管理


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


在vue项目中我们是如何管理自己的状态呢?


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


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


  1. 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions。


复杂的状态管理


JavaScript开发的应用程序,已经变得越来越复杂了。JavaScript需要管理的状态越来越多,越来越复杂。这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等。也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页。当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。多个视图依赖于同一状态。来自不同视图的行为需要变更同一状态。


我们是否可以通过组件数据的传递来完成呢?


对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态。


但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的。

比如兄弟组件如何共享数据呢?


当然可以通过事件总线的方式传递数据。但是状态多了,也不好管理。所以我们就需要vue官方提供的状态管理库vuex。


Vuex的状态管理


下面这张图就可以概括vuex的一切了。


网络异常,图片无法展示
|


如果您不打算开发大型单页应用,您最好不要使用 Vuex。就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。


vuex使用


下面我们就来介绍vuex的使用了。


这里我们介绍的是vuex4.x的版本,所以需要安装 npm install vuex@next --save


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


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


  • Vuex的状态存储是响应式的


  • 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新。


  • 你不能直接改变store中的状态


  • 改变store中的状态的唯一途径就显示提交 (commit) mutation。


  • 而且异步数据,需要在action中提交mutation, 在项目中通过dispatch将数据提交到action。 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态。


vux的基本使用


  • 通过createStoreAPI创建一个store对象,用于保存数据。


  • 在app.use中注册这个store对象。 下面我们来介绍vuex中的5个核心


state


用于定义项目的共享数据。参数为对象或者函数。我们建议使用函数作为state的值。


const store = createStore({
      state() {
        return {
          rootCounter: 100
        }
      }
    })


我们项目中获取到定义的state呢?


  • 在template中,我们自己通过$store.state即可获取state的值。


  • 在optionsAPI中,我们可以通过this.$store.state获取state的值。


  • 在compositionAPI中,我们可以通过vuex提供的useStoreAPI获取到state的值。 一般我们都会都会将值放在computed中。


computed: {
    name() {
      return this.$store.name
    }
}


如果我们有很多数据需要在store中获取,直接通过上述方法取值,比较繁琐,所以我们可以通过vuex提供的对应的map方法,mapState


mapState做了什么呢?


他就是将store对象中对应的state做一层映射。他可以传入一个数组或者一个对象。并且返回一个对象,里面是计算函数。


  • 传入数组时,我们将state中的状态作为数组中的元素(字符串)


// 直接使用
    <h2>Home:{{ age }}</h2>
    <h2>Home:{{ name }}</h2>
    const store = createStore({
      state() {
        return {
          name: 'zh',
          age: 20
        }
      }
    })
    computed: {
      ...mapState(["name", "age"])
    }


网络异常,图片无法展示
|


  • 传入对象时,我们可以指定对应state状态的名字。防止和组件本身的data数据名同名。


<h2>Home:{{ sAge }}</h2>
    <h2>Home:{{ sName }}</h2>
    const store = createStore({
      state() {
        return {
          name: 'zh',
          age: 20
        }
      }
    })
    computed: {
       ...mapState({
          sAge: (state) => state.age,
          sName: (state) => state.name,
       })
    }


网络异常,图片无法展示
|


我们发现,在optionsAPI的computed中使用mapState非常简单,但是我们知道在compositionAPI的computed需要传入一个函数返回的是一个计算属性的值。这样mapState在其中就不好使用了。


下面我们来看看情况


<h2>{{storeState.age}}</h2>
    <h2>{{storeState.name}}</h2>
    <h2>{{storeState.counter}}</h2>
    const storeState = computed(() => ({
      ...mapState(['counter', 'name', 'age']),
    }))


网络异常,图片无法展示
|


从上面可以看出storeState中的属性都是一个个函数。


这是为什么呢?


因为mapState返回的是一个对象,他的属性就是返回的一个个计算函数。


可能有人会说,那么我们就在template使用的时候当成函数调用就行了啊。哈哈,我们来试试。


<h2>{{storeState.age()}}</h2>
    <h2>{{storeState.name()}}</h2>
    <h2>{{storeState.counter()}}</h2>


网络异常,图片无法展示
|


看到这,还有人不死心,说可以在调用的时候绑定this啊,将store对象绑定到该函数中啊。那确实。再来试试。


<h2>{{storeState.age.call({$store: store})}}</h2>
    <h2>{{storeState.name.call({$store: store})}}</h2>
    <h2>{{storeState.counter.call({$store: store})}}</h2>
    setup() {
        const store = useStore()
        const storeState = computed(() => ({
          ...mapState(['counter', 'name', 'age']),
        }))
        return {
          storeState,
          store,
        }
   }


网络异常,图片无法展示
|


这时候就可以展示出具体内容了。不容易啊。可是这样的实现,还不如直接通过store.state直接一个个取出数据呢。所以我们需要封装一个hook。步骤其实就是上面的实现过程。


import { useStore, mapState } from 'vuex'
    import { computed } from 'vue'
    const useState = function(mapper) {
        // mapper: Array | Object
        const store = useStore()
        //将返回一个对象
        const storeStateFns = mapState(mapper)
        // 用于存放获取到的state.属性: ref对象 键值对
        const storeState = {}
        Object.keys(storeStateFns).forEach(item => {
            // 这我们知道辅助函数的内部是通过this.$store来实现的
            // setup中没有this, 所以通过bind来改变this的指向
            const fn = storeStateFns[item].bind({$store: store})
            //将最后的值放在storeState中
            storeState[item] = computed(fn)
        })
        return storeState
    }
    export default useState


测试hook


<hr>数组
    <h2>{{counter}}</h2>
    <h2>{{name}}</h2>
    <h2>{{age}}</h2>
    <hr>对象
    <h2>{{sAge}}</h2>
    <h2>{{sCounter}}</h2>
    <h2>{{sName}}</h2>
    <hr>
    setup() {
      const storeState = useState(["counter", "name", "age"])
      const storeState2 = useState({
        sCounter: state => state.counter,
        sName: state => state.name,
        sAge: (state) => state.age
      })
      return {
        ...storeState,
        ...storeState2
      }
    }


网络异常,图片无法展示
|


getters


有时候我们需要从 store 中的 state 中派生出一些状态。例如计算列表的长度,可以在多个地方进行复用。我们就可以在getters中定义,他的作用就好像计算属性computed。但是这个缓存好像有问题。


网络异常,图片无法展示
|


const store = createStore({ 
    getters: {
        doubleRootCounter (state) {
          return state.age * 2
        }
    }
})


getters中定义的方法可以接受state作为一个参数


  • 我们主要是为了处理state中的状态。


state () {
     return {
       counter: 100,
       name: "zh",
       age: 20,
       books: [
         { name: "深入Vuejs", price: 200, count: 3 },
         { name: "深入Webpack", price: 240, count: 5 },
         { name: "深入React", price: 130, count: 1 },
         { name: "深入Node", price: 220, count: 2 },
       ],
       discount: 0.6,
       banners: []
     };
   },
   getters: {
     currentDiscount(state) {
       return state.discount * 0.9
     }
   }


getters中定义的方法也可以接受另一个参数getters


  • 主要是为了结合其他的getter做一些事情。 下面这个例子是结合当前的折扣,来计算总价格


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


如果我们想要使用外界传入的数据,来结合state中的状态,我们可以让getter返回一个函数,并将外界传入的数据作为这个函数的参数。


下面这个例子是让外界传入一个整数,来过滤计算的总价格


getters: {
    totalPriceCountGreaterN(state, getters) {
      return function(n) {
        let totalPrice = 0
        for (const book of state.books) {
          if (book.count > n) {
            totalPrice += book.count * book.price
          }
        }
        return totalPrice * getters.currentDiscount
      }
    }
  }


我们定义了上面的getters方法,如何在项目中用起来呢?


  • 在template中,直接通过 $store.getters获取即可。


<h2>总价值: {{ $store.getters.totalPrice }}</h2>
    <h2>总价值: {{ $store.getters.totalPriceCountGreaterN(1) }}</h2>


  • 在optionsAPI中,我们通过this.$store.getters获取即可。


computed: {
        totalPrice() {
          return this.$store.getters.totalPrice
        }
    }


  • 在compositionAPI中,我们通过vuex提供的useStoreAPI来获取。


setup() {
    const store = useStore()
    const sGetter = computed(() => store.getters.totalPrice)
    return {
      sGetter,
    }
  }


同获取state一样,如果我们有很多getter需要在store中获取,直接通过上述方法取值,比较繁琐,所以我们可以通过vuex提供的对应的map方法,mapGetters


他的使用同mapState一样,可以传入数组或者对象,在optionsAPI中的computed使用不会出现问题。


<h2>{{ sNameInfo }}</h2>
    <h2>{{ sAgeInfo }}</h2>
    <h2>{{ ageInfo }}</h2>
    <h2>{{ heightInfo }}</h2>
  getters: {
    nameInfo (state) {
      return `name: ${state.name}`
    },
    ageInfo (state) {
      return `age: ${state.age}`
    }
  },
   computed: {
    ...mapGetters(['nameInfo', 'ageInfo']),
    ...mapGetters({
      sNameInfo: 'nameInfo',
      sAgeInfo: 'ageInfo',
    }),
  }


对应的我们可以借鉴对mapState封装的一个hook。我们只需要给mapState改成mapGetters即可。


import { useStore, mapGetters } from 'vuex'
import { computed } from 'vue'
const useGetters = (mapper) => {
  const store = useStore()
  const storeGetterFns = mapGetters(mapper)
  const storeGetter = {}
  Object.keys(storeGetterFns).forEach((item) => {
    const fn = storeGetterFns[item].bind({ $store: store })
    storeGetter[item] = computed(fn)
  })
  return storeGetter
}
export default useGetters


测试hook


<h2>{{ nameInfo }}</h2>
  <h2>{{ ageInfo }}</h2>
 setup() {
    const storeGetters = useGetters(['nameInfo', 'ageInfo'])
    return {
      ...storeGetters,
    }
  },


网络异常,图片无法展示
|


相关文章
|
8天前
|
JavaScript 前端开发
在Vue中使用TypeScript的常见问题有哪些?
在Vue中使用TypeScript的常见问题有哪些?
35 2
|
8天前
|
JavaScript 前端开发
在Vue中使用TypeScript的优缺点是什么?
在Vue中使用TypeScript的优缺点是什么?
20 0
|
8天前
|
JavaScript
在 Vue 中如何使用 TypeScript?
在 Vue 中如何使用 TypeScript?
17 0
|
8天前
|
JavaScript 安全 容器
Vue3 + setup + TypeScript: 构建现代、类型安全的Vue应用的关键技巧总结
当使用 setup 的时候,组件直接引入就可以了,不需要再自己手动注册
|
8天前
|
前端开发 JavaScript 测试技术
Vue3+Vite+TypeScript常用项目模块详解(下)
现在无论gitee还是github,越来越多的前端开源项目采用Vue3+Vite+TypeScript+Pinia+Elementplus+axios+Sass(css预编译语言等),其中还有各种项目配置比如eslint 校验代码工具配置等等,而我们想要进行前端项目的二次开发,就必须了解会使用这些东西,所以作者写了这篇文章进行简单的介绍。
|
6天前
|
缓存 监控 JavaScript
探讨优化Vue应用性能和加载速度的策略
【5月更文挑战第17天】本文探讨了优化Vue应用性能和加载速度的策略:1) 精简代码和组件拆分以减少冗余;2) 使用计算属性和侦听器、懒加载、预加载和预获取优化路由;3) 数据懒加载和防抖节流处理高频事件;4) 图片压缩和选择合适格式,使用CDN加速资源加载;5) 利用浏览器缓存和组件缓存提高效率;6) 使用Vue Devtools和性能分析工具监控及调试。通过这些方法,可提升用户在复杂应用中的体验。
21 0
|
7天前
|
JavaScript 前端开发
vue(1),小白看完都会了
vue(1),小白看完都会了
|
7天前
|
JavaScript 数据库
ant design vue日期组件怎么清空 取消默认当天日期
ant design vue日期组件怎么清空 取消默认当天日期
|
7天前
|
JavaScript C++
vue高亮显示组件--转载
vue高亮显示组件--转载
12 0
|
2天前
|
JavaScript Java 关系型数据库
基于SprinBoot+vue的租房管理系统2
基于SprinBoot+vue的租房管理系统2
9 0