什么?我居然在 React 用 Pinia?

众所周知,相信大家用过 Pinia 的都知道,Pinia 定义储存采用的是 Centralized State Management(集中式状态管理)的模式,从而区分 State(状态)、Actions(操作)、Getters(计算),而这种模式非常利于数据与视图的逻辑分离:

ts 复制代码
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    },
  },
  getters: {
    doubled() {
      return this.count * 2
    },
  }
})

而视图组件则只需要 use 对应的数据集合,则可直接读取统一管理的数据:

ts 复制代码
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()

counter.count++
counter.increment()
</script>

<template>
  <div>Current Count: {{ counter.count }}</div>
</template>

Pinia 凭借简洁的语法定义与逻辑架构,完美解决了 Vuex 遗留的类型推导缺失及魔法字符串问题。目前 Pinia 已成为 Vue 生态中数据管理的首选方案,足以证明了该设计模式对开发者体验的巨大提升。

React

React 的状态管理由社区支持,状态库层出不穷,从很久以前的 Redux、Mobx,到现在流行的 Zustand、Valtio、Recoil、Jotai、XState,每一个都有自己的设计哲学,作为一个前端开发,大部分我都用了个遍,说实话你问我哪个语法上好用,我是真不知道,它们每一个都可以作为生产级别的数据储存,但我都觉得它们要么太复杂、抽象度太高;要么语法是简洁了,但 State、Action 非常零散,接手项目的时候看的人脑壳痛。

大伙可能想到了,和 Pinia 类似的不就是 Valtio。作为一个轻量级的状态管理库,Valtio 在数据管理的逻辑上,使用 proxy(代理)驱动视图更新,与 Pinia 和 vue3 的逻辑非常相似,这样的状态管理方式更加精简,学习曲线也更低,也不需要 dispatch 之类的概念。

但 Valtio 定义数据的方式则是直接定义代理对象,作为快速启动中小型项目来说是很方便,但是一但数据多起来了,每个页面的 actions 跟 getters 就会非常分散。而我们只需要解决这个问题就可以了。

Valtio Define

github.com/Hairyf/valt...

首先是 API 的设计,我们就参考 Pinia 的模式,通过包装器(defineStore)作为定义数据的手段:

ts 复制代码
import { defineStore } from 'valtio-define'

const store = defineStore({
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    },
  },
  getters: {
    doubled() {
      return this.count * 2
    },
  }
})
store.$state // { count: number }
store.$getters // { doubled: number }
store.$subscribe // Function -> 等同于 subscribe(store.$state)
store.increment // Function

使用钩子(useStore),作为视图引用的方法:

ts 复制代码
function Counter() {
  const { count, doubled } = useStore(store)
  return (
    <div>
      <button onClick={store.increment}>Increment</button>
      <div>{count} / x2:{doubled}</div>
    </div>
  )
}

这里我们与 Pinia 会有一些差异,由于 valtio 定义与使用是分离的(这其实也是 react 框架的差异),我们则遵从框架的设计理念,将 hook 分离了出来。

插件

当然少不了插件的设计,这里就拿作为使用率最高的 persist(持久化储存)来说,我们在定义插件时也要保证精简的原则:

ts 复制代码
import type { Plugin } from 'valtio-define'
import type { PersistentOptions } from 'valtio-define'
import { subscribe } from 'valtio'

export function persist(): Plugin {
  return ({ store, options }) => {
    ...
    function watch() {
      subscribe(store.$state, () => {
        ...
      })
    }
    watch()
  }
}

declare module 'valtio-define' {
  export interface StoreDefineOptions<S extends object> {
    persist?: PersistentOptions<S> | boolean
  }
}

那么在使用的时候,我们得可以通过 store.use,去加载这个插件:

ts 复制代码
const store = defineStore({
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    },
  },
  // 通过选项或配置开启持久化
  persist: true,
})
store.use(persist())

当然,为了节省时间,也支持从全局中加载:

ts 复制代码
import valtio from 'valtio-define'

// Register the persist plugin globally
valtio.use(persist())

省流

当然啦,我怎么会忍心让你们写代码,如果你喜欢这样的方式,我已经把上面的所有功能都做成了一个项目了(valtio-define

觉得不错的点个 Star!这对我真的很有帮助,谢谢啦。

相关推荐
+VX:Fegn089522 分钟前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计
Nicander2 小时前
我给 Excalidraw 做了一个本地工作区:DrawSpace
前端·开源
linux_cfan3 小时前
17 · 引擎适配器全景:HLS/DASH/Vimeo/Mux/Cast
前端·javascript·音视频
计算机魔术师4 小时前
烧掉2780亿美元还不够?OpenAI的资本豪赌让人头皮发麻
前端
IT_陈寒5 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
卡布鲁5 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼5 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
步行cgn5 小时前
Spring p 命名空间注入详解
java·前端·spring
何何____5 小时前
Vue 生命周期详解
前端·javascript
江米小枣tonylua6 小时前
TypeScript 全面の拥抱 !Prisma 8 + Electron 升级实战
前端