【 如快 Tauri 2 实践 】结合 pinia 实现本地文件存储响应式

开发 Tauri 程序的时候,有时候需要在本地存储一些数据,并希望借助 pinia 管理以实现响应式,则可以参考本篇文章。

项目源码可前往 sofast-tauri2-practice 查看。

useStorageAsync

vueuse 中有一个特别实用的函数 useStorageAsync,可以很方便地将浏览器的 LocalStorage 交给 pinia 管理以实现响应式。

同时,它还提供了一个 StorageLikeAsync 接口,用于实现自定义的存储,而 Tauri 则可以通过实现这个接口来实现响应式本地存储。

typescript 复制代码
interface StorageLikeAsync {
  getItem: (key: string) => Awaitable<string | null>;
  setItem: (key: string, value: string) => Awaitable<void>;
  removeItem: (key: string) => Awaitable<void>;
}

如果需要序列化,需要实现 SerializerAsync 接口:

typescript 复制代码
interface SerializerAsync<T> {
  read: (raw: string) => Awaitable<T>;
  write: (value: T) => Awaitable<string>;
}

tauri-fs

Tauri2 需要用到 fs 插件来管理本地文件系统,需要先添加 fs 插件:

bash 复制代码
pnpm tauri add fs

同时,几乎所有插件的权限在前端默认都是禁止的,需要手动在 src-tauri/capabilities目录下的权限 json 文件中开启我们需要用到的权限:

json 复制代码
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "opener:default",

   "fs:default",
    "fs:allow-write-text-file",
    "fs:allow-read-text-file",
    "fs:allow-exists",
    "fs:allow-remove",
    "fs:allow-mkdir",
    {
      "identifier": "fs:scope",
      "allow": [
        "$APPDATA",
        "$APPDATA/*"
      ]
    }
  ]
}

实现

接下来只需要实现一下 StorageLikeAsync 接口以及 SerializerAsync 接口即可:

typescript 复制代码
class JsonLocalStorage implements StorageLikeAsync {
  getItem(key: string): Awaitable<string | null> {
    return new Promise(async resolve => {
      const path = key + '.json';
      // 不存在直接返回 null
      // 如果返回的是 null,useStorageAsync 默认会调用一次 setItem
      if (!(await exists(path, {baseDir: BaseDirectory.AppData}))) {
        resolve(null)
        return
      }
      resolve(await readTextFile(path, {baseDir: BaseDirectory.AppData}))
    })
  }

  removeItem(key: string): Awaitable<void> {
    return remove(key + '.json', {baseDir: BaseDirectory.AppData})
  }

  setItem(key: string, value: string): Awaitable<void> {
    return writeTextFile(key + '.json', value, {baseDir: BaseDirectory.AppData})
  }
}

class JsonSerializer implements SerializerAsync<object> {
  read(raw: string): Awaitable<object> {
    return JSON.parse(raw);
  }

  write(value: object): Awaitable<string> {
    return JSON.stringify(value);
  }
}

然后再稍微封装一下:

typescript 复制代码
const jsonLocalStorage = new JsonLocalStorage()
const jsonSerializer = new JsonSerializer()

export const useJsonLocalStorage = <T>(key: string, initialValue: MaybeRefOrGetter<T>) => {
  return useStorageAsync<T>(key, initialValue, jsonLocalStorage, {serializer: jsonSerializer as any})
}

使用

接下来,我们只需要在 pinia 中使用即可:

typescript 复制代码
import {defineStore} from "pinia";
import {useJsonLocalStorage} from "./storage.ts";

export const useStore = defineStore('main', () => {
  const state = useJsonLocalStorage<{
    count: number
  }>('state', {count: 0})

  return {
    state
  }
})

示例

接下来就可以写个简单的示例测试一下了:

typescript 复制代码
<template>
  <div>
    count is {{ store.state.count }}
    <a @click="handlerAdd" href="">add</a>
  </div>
</template>

<script setup lang="ts">
import {useStore} from "../store/index.store.ts";

const store = useStore()
const handlerAdd = (e: MouseEvent) => {
  e.preventDefault()
  store.state.count++
}
</script>

退出程序重新打开,依然是 2:

避坑

权限问题

Tauri 2 对于权限要求非常严格且细致,在前端使用某个 Tauri 插件的 api 的时候,一定要检查是否开启相关权限,可以前往官方文档或者对应仓库下面的 permissions/autogenerated/reference.md文件查看相关权限以及说明,如 fs可前往:

github.com/tauri-apps/...

同时,fs插件有个比较重要的权限是 scope,配置允许访问的文件有哪些,默认是空的,如果不配置,就算你开启了各种读写权限,依然无法正常读写,所以可以像上面那样配置一个 scope

json 复制代码
{
  "identifier": "fs:scope",
  "allow": [
    "$APPDATA",
    "$APPDATA/*"
  ]
}

这里配置 $APPDATA$APPDATA 两个后面会讲到。

写文件需要目录存在

Tauri 在写文件的时候,需要文件所在的目录是存在的,否则就会报错。

具体表现为 writeTextFile 或者 writeFile 的时候,如果传入的 path 不存在,则会报错。

而我们这里用到了 {baseDir: BaseDirectory.AppData} 这个选项,所以,需要在写文件的时候先确保 $APPDATA 这个目录是存在的,否则就会报错。

这边我选择的是在软件启动的时候判断是否存在并创建它:

typescript 复制代码
onMounted(() => {
  exists("", {baseDir: BaseDirectory.AppData}).then(exist => {
    if (!exist) {
      mkdir("", {baseDir: BaseDirectory.AppData})
    }
  })
})

值得注意的是,由于 mkdir 传入的是空串,也就是创建 $APPDATA 这个目录,所以需要确保 $APPDATA 这个目录是有权限的,于是需要配置 $APPDATAscope 权限。

相关推荐
今禾3 分钟前
" 当Base64遇上Blob,图像转换不再神秘,让你的网页瞬间变身魔法画布! "
前端·数据可视化
华科云商xiao徐7 分钟前
高性能小型爬虫语言与代码示例
前端·爬虫
十盒半价8 分钟前
深入理解 React useEffect:从基础到实战的全攻略
前端·react.js·trae
攀登的牵牛花8 分钟前
Electron+Vue+Python全栈项目打包实战指南
前端·electron·全栈
iccb10139 分钟前
我是如何实现在线客服系统的极致稳定性与安全性的
前端·javascript·后端
一大树10 分钟前
Vue3祖孙组件通信方法总结
前端·vue.js
不要进入那温驯的良夜11 分钟前
跨平台UI自动化-Appium
前端
海底火旺11 分钟前
以一个简单的React应用理解数据绑定的重要性
前端·css·react.js
不要进入那温驯的良夜12 分钟前
浏览器技术原理
前端
在泡泡里12 分钟前
前端 mcp 的使用
前端