重学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,
    }
  },


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


相关文章
|
3月前
|
JavaScript 前端开发 安全
【技术革新】Vue.js + TypeScript:如何让前端开发既高效又安心?
【8月更文挑战第30天】在使用Vue.js构建前端应用时,结合TypeScript能显著提升代码质量和开发效率。TypeScript作为JavaScript的超集,通过添加静态类型检查帮助早期发现错误,减少运行时问题。本文通过具体案例展示如何在Vue.js项目中集成TypeScript,并利用其类型系统提升代码质量。首先,使用Vue CLI创建支持TypeScript的新项目,然后构建一个简单的待办事项应用,通过定义接口描述数据结构并在组件中使用类型注解,确保代码符合预期并提供更好的编辑器支持。
84 0
|
3月前
|
JavaScript 前端开发 安全
立等可取的 Vue + Typescript 函数式组件实战
立等可取的 Vue + Typescript 函数式组件实战
|
4月前
|
JavaScript 前端开发
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
49 0
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
|
5月前
|
JavaScript 安全 前端开发
Vue 3 中的 TypeScript
【6月更文挑战第15天】
83 6
|
6月前
|
JavaScript 前端开发 开发者
类型检查:结合TypeScript和Vue进行开发
【4月更文挑战第24天】TypeScript是JavaScript超集,提供类型注解等特性,提升代码质量和可维护性。Vue.js是一款高效前端框架,两者结合优化开发体验。本文指导如何配置和使用TypeScript与Vue:安装TypeScript和Vue CLI,创建Vue项目时选择TypeScript支持,配置`tsconfig.json`,编写`.tsx`组件,最后运行和构建项目。这种结合有助于错误检查和提升开发效率。
56 2
|
6月前
|
JavaScript 前端开发 开发者
Vue工具和生态系统: Vue.js和TypeScript可以一起使用吗?
【4月更文挑战第18天】Vue.js与TypeScript兼容,官方文档支持在Vue项目中集成TypeScript。TypeScript作为JavaScript超集,提供静态类型检查和面向对象编程,增强代码准确性和健壮性。使用TypeScript能提前发现潜在错误,提升代码可读性,支持接口和泛型,使数据结构和函数更灵活。然而,不是所有Vue插件都兼容TypeScript,可能需额外配置。推荐尝试在Vue项目中使用TypeScript以提升项目质量。
113 0
|
JavaScript 前端开发
基于Vue2+TypeScript的项目规划搭建
最近手头的项目都已经接近尾声,时间比较宽裕,于是想着升级一下网站。上一版的网站还是我刚接触前端时设计的,使用Zepto为主要框架,代码毫无模块化思想,因为后续的功能越加越多,现在每次维护都有自杀的冲动。
1634 0
|
8天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。