什么?我居然在 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!这对我真的很有帮助,谢谢啦。

相关推荐
Mh6 小时前
如何使用GSAP实现一个 `pinned` 滚动楼层叙事?
前端·javascript·css
用户9385156350715 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
CodeSheep15 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员
鱼樱前端15 小时前
用 AI 做内容变收入
前端·人工智能·ai编程
浮生望16 小时前
React + TypeScript 组件设计进阶:从类型约束到无状态组件的三次进化
前端
90后的晨仔16 小时前
Mac 开发 uni-app 终极方案:让安卓模拟器彻底脱离 Android Studio 独立运行
前端·vue.js
90后的晨仔16 小时前
别再搞混了!uni-app 中 node_modules 和 uni_modules 到底是什么关系?
前端
kyriewen17 小时前
面试官问"这段逻辑为什么这样写"——我才发现,这半年我写的代码,我自己都解释不了
前端·javascript·面试
谢小飞17 小时前
大文件上传很难?学会这招,GB文件秒传不是梦
前端·node.js
程序员黑豆18 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程