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

相关推荐
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60613 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角4 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!4 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen7 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu8 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss