异步初始化自定义 Svelte 存储
Initializing a custom Svelte store asynchronously
背景
我正在尝试使用 Svelte 和 Tauri
开发跨平台桌面应用程序
当应用程序启动时,我需要将文件系统中的 settings.json 文件加载到自定义 Svelte 商店中。
它需要是自定义存储,因为我必须在写入数据之前使用自定义设置函数验证数据
商店将持有一个对象。
我使用的是常规 Svelte 而不是 Svelte-kit,因为不需要 SSR。
问题
- Tauri 在他们的 fs-api
中没有任何同步读取文件的方法
- Svelte 似乎没有任何直观的方法可以做到这一点
测试
- 按照 Svelte 的 promiseStore 示例,这适用于常规商店,但不适用于自定义商店,因为无法达到自定义设置方法
- 使用递归超时函数等待文件被读取
- 使用 while 循环等待文件被读取
- 试图找到一种在 Svelte 初始化之前将数据加载到全局变量的方法
例子
如果我要 post 所有失败的尝试,那将是很多代码,所以我将提供一个我试图实现的示例。
当 createStore 不是异步时,代码中的所有内容都有效,除了读取设置文件。
import { writable, get as getStore } from 'svelte/store'; // Svelte store
import _set from 'lodash.set'; // Creating objects with any key/path
import _merge from 'lodash.merge'; // Merging objects
import { fs } from '@tauri-apps/api'; // Accessing local filesystem
async function createStore() {
// Read settings from the file system
let settings = {}
try { settings = JSON.parse(await fs.readTextFile('./settings.json')); }
catch {}
// Create the store
const store = writable(settings);
// Custom set function
function set (key, value) {
if(!key) return;
// Use lodash to create an object
const change = _set({}, key, value);
// Retreive the current store and merge it with the object above
const currentStore = getStore(store)
const updated = _merge({}, currentStore, change)
// Update the store
store.update(() => updated)
// Save the updated settings back to the filesystem
fs.writeFile({
contents: JSON.stringify(updated, null, 2),
path: './settings.json'}
)
}
// Bundle the custom store
const customStore = {
subscribe: store.subscribe,
set
}
return customStore;
}
export default createStore();
当有一个需要异步初始化的自定义商店时,如果直接需要商店,我会通过商店上的异步方法执行此操作,我会从 App
组件调用该方法
(请注意 fs.writeFile()
也是 returns 一个 Promise。如果有错误,这还不会被处理...)
App.svelte
<script>
import settings from './settings'
import {onMount} from 'svelte'
let appInitialized
onMount(async () => {
try {
await settings.init()
appInitialized = true
}catch(error) {
console.error(error)
}
})
</script>
{#if appInitialized}
'showing App'
{:else}
'initializing App'
{/if}
只有一个商店要使用 {#await}
block
进行初始化时的替代组件逻辑
<script>
import settings from './settings'
</script>
{#await settings.init()}
'initializing store'
{:then}
'show App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
如果有更多商店要初始化,则为一个
<script>
import settings from './settings'
import store2 from './store2'
import store3 from './store3'
const initStores = [
settings.init(),
store2.init(),
store3.init()
]
</script>
{#await Promise.all(initStores)}
'initializing stores'
{:then}
'showing App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';
function createStore() {
let initialValue = {}
// destructure the store on creation to have 'direct access' to methods
const {subscribe, update, set} = writable(initialValue);
return {
subscribe,
async init() {
const savedSettings = JSON.parse(await fs.readTextFile('./settings.json'))
set(savedSettings);
},
changeSetting(key, value) {
if(!key) return;
const storeValue = get(this)
storeValue[key] = value
update(_ => storeValue)
fs.writeFile({
contents: JSON.stringify(storeValue, null, 2),
path: './settings.json'
})
}
}
}
export default createStore();
更新:我建议在 Corrl 修订后的答案中使用包装器组件方法,
但是使用 #await 块而不是 #if.
由于加载设置是应用程序启动的一部分,您可以延迟加载 Svelte 应用程序,直到加载设置。
这允许组件使用商店而不用担心加载状态:
// main.js
initSettings().then(()=> {
new App({ target: document.body })
})
// settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';
let store;
const settings = {
subscribe() {
if (!store) {
throw new Error('Not initialized')
}
return store.subscribe()
},
async changeSetting(key, value) {
if (!store) {
throw new Error('Not initialized')
}
// ... save to fs
}
}
export default settings;
export async function initSettings() {
const data = JSON.parse(await fs.readTextFile('./settings.json'))
if (store) {
store.set(data)
} else {
store = writable(data);
}
}
缺点是它会延迟应用程序的启动,如果您不在 main.js 中实现 .catch,当承诺被拒绝时,应用程序将保持空白。
背景
我正在尝试使用 Svelte 和 Tauri
开发跨平台桌面应用程序
当应用程序启动时,我需要将文件系统中的 settings.json 文件加载到自定义 Svelte 商店中。
它需要是自定义存储,因为我必须在写入数据之前使用自定义设置函数验证数据
商店将持有一个对象。
我使用的是常规 Svelte 而不是 Svelte-kit,因为不需要 SSR。
问题
- Tauri 在他们的 fs-api 中没有任何同步读取文件的方法
- Svelte 似乎没有任何直观的方法可以做到这一点
测试
- 按照 Svelte 的 promiseStore 示例,这适用于常规商店,但不适用于自定义商店,因为无法达到自定义设置方法
- 使用递归超时函数等待文件被读取
- 使用 while 循环等待文件被读取
- 试图找到一种在 Svelte 初始化之前将数据加载到全局变量的方法
例子
如果我要 post 所有失败的尝试,那将是很多代码,所以我将提供一个我试图实现的示例。
当 createStore 不是异步时,代码中的所有内容都有效,除了读取设置文件。
import { writable, get as getStore } from 'svelte/store'; // Svelte store
import _set from 'lodash.set'; // Creating objects with any key/path
import _merge from 'lodash.merge'; // Merging objects
import { fs } from '@tauri-apps/api'; // Accessing local filesystem
async function createStore() {
// Read settings from the file system
let settings = {}
try { settings = JSON.parse(await fs.readTextFile('./settings.json')); }
catch {}
// Create the store
const store = writable(settings);
// Custom set function
function set (key, value) {
if(!key) return;
// Use lodash to create an object
const change = _set({}, key, value);
// Retreive the current store and merge it with the object above
const currentStore = getStore(store)
const updated = _merge({}, currentStore, change)
// Update the store
store.update(() => updated)
// Save the updated settings back to the filesystem
fs.writeFile({
contents: JSON.stringify(updated, null, 2),
path: './settings.json'}
)
}
// Bundle the custom store
const customStore = {
subscribe: store.subscribe,
set
}
return customStore;
}
export default createStore();
当有一个需要异步初始化的自定义商店时,如果直接需要商店,我会通过商店上的异步方法执行此操作,我会从 App
组件调用该方法
(请注意 fs.writeFile()
也是 returns 一个 Promise。如果有错误,这还不会被处理...)
App.svelte
<script>
import settings from './settings'
import {onMount} from 'svelte'
let appInitialized
onMount(async () => {
try {
await settings.init()
appInitialized = true
}catch(error) {
console.error(error)
}
})
</script>
{#if appInitialized}
'showing App'
{:else}
'initializing App'
{/if}
只有一个商店要使用 {#await}
block
<script>
import settings from './settings'
</script>
{#await settings.init()}
'initializing store'
{:then}
'show App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
如果有更多商店要初始化,则为一个
<script>
import settings from './settings'
import store2 from './store2'
import store3 from './store3'
const initStores = [
settings.init(),
store2.init(),
store3.init()
]
</script>
{#await Promise.all(initStores)}
'initializing stores'
{:then}
'showing App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';
function createStore() {
let initialValue = {}
// destructure the store on creation to have 'direct access' to methods
const {subscribe, update, set} = writable(initialValue);
return {
subscribe,
async init() {
const savedSettings = JSON.parse(await fs.readTextFile('./settings.json'))
set(savedSettings);
},
changeSetting(key, value) {
if(!key) return;
const storeValue = get(this)
storeValue[key] = value
update(_ => storeValue)
fs.writeFile({
contents: JSON.stringify(storeValue, null, 2),
path: './settings.json'
})
}
}
}
export default createStore();
更新:我建议在 Corrl 修订后的答案中使用包装器组件方法,
但是使用 #await 块而不是 #if.
由于加载设置是应用程序启动的一部分,您可以延迟加载 Svelte 应用程序,直到加载设置。
这允许组件使用商店而不用担心加载状态:
// main.js
initSettings().then(()=> {
new App({ target: document.body })
})
// settings.js
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';
let store;
const settings = {
subscribe() {
if (!store) {
throw new Error('Not initialized')
}
return store.subscribe()
},
async changeSetting(key, value) {
if (!store) {
throw new Error('Not initialized')
}
// ... save to fs
}
}
export default settings;
export async function initSettings() {
const data = JSON.parse(await fs.readTextFile('./settings.json'))
if (store) {
store.set(data)
} else {
store = writable(data);
}
}
缺点是它会延迟应用程序的启动,如果您不在 main.js 中实现 .catch,当承诺被拒绝时,应用程序将保持空白。