Vue3中的reactive、ref实现状态管理,并实现持久化

436 阅读4分钟

引言

在Vue 3中,响应式系统的核心在于reactiveref。这两个API不仅提供了强大的状态管理能力,还具备了在多个组件之间共享状态的灵活性。令人称道的是,reactiveref无需在组件内部定义,这使得在不同组件间共享状态变得尤为便捷。因此,在中小型的Vue 3项目中,完全可以借助reactiveref来替代PiniaVuex,实现高效的状态管理。

使用reactive、ref实现状态管理

准备好一个vue 3项目,在src目录下新建一个store目录,在目录下新建一个index.ts文件。代码如下:

import { ref, reactive } from 'vue'

const reactiveState = reactive({
	name: '张三'
})

const refState = ref({
	count: 0
})

function setName(val: string) {
	reactiveState.name = val
}

function setCount(val: number) {
	refState.value.count = val
}

export const useStore = () => {
	return { reactiveState, refState, setName, setCount }
}

这样我们就得到了一个简单的状态管理工具。虽然 refreactive 的实现方法相似,但 ref 在重新赋值和读取时需要使用 .value。基本类型的话可以直接使用ref去做状态管理,但如果是对象类型整个调用链就会特别长,这感觉非常不便。因此,我个人更倾向于直接使用 reactive 进行状态管理,因为它使用起来更加简洁。


虽然我们已经实现了一个简单的状态管理工具,但这与我预期的功能仍有差距。例如,使用 reactive 时无法使用解构语法,这会导致属性失去响应性,使用起来非常不便。那么,有什么方法可以解决这个问题呢?在 vue 3 中,toRefs 这个 API 可以帮我们解决这个问题。实际上,Pinia 中的 storeToRefs 方法就是利用 vue 3 中的 toRefstoRef API 实现的。这边我打算直接使用toRefs 来解决这个问题。代码如下:

import { reactive, toRefs } from 'vue'

const reactiveState = reactive({
	name: '张三',
	age: 18
})

const actions = {
	setName(val: string) {
		reactiveState.name = val
	},
	setAge(val: number) {
		reactiveState.age = val
	}
}

const toRefsReactive = toRefs(reactiveState)
export const useStore = () => {
	return { ...toRefsReactive, ...action }
}

使用效果如下:

上面我们已经解决了解构的问题,使得实际使用更加便捷。然而,在使用过程中,我们可以选择不使用状态管理中提供的修改属性方法,而是直接修改可访问的属性。这种方式使得状态的变化难以追踪,不利于调试和管理。Vue 3 中的 readonly API 可以接受一个对象(无论是响应式的还是普通的)或是一个 ref,并返回一个原值的只读代理。通过结合 readonly,我们可以实现这一功能。但是readonly可以实现无法通过直接访问属性来修改状态。然而,使用时该语法仅在运行时报 warning,无法在编译时提示无法修改的问题。这里我选择TypeScript中的readonly关键字去解决这个问题。代码如下:

先在utils中新建一个types.ts文件这里定义公共的类型别名。代码如下:

/**
 * Readonly
 * 递归只读类型别名
 */
export type Readonly<T> = {
	readonly [K in keyof T]: T[K] extends object ? Readonly<T[K]> : T[K]
}

然后改造index.ts代码如下:

import { reactive, toRefs, readonly } from 'vue'
import { Readonly } from '@/utils/types'

const state = reactive({
	name: '张三',
	age: 18
})

const actions = {
	setName(val: string) {
		state.name = val
	},
	setAge(val: number) {
		state.age = val
	}
}
const toRefsState = toRefs(readonly(state))

const readonlyState: Readonly<typeof toRefsState> = toRefsState

export const useStore = () => {
	return { ...readonlyState, ...actions }
}

现在编译时就有很好的代码提示,运行时也无法直接修改属性值,只能通过特定的方法去修改状态。如下:

实现持久化

有时候我们可能需要去持久化的去存储一些重要的信息,所以我们接下来去实现状态管理工具的持久化功能。好废话不多说,直接上代码:

  1. 先定义个状态管理任务taskSet去存储要持久化的属性
const taskSet = new Set<string>()
  1. 编写setSessionStorage NotSpecifiedType:限制存储的值不能是null|undefined
export type NotSpecifiedType<T, U> = T extends U ? never : T

function setSessionStorage<T>(key: string, taskKey: string, val: NotSpecifiedType<T, null | undefined>) {
	if (!taskSet.has(key)) {
		taskSet.add(key)
		sessionStorage.setItem(taskKey, JSON.stringify([...taskSet]))
	}
	sessionStorage.setItem(key, JSON.stringify(val))
}
  1. 编写getSessionStorage
function getSessionStorage(state: any) {
  taskSet.forEach((value, key) => {
    const storageVal = sessionStorage.getItem(key) || null
    const val = storageVal && JSON.parse(storageVal)
    if (key in state) {
      state[key] = val
    }
  })
}
  1. 从缓存中获取taskSet、设置taskSet
const sessionTaskSet = sessionStorage.getItem('index_TaskSet') || null
const taskSet = new Set<string>(sessionTaskSet && JSON.parse(sessionTaskSet))
  1. 持久化存储数据
setName(val: string, persistence?: boolean) {
  state.name = val
  persistence && setSessionStorage('name', 'index_TaskSet', val)
},
  1. 获取持久化的数据
if (storageTask.size) {
  getSessionStorage(state)
}

下面是完整的代码:

import { reactive, toRefs, readonly } from 'vue'
import { NotSpecifiedType, Readonly } from '@/utils/types'

const sessionTaskSet = sessionStorage.getItem('index_TaskSet') || null
const taskSet = new Set<string>(sessionTaskSet && JSON.parse(sessionTaskSet))

const state = reactive({
	name: '张三',
	age: 18
})

// 获取持久化的数据
if (taskSet.size) {
	getSessionStorage(state)
}

const actions = {
	setName(val: string, persistence?: boolean) {
		state.name = val
		persistence && setSessionStorage('name', 'index_TaskSet', val)
	},
	setAge(val: number, persistence?: boolean) {
		state.age = val
		persistence && setSessionStorage('age', 'index_TaskSet', val)
	}
}

const toRefsState = toRefs(readonly(state))

const readonlyState: Readonly<typeof toRefsState> = toRefsState

function setSessionStorage<T>(key: string, taskKey: string, val: NotSpecifiedType<T, null | undefined>) {
	if (!taskSet.has(key)) {
		taskSet.add(key)
		sessionStorage.setItem(taskKey, JSON.stringify([...taskSet]))
	}
	sessionStorage.setItem(key, JSON.stringify(val))
}

function getSessionStorage(state: any) {
	taskSet.forEach((value, key) => {
		const storageVal = sessionStorage.getItem(key) || null
		const val = storageVal && JSON.parse(storageVal)
		if (key in state) {
			state[key] = val
		}
	})
}

export const useStore = () => {
	return { ...readonlyState, ...actions }
}

到这里我们就完成了一个简单的持久化状态管理工具。

结语

文章在这里就结束了,有任何不准确的地方请多多指正,希望各位大佬勿喷。感谢你的阅读,愿你在技术探索的路上不断前行,一起加油!