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

相关推荐
Swing_wingS20 分钟前
SpringMvc解决跨域问题的源码汇总。
前端
乌龟的黑头-阿尔及利亚22 分钟前
使用 Vite 创建 Vue 3 项目:从零开始的详细指南
前端·javascript·vue.js
三天不学习34 分钟前
what?ngify 比 axios 更好用,更强大?
前端·axios·请求响应·ngify
2403_875180951 小时前
一键掌握多平台短视频矩阵营销/源码部署
java·前端·数据结构·线性代数·矩阵·php
Best_卡卡1 小时前
前端性能-HTTP缓存
前端·http·缓存
GIS好难学2 小时前
《Openlayers零基础教程》第六课:地图控件
前端·javascript·零基础·openlayers
桃园码工3 小时前
3_CSS3 渐变 --[CSS3 进阶之路]
前端·css·css3·css3 渐变
NoneCoder4 小时前
JavaScript系列(22)--模块化进阶
开发语言·javascript·ecmascript
成长中的向日葵4 小时前
element-ui dialog弹窗 设置点击空白处不关闭
前端·vue.js
ihengshuai4 小时前
Gitlab流水线配置
前端·docker·gitlab·devops