前端状态管理方案 - 作用域单例模式

29 阅读2分钟

记录一下本人工作中遇到的一些问题,以及解决思路

背景

本人设计项目设计之初,状态管理最开始使用pina

但是pina更适合作为一种全局的状态管理方式。

面对以下场景时可能不太合适:

  • 1、涉及到不同实例间的切换: 本项目中:不同的案件共用一个pina都store, 可能会面对不同案件之间相互污染数据的情况: 在激活案件a时,store此时存储着案件a的数据,这时候如果发起一个请求a,这个请求a会涉及到改变store中的状态。在请求a还未返回时,切换至案件b,此时store要切换至存储案件b的数据,这样一来,当请求a返回时,带来的是案件a的数据,就会污染此时存储着案件b的store。

  • 2、每个案件存储的数据量其实比较庞大,理论上,只需要这工作空间这一个模块需要这些响应式数据,切换至别的模块时,这些数据应该清理。

    尽管可以在pina中手动设置reset,来清理数据。但事实上,如果涉及到的状态变量太多,难免没有清理干净,带来内存方面的压力,尽管在页面上影响不大。但这不利于后续的维护,设计上可以再优化下

解决方式:

一、作用域单例:

有点类似于react的设计,具体实方法如下

实现方式: 1、创建一个工厂函数,里面包含模块内部所需要的所有状态和方法

export function useMaterialProcess() {
  const currentID = ref<string | null>(null);
  const currentStatus = ref<number>(0);
  ...
  
  return {
    currentID,
    currentStatus
  }
 }

2、封装Provider组件

2.1: symbol确保唯一key + 封装注入hook

import type { InjectionKey } from 'vue'
import { inject } from 'vue'
import { useMaterialProcess } from './useMaterialProcess'

export type MaterialProcessContext = ReturnType<typeof useMaterialProcess>
// symbol确保唯一key
export const materialProcessKey: InjectionKey<MaterialProcessContext> = Symbol('MaterialProcess')

export function useMaterialProcessInject() {
  const ctx = inject(materialProcessKey)
  if (!ctx) {
    throw new Error('useMaterialProcessInject must be used inside <MaterialProcessProvider>')
  }
  return ctx
}

2.2: Provider组件,负责创建一次实例 & Provider

这样可以确保:

a) ctx只会创建一次, 组件存在期间就是单例

b) 组件卸载 即为 自动释放

<script setup lang="ts">
import { provide, onUnmounted } from 'vue'
import { materialProcessKey } from './materialProcessContext'
import { useMaterialProcess } from './useMaterialProcess'

const ctx = useMaterialProcess()
provide(materialProcessKey, ctx)

onUnmounted(() => {
    // 处理回收逻辑
})
</script>

<template>
  <slot />
</template>

2.3 使用方式:

模块最外层包裹一层provider:

通过 key 确保每次切换新的case,都会更新,不会造成实例的污染

<template>
  <MaterialProcessProvider :key="caseId">
    <OutestModule />
  </MaterialProcessProvider>
</template>

<script setup lang="ts">
import MaterialProcessProvider from './composables/MaterialProcessProvider.vue';
</script>

综上,通过以上方式,可从根本上解决本项目使用pina所带来的问题