【 如快 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 权限。

相关推荐
靓仔建2 小时前
Vue3导入组件出错does not provide an export named ‘user_setting‘ (at index.vue:180:10)
开发语言·前端·typescript
EnoYao2 小时前
我写了一个团队体检报告 Skill,把摸鱼的同事扒出来了😅
前端·javascript
梁正雄2 小时前
Python前端-2-css练习
前端·css·python
清汤饺子2 小时前
用 Cursor 半年了,效率还是没提升?是因为你没用对这 7 个功能
前端·后端·cursor
Never_Satisfied2 小时前
在JavaScript / Node.js中,package.json文件中的依赖项自动选择最新版安装
javascript·node.js·json
蓝莓味的口香糖2 小时前
【vue3】组件的批量全局注册
前端·javascript·vue.js
wefly20173 小时前
开发者效率神器!jsontop.cn一站式工具集,覆盖开发全流程高频需求
前端·后端·python·django·flask·前端开发工具·后端开发工具
独泪了无痕3 小时前
自动导入 AutoImport:告别手动引入依赖,优化Vue3开发体验
前端·vue.js·typescript
GDAL3 小时前
MANIFEST.in简介
linux·服务器·前端·python
XPoet4 小时前
AI 编程工程化:Command——给你的 AI 员工编一套操作手册
前端·后端·ai编程