前言
之前使用vue3都是在公司的基建项目中,为了快速达到目的,把以前vue2的模板拿来简单改改就直接用了,所以项目中用法特别乱,比如:状态管理依旧用的vuex
,各种类型定义全是any
,有些代码是选项式API,有些代码是组合式API...
最近终于有时间推动一下业务项目使用vue3
了。作为极简主义的我,始终奉行少即是多,既然是新场景,一切从新,从头开始写模版:
- 使用最新的vue3版本
v3.5.x
。 - 所有使用的内部库全部生成
ts
类型并引入到环境中。 - 将所有的
mixins
重写,包装成组合式函数。 - 将以前的
vue
上的全局变量挂载到app.config.globalProperties
。 - 全局变量申明类型到
vue-runtime-core.d.ts
中,方便使用。 - 全部使用
setup
语法,使用标签<script setup lang="ts">
- 使用
pinia
作为状态管理。
pinia使用
等等,pinia
?好用吗?打开官方文档研究了下,官方优先推荐的是选项式API的写法。
调用defineStore
方法,添加属性state, getters, actions
等。
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment () {
this.count++
},
},
})
使用的时候,调用useCounterStore
即可。
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'
const store = useCounterStore()
setTimeout(() => {
store.increment()
}, 1000)
const doubleValue = computed(() => store.doubleCount)
看上去还不错,但是我模版中全部用的是组合式写法,肯定要用组合式API,试着写了个demo
,ref
就是选项式写法中的state
,computed
就是选项式中的getters
,function
就是actions
。
// useTime.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import vueConfig from '../../../common/config/vueConfig'
import * as dayjs from 'dayjs'
export default defineStore('time', () => {
const $this = vueConfig()
const time = ref<number>()
const timeFormat = computed(() => dayjs(time.value).format('YYYY-MM-DD HH:mm:ss'))
const getSystemTime = async () => {
const res = await $this?.$request.post('/system/time')
time.value = Number(res.timestamp)
}
return { timeFormat, getSystemTime }
})
调用时解构赋值,就可以直接用了。
// index.vue
<script setup lang="ts">
import { onMounted } from 'vue'
import useTime from './use/useTime'
const { timeFormat, getSystemTime } = useTime()
onMounted(async () => {
// 请求
await getSystemTime()
console.log('当前时间:', timeFormat)
})
</script>
优雅了很多,之前用vuex
时还有个问题,storeA
中的state、actions
等,会在storeB
中使用,这一点pinia
文档也有说明,直接在storeB
调用就好了,比如我想在另一个组件中调用上文中提到的timeFormat
。
defineStore('count', () => {
const count = ref<number>(0)
const { timeFormat } = useTime()
return {
count,
timeFormat,
}
})
怎么看着这么眼熟呢,这不就是组合式函数吗?为什么我要用defineStore
再包一层呢?试一试不用pinia
,看能不能完成状态管理。
组合式函数
直接添加一个useCount.ts
文件,申明一个组合式函数。
// useCount.ts
import { computed, ref } from 'vue'
const useCount = () => {
const count = ref<number>(0)
const doubleCount = computed(() => {
return count.value * 2
})
const setCount = (v: number) => {
count.value = v
}
return {
count,
doubleCount,
setCount,
}
}
export default useCount
使用时直接解构申明,并使用。
import useCount from './use/useCount'
const { count, setCount } = useCount()
onMounted(async () => {
console.log('count', count.value) // 0
setCount(10)
console.log('count', count.value) // 10
})
最大的问题来了,如何在多个地方共用count
的值呢,这也是store
最大的好处,了解javascript
函数机制的我们知道useCount
本身是一个闭包,每次调用,里面的ref
就会重新生成。count
就会重置。
import useCount from './use/useCount'
const { count, setCount } = useCount()
const { doubleCount } = useCount()
onMounted(async () => {
console.log('count', count.value, doubleCount.value) // 0 0
setCount(10)
console.log('count', count.value, doubleCount.value) // 10 0
})
这个时候doubleCount
用的并不是第一个useCount
中的count
,而是第二个重新生成的,所以setCount
并不会引起doubleCount
的变化。
怎么办呢?简单,我们只需要把count
的声明暴露在全局环境中,这样在import
时就会申明了,调用函数时不会被重置。
import { computed, ref } from 'vue'
const count = ref<number>(0)
const useCount = () => {
const doubleCount = computed(() => {
return count.value * 2
})
const setCount = (v: number) => {
count.value = v
}
return {
count,
doubleCount,
setCount,
}
}
export default useCount
当我们多次调用时,发现可以共享了。
import useCount from './use/useCount'
const { count, setCount } = useCount()
const { doubleCount } = useCount()
onMounted(async () => {
console.log('count', count.value, doubleCount.value) // 0 0
setCount(10)
console.log('count', count.value, doubleCount.value) // 10 20
})
但是这个时候count
是比较危险的,store
应该可以保护state
不被外部所修改,很简单,我们只需要用readonly
包裹一下返回的值即可。
import { computed, readonly, ref } from 'vue'
const count = ref<number>(0)
const useCount = () => {
const doubleCount = computed(() => {
return count.value * 2
})
const setCount = (v: number) => {
count.value = v
}
return {
// readonly可以确保引用对象不会被修改
count: readonly(count),
doubleCount,
setCount,
}
}
export default useCount
总结
经过我的努力,vue3
又减少了一个库的使用,我就说不需要用pinia
,不过放弃pinia
也就意味着放弃了它自带的一些方法store.$state
,store.$patch
等等,这些方法实现很简单,很轻松就可以手写出来,如果你是这些方法的重度用户,保留pinia
也没问题,如果你也想代码更加精简,赶紧尝试下组合式函数吧。