在 Vue 生态系统中,状态管理一直是开发者们关注的焦点之一。而随着 VueUse 的出现,我们迎来了一种全新的方式来处理状态管理,它让我们能够以更简单、更灵活的方式来管理应用程序的状态。
在本文中我们将深入探讨 VueUse 中与状态管理相关的内容,从基本的状态响应式到高级的状态管理技巧,以及一些实用的状态管理模式。无论你是刚刚接触 Vue 还是已经是一名经验丰富的 Vue 开发者,本文都将为你带来新的见解和灵感,让你在状态管理的道路上走得更远、更高。
目录
createGlobalState(项目全局状态)
createInjectionState(组件全局状态)
useAsyncState(异步加载的所有状态)
useLastChanged(获取前后时间差)
useStorage(本地存储数据管理)
useLocalStorage(本地存储空间管理)
useSessionStorage(会话存储空间)
最后总结
createGlobalState(项目全局状态)
将状态保存全局作用域中,以便跨Vue实例复用。什么意思呢?就是实现类似pinia和vuex的功能效果,将数据进行集中状态管理,放在全局上所有的组件都能拿到使用,所有使用这个全局状态的组件都可以共享和修改这个对象。它不需要组件之间的层次结构,可以在任何地方使用。牛犇只能说,如下:
创建一个store文件夹用于存储全局状态数据,借助createGlobalState函数撰写响应式数据,被设置相关的actions和getters函数,并把state和函数return出去。然后在组件中直接使用即可,方法和pinia的写法类似只能说,会pinia使用createGlobalState简直手到擒来!
createGlobalState并没有使用持久化的效果,实现持久化需要再借助另一个API函数,这个后面会讲到,先看看实现的效果吧!非常nice。
createInjectionState(组件全局状态)
创建可以注入到组件中的全局状态。什么意思呢?就是允许在组件之间共享状态,使用类似vue中的 provide / inject API,可以在父组件中创建状态,然后把状态注入到子组件中,子组件来拿到状态数据,这种方式适用于有层次结构的组件之间的状态传递,如下:
我们在仓库中定义一个组件全局状态的函数,我们并不需要给其设置初始值而是通过传参即可:
import { ref, computed } from "vue";
import { createInjectionState } from "@vueuse/shared";
const [useProvideCounterStore, useCounterStore] = createInjectionState((initialValue) => {
// state
const count = ref(initialValue)
// actions函数
const increment = () => {
count.value++
}
// getters函数
const double = computed(() => count.value * 2)
return { count, double, increment }
})
// 如果要隐藏“useCounterStore”并将其包装在默认值逻辑中或抛出错误逻辑,请不要导出“useCounterStore”
export { useProvideCounterStore, useCounterStore }
接下来我们通过如下的方式进行父子组件状态的传递,仓库只是一个踏板,真正控制数据的还是父组件,如下:
最终达到的效果如下所示:
createSharedComposable(封装复用函数)
让一个钩子函数可用于多个Vue实例中。什么意思呢?就是用于创建共享的组合式函数,将逻辑组织为可重用的函数,以更好地管理组件的复杂性。意义在于创建一个可以在多个组件之间共享状态和逻辑的组合式函数,避免在多个组件中重复编写相同的逻辑,同时也提高了代码的可维护性和可复用性,如下:
我们随便创建个文件,然后写一个共享的函数,注意createSharedComposable参数只能是一个,如果你想返回多个函数的话需将其放在一个函数里面然后return出去即可,这里我们使用了自带的API函数还有自定义一个函数,然后将这两个公共函数return出去:
import { createSharedComposable, useMouse } from '@vueuse/core'
// 自定义函数
const custom = ()=>{
return [ 1, 2 ]
}
// createSharedComposable必须返回一个函数
export const useSharedMouse = (()=>{
return {
useMouse,
custom
}
})
接下来在两个子组件中使用createSharedComposable给我们的共享函数:
然后在父组件中调用这两个子组件即可,发现两个子组件的得到的结果都是一样的,程序复用:
useAsyncState(异步加载的所有状态)
响应式获取异步状态。不会阻塞setup 函数,在promise完成后,将自动触发。什么意思呢?就是为了简化在 Vue 组件中处理异步操作的状态管理。通常情况下,当我们在 Vue 组件中执行异步操作时,需要定义多个状态来表示异步操作的不同阶段(如加载中、成功、失败等),并在组件中进行相应的状态管理和更新。而 useAsyncState 函数可以帮助我们更轻松地管理这些状态。如下:
这里我直接打出 useAsyncState 基本上所有用到的参数,通过注释可以看到每个参数具体的含义
<template>
<div class="use-async-state">
接口数据:{{ ajaxData.state }}
<br><br>
Ready: {{ ajaxData.isReady ? '准备完毕' : '未准备' }}
<br>
Loading: {{ ajaxData.isLoading ? '加载中' : '加载完毕' }}
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, onBeforeMount } from 'vue'
import { useAsyncState } from '@vueuse/core'
const ajaxData = reactive({
state: '',
isReady: false,
isLoading: true,
})
const fetchUser = async() => {
const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${1}`)
return resp.json()
}
const { state, isLoading, isReady, error, execute }= useAsyncState(fetchUser, {default: '未加载前的数据'}, {
// immediate: false, // 默认是true,控制组件加载后立即执行
delay: 2000, // 默认是0,控制组件加载后多久执行
onSuccess: () => {
ajaxData.state = state.value
ajaxData.isLoading = isLoading.value
ajaxData.isReady = isReady.value
console.log('useAsyncState', state.value);
},
onError: () => {}, // 错误处理
})
console.log('setup函数中',state.value);
onMounted(()=>{
console.log('我是挂载后',state.value);
})
onBeforeMount(()=>{
console.log('我是挂载前',state.value);
})
</script>
我设置了延迟两秒执行获取接口数据,结果如下:
代码中的immediate属性,搭配execute可以手动控制函数的执行,如下:
<template>
<div class="use-async-state">
接口数据:{{ ajaxData.state }}
<br><br>
Ready: {{ ajaxData.isReady ? '准备完毕' : '未准备' }}
<br>
Loading: {{ ajaxData.isLoading ? '加载中' : '加载完毕' }}
<br />
<button @click="loadData">重新加载数据</button>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, onBeforeMount } from 'vue'
import { useAsyncState } from '@vueuse/core'
const ajaxData = reactive({
state: '',
isReady: false,
isLoading: true,
})
const fetchUser = async(id: number) => {
const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
return resp.json()
}
const { state, isLoading, isReady, error, execute }= useAsyncState(fetchUser, {default: '未加载前的数据'}, {
immediate: false, // 默认是true,控制组件加载后立即执行
delay: 2000, // 默认是0,控制组件加载后多久执行
onSuccess: () => {
ajaxData.state = state.value
ajaxData.isLoading = isLoading.value
ajaxData.isReady = isReady.value
console.log('useAsyncState', state.value);
},
onError: () => {}, // 错误处理
})
let id = 0
const loadData = () => {
ajaxData.state = ''
ajaxData.isReady = false
ajaxData.isLoading = true
id+=1
execute(2000, id) // 执行函数,第一个参数是延迟时间,第二个参数是函数的参数
}
console.log('setup函数中',state.value);
onMounted(()=>{
loadData()
console.log('我是挂载后',state.value);
})
onBeforeMount(()=>{
console.log('我是挂载前',state.value);
})
</script>
最终呈现的效果如下,效果很不错!
useLastChanged(获取前后时间差)
记录最后一次更改的时间戳。什么意思呢?就是跟踪指定的值在最后一次发生改变的时间,这在某些场景下非常有用,比如监控某个状态的变化时间,或者执行一些特定的操作,当值发生改变时,为开发者提供方便的状态管理功能,从而提高开发效率和代码可维护性。如下:
这里我通过useLastChanged获取输入框前后输入数据的时间差:
<template>
<div class="use-last-changed">
<input v-model="input" /> <br />
lastChanged: {{ lastChanged }}
<br />
formattedTime: {{ formattedTime }}
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useLastChanged } from '@vueuse/core'
const input = ref('')
const lastChanged = useLastChanged(input)
// 格式化时间的 ref
const formattedTime = ref('')
watch(lastChanged, () => {
const timestamp = lastChanged.value?.toString()
const date = new Date(Number(timestamp))
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hours = date.getHours()
const minutes = date.getMinutes()
const seconds = date.getSeconds()
// 更新 formattedTime
formattedTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
})
</script>
达到的效果如下,总体来说还是不错的api:
useStorage(本地存储数据管理)
响应式 LocalStorage/SessionStorage。什么意思呢?就是它用于在 Vue 组件中管理浏览器本地存储(LocalStorage 或 SessionStorage)的值。通过使用 useStorage,你可以方便地在 Vue 组件中读取、写入和监听本地存储中的数据,而不需要手动处理存储和监听逻辑。这可以帮助你更轻松地在应用程序中实现持久化数据的存储和状态管理。
如下我们对useStorage进行一个最简便的操作演示,默认情况下,useStorage将使用存储中的值,如果它存在就会忽略你传入的默认值。忽略的默认值去访问肯定是未定义的。
最终得到如下的结果,父组件存储的数据是持久化的,修改或删除本地数据,子组件也会发生变化,注意useStorage默认是存储本地存储空间,即localStorage:
如果你想采用sessionStorage存储的话,给useStorage传递第三个参数即可,如下:
结果如下,这里不再赘述:
合并默认值:如果你想对原本本地存储的数据和你传递的默认值进行合并的话,可以采用如下的操作,启用mergeDefaault选项,将其设置为true时,它将对对象执行浅合并:
您可以传递一个函数来执行自定义合并(例如深度合并),例如:
const state = useStorage(
'my-store',
{ hello: 'hi', greeting: 'hello' },
localStorage,
{ mergeDefaults: (storageValue, defaults) => deepMerge(defaults, storageValue) }
)
自定义序列化:
默认情况下,useStorage会根据提供的默认值的数据类型巧妙地使用相应的序列化程序。例如,JSON. stringify/JSON.parse将用于对象,Number.toString/parseFloat用于数字等,还可以提供自己的序列化函数来使用Storage,如下:
import { useStorage } from '@vueuse/core';
// 自定义序列化
const store = useStorage(
'key',
'123',
undefined,
{
serializer: {
read: (v: any) => v ? JSON.parse(v) : null,
write: (v: any) => JSON.parse(v),
},
},
)
console.log(store.value) // 123
store.value = null
console.log(store.value) // null
请注意,当您提供null作为默认值时,useStorage不能从中假定数据类型。在这种情况下,您可以提供自定义序列化程序或显式重用内置序列化程序。
import { StorageSerializers, useStorage } from '@vueuse/core'
const objectLike = useStorage('key', null, undefined, { serializer: StorageSerializers.object })
objectLike.value = { foo: 'bar' }
useLocalStorage(本地存储空间管理)
响应式的 LocalStorage。什么意思呢?就是用于在 Vue 组件中使用本地存储的 Hook。它能够方便地将数据保存在浏览器的本地存储中,并且自动将存储的数据与 Vue 组件中的状态进行同步。
和上面useStorage相比就是这个更具针对性,用法一样:
useSessionStorage(会话存储空间)
响应式SessionStorage。什么意思呢?就是用于在 Vue 组件中使用会话存储的 Hook。与 useLocalStorage 类似,它能够方便地将数据保存在浏览器的会话存储中,并且自动将存储的数据与 Vue 组件中的状态进行同步。
最后总结
createGlobalState:用于在任何组件中共享状态,创建一个全局响应式对象。
createInjectionState:用于在父子组件之间传递状态,通过provide/inject API实现。
createSharedComposable:封装复用函数,多组件共享函数的状态和逻辑。
useAsyncState:简化在 Vue 组件中处理异步操作的状态管理,减少重复的状态管理代码。
useLastChanged:跟踪指定的值在最后一次发生改变的时间。
useStorage:用于在Vue组件中管理浏览器本地存储LocalStorage或SessionStorage的值。
useLocalStorage:利用浏览器的本地存储功能,实现数据的持久化存储和状态管理。
useSessionStorage:将数据保存在会话存储中,实现临时性数据的存储和状态管理。