API
nsh/storage
nsh/storage 把应用的数据保存在设备上:以字符串为键、字符串为值,应用下次启动时数据仍在。适合保存设置、草稿、用户上次打开的标签页、服务器返回内容的缓存——任何需要比应用进程活得更久的状态。
// Storage: a launch counter kept across reloads, the stored keys and a reset.
import { mount, signal, Text, View } from "nsh";
import { Storage } from "nsh/storage";
import { Button } from "nsh/ui/mobile";
import { ui } from "./palette";
function App() {
const opened = signal<number | null>(null);
const keys = signal<string[]>([]);
const start = async () => {
const count = Number((await Storage.getItem("opened")) ?? "0") + 1;
await Storage.setItem("opened", String(count));
await Storage.setItem("lastOpened", new Date().toISOString());
opened.set(count);
keys.set(await Storage.getAllKeys());
};
const reset = async () => {
await Storage.clear();
opened.set(0);
keys.set(await Storage.getAllKeys());
};
start();
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 16, paddingTop: 24, gap: 12 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>Storage</Text>
<View style={{ borderRadius: 14, padding: 16, gap: 6, ...ui().card }}>
<Text style={{ fontSize: 28, fontWeight: "700", color: ui().text }}>
{opened() === null ? "Reading…" : `Opened ${opened()} times`}
</Text>
<Text style={{ color: ui().muted }}>Reload the page: the count is still there.</Text>
</View>
<Text style={{ color: ui().muted }}>{`Keys: ${keys().length ? keys().join(", ") : "none"}`}</Text>
<Button title="Reset" variant="secondary" onPress={reset} />
</View>
);
}
mount(App);import { Storage } from "nsh/storage";
const Storage: {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem(key: string): Promise<void>;
getAllKeys(): Promise<string[]>;
clear(): Promise<void>;
};
每个应用有自己的存储,键和值合计最多 5 MB。调用按应用发起的顺序执行,所以在 setItem 之后调用 getItem 无需等待写入就能读到新值。读取来自内存;写入在后台成批落盘。
| 平台 | 数据保存位置 |
|---|---|
| iOS | 应用容器内的 Library/Application Support/nsh/<app id>/storage.json |
| Android | 应用私有目录内的 files/nsh/<app id>/storage.json |
| macOS | ~/Library/Application Support/<app id>/storage.json |
| Windows | %APPDATA%\<app id>\storage.json |
| Linux | $XDG_DATA_HOME/<app id>/storage.json(~/.local/share/…) |
| Web | 浏览器的 localStorage,键以 app id 命名(nsh:com.example.todo:…) |
损坏的 storage.json 会被保留为同目录下的 storage.json.corrupt,应用从空存储开始;读不了的文件则让应用的改动只留在内存中,文件本身永远不会被覆盖。
app id 是 nsh.json 中的 id,它随应用一起传递:nsh build 和 nsh package 把它写在 bundle 旁边,开发服务器会发送它,签名包则带有经发布密钥证书担保的 id。因此无论应用在哪里运行,都无需任何设置就各自保存自己的数据;没有 id 的应用保存在 default 下。宿主应用可以另选位置(见在 iOS 中嵌入和在 Android 中嵌入)。
#Storage.getItem
getItem(key: string): Promise<string | null>;
返回 key 的值,没有值时返回 null。
const theme = (await Storage.getItem("theme")) ?? "system";
#Storage.setItem
setItem(key: string, value: string): Promise<void>;
把 key 设为 value,替换原有的值。如果存储会超过 5 MB,则 Promise 被拒绝,值不会被保存。值都是字符串:结构化数据用 JSON.stringify 保存,用 JSON.parse 读回。
Storage.setItem("draft", JSON.stringify({ title: title(), body: body() }));
#Storage.removeItem
removeItem(key: string): Promise<void>;
删除 key 及其值。删除一个没有值的键不做任何事。
#Storage.getAllKeys
getAllKeys(): Promise<string[]>;
按顺序返回所有键。
#Storage.clear
clear(): Promise<void>;
删除应用的所有键,例如在用户退出登录时。
#把 signal 保存在设备上
应用启动时读取保存的值,之后每当 signal 变化就写回去:
import { effect, mount, signal, Text, View } from "nsh";
import { Storage } from "nsh/storage";
const count = signal(0);
const loaded = signal(false);
Storage.getItem("count").then((saved) => {
count.set(Number(saved ?? 0));
loaded.set(true);
});
effect(() => {
if (loaded()) Storage.setItem("count", String(count()));
});
mount(() => (
<View onPress={() => count.update((n) => n + 1)}>
<Text>按了 {count()} 次</Text>
</View>
));
#在 Rust 中使用
Rust 组件通过 nsh_core::storage::Storage 读写同一份存储,它的函数与 TS 一一对应,因此同时有 TSX 入口和 Rust 入口的应用看到的是同一份数据。这些函数是同步的:数据就在内存中。
use nsh_core::storage::Storage;
let count: u32 = Storage::get_item("count").and_then(|n| n.parse().ok()).unwrap_or(0);
Storage::set_item("count", (count + 1).to_string())?;
| TS | Rust |
|---|---|
await Storage.getItem(key) | Storage::get_item(key) -> Option<String> |
await Storage.setItem(key, value) | Storage::set_item(key, value) -> Result<(), StorageFull> |
await Storage.removeItem(key) | Storage::remove_item(key) |
await Storage.getAllKeys() | Storage::get_all_keys() -> Vec<String> |
await Storage.clear() | Storage::clear() |