TypeScript支持 #

一、TypeScript 配置 #

1.1 基础配置 #

json
// tsconfig.json
{
    "compilerOptions": {
        "target": "ES2020",
        "useDefineForClassFields": true,
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "skipLibCheck": true,
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx",
        "strict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true,
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"]
        }
    },
    "include": ["src"],
    "references": [{ "path": "./tsconfig.node.json" }]
}

1.2 类型声明文件 #

typescript
// src/types/tauri.d.ts
declare module '@tauri-apps/api/core' {
    export function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
}

declare module '@tauri-apps/api/event' {
    export interface Event<T> {
        event: string;
        id: number;
        payload: T;
        windowLabel: string;
    }

    export function listen<T>(
        event: string,
        handler: (event: Event<T>) => void
    ): Promise<() => void>;

    export function emit(event: string, payload?: unknown): Promise<void>;

    export function emitTo(
        target: string,
        event: string,
        payload?: unknown
    ): Promise<void>;
}

二、命令类型定义 #

2.1 定义命令类型映射 #

typescript
// src/types/commands.ts
export interface CommandMap {
    'get_user': {
        params: { id: number };
        result: User;
    };
    'create_user': {
        params: { name: string; email: string };
        result: User;
    };
    'delete_user': {
        params: { id: number };
        result: void;
    };
    'get_config': {
        params: { key: string };
        result: string | null;
    };
    'set_config': {
        params: { key: string; value: string };
        result: void;
    };
}

export interface User {
    id: number;
    name: string;
    email: string;
    createdAt: string;
}

2.2 类型安全的 invoke 函数 #

typescript
// src/utils/invoke.ts
import { invoke as tauriInvoke } from '@tauri-apps/api/core';
import type { CommandMap } from '../types/commands';

type CommandKey = keyof CommandMap;

export async function invoke<K extends CommandKey>(
    cmd: K,
    args: CommandMap[K]['params']
): Promise<CommandMap[K]['result']> {
    return tauriInvoke(cmd, args);
}

// 可选参数版本
export async function invokeOptional<K extends CommandKey>(
    cmd: K,
    args?: Partial<CommandMap[K]['params']>
): Promise<CommandMap[K]['result']> {
    return tauriInvoke(cmd, args);
}

2.3 使用类型安全的 invoke #

typescript
import { invoke } from './utils/invoke';

// 类型安全的调用
const user = await invoke('get_user', { id: 1 });
// user 的类型自动推断为 User

// 错误:参数类型不匹配
// await invoke('get_user', { id: 'string' }); // 编译错误

三、事件类型定义 #

3.1 定义事件类型映射 #

typescript
// src/types/events.ts
export interface EventMap {
    'user:login': { userId: string; token: string };
    'user:logout': void;
    'config:update': { key: string; value: unknown };
    'notification:show': { title: string; message: string; type: 'info' | 'error' };
    'file:changed': { path: string; content: string };
}

3.2 类型安全的事件函数 #

typescript
// src/utils/events.ts
import { listen as tauriListen, emit as tauriEmit, emitTo as tauriEmitTo } from '@tauri-apps/api/event';
import type { EventMap } from '../types/events';

type EventKey = keyof EventMap;

export async function listen<K extends EventKey>(
    event: K,
    handler: (payload: EventMap[K]) => void
): Promise<() => void> {
    return tauriListen<EventMap[K]>(event, (e) => {
        handler(e.payload);
    });
}

export async function emit<K extends EventKey>(
    event: K,
    payload: EventMap[K]
): Promise<void> {
    return tauriEmit(event, payload);
}

export async function emitTo<K extends EventKey>(
    target: string,
    event: K,
    payload: EventMap[K]
): Promise<void> {
    return tauriEmitTo(target, event, payload);
}

3.3 使用类型安全的事件 #

typescript
import { listen, emit } from './utils/events';

// 发送事件
await emit('user:login', { userId: '123', token: 'abc' });

// 监听事件
const unlisten = await listen('user:login', (payload) => {
    // payload 类型自动推断为 { userId: string; token: string }
    console.log(payload.userId, payload.token);
});

四、Rust 与 TypeScript 类型同步 #

4.1 Rust 结构体 #

rust
// src-tauri/src/models/user.rs
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: u32,
    pub name: String,
    pub email: String,
    pub created_at: String,
}

4.2 TypeScript 类型生成 #

使用 ts-rs crate 自动生成 TypeScript 类型:

toml
# Cargo.toml
[dependencies]
ts-rs = "7"
rust
// src-tauri/src/models/user.rs
use serde::{Deserialize, Serialize};
use ts_rs::TS;

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct User {
    pub id: u32,
    pub name: String,
    pub email: String,
    pub created_at: String,
}

生成的 TypeScript 类型:

typescript
// generated/User.ts
export interface User {
    id: number;
    name: string;
    email: string;
    created_at: string;
}

4.3 构建脚本 #

rust
// src-tauri/build.rs
fn main() {
    tauri_build::build();
    
    // 生成 TypeScript 类型
    println!("cargo:rerun-if-changed=src/models");
}

五、API 客户端封装 #

5.1 创建 API 客户端 #

typescript
// src/api/client.ts
import { invoke } from '../utils/invoke';

export const api = {
    user: {
        async get(id: number) {
            return invoke('get_user', { id });
        },

        async create(name: string, email: string) {
            return invoke('create_user', { name, email });
        },

        async delete(id: number) {
            return invoke('delete_user', { id });
        },
    },

    config: {
        async get(key: string) {
            return invoke('get_config', { key });
        },

        async set(key: string, value: string) {
            return invoke('set_config', { key, value });
        },
    },
};

5.2 使用 API 客户端 #

typescript
import { api } from './api/client';

// 获取用户
const user = await api.user.get(1);

// 创建用户
const newUser = await api.user.create('Alice', 'alice@example.com');

// 删除用户
await api.user.delete(1);

六、React Hooks 类型 #

6.1 类型安全的 Hook #

typescript
// src/hooks/useCommand.ts
import { useState, useCallback } from 'react';
import { invoke as tauriInvoke } from '@tauri-apps/api/core';
import type { CommandMap } from '../types/commands';

type CommandKey = keyof CommandMap;

interface UseCommandResult<K extends CommandKey> {
    data: CommandMap[K]['result'] | null;
    loading: boolean;
    error: string | null;
    execute: (params: CommandMap[K]['params']) => Promise<CommandMap[K]['result']>;
}

export function useCommand<K extends CommandKey>(cmd: K): UseCommandResult<K> {
    const [data, setData] = useState<CommandMap[K]['result'] | null>(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const execute = useCallback(
        async (params: CommandMap[K]['params']) => {
            setLoading(true);
            setError(null);

            try {
                const result = await tauriInvoke<CommandMap[K]['result']>(cmd, params);
                setData(result);
                return result;
            } catch (err) {
                setError(String(err));
                throw err;
            } finally {
                setLoading(false);
            }
        },
        [cmd]
    );

    return { data, loading, error, execute };
}

6.2 使用 Hook #

tsx
import { useCommand } from '../hooks/useCommand';

function UserProfile() {
    const { data: user, loading, error, execute } = useCommand('get_user');

    useEffect(() => {
        execute({ id: 1 });
    }, []);

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error}</div>;
    if (!user) return null;

    return (
        <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
        </div>
    );
}

七、类型守卫 #

7.1 类型守卫函数 #

typescript
// src/utils/guards.ts
import type { User } from '../types/commands';

export function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'id' in value &&
        'name' in value &&
        'email' in value &&
        typeof (value as User).id === 'number' &&
        typeof (value as User).name === 'string' &&
        typeof (value as User).email === 'string'
    );
}

export function isUserArray(value: unknown): value is User[] {
    return Array.isArray(value) && value.every(isUser);
}

7.2 使用类型守卫 #

typescript
import { isUser } from '../utils/guards';

const result = await invoke('get_user', { id: 1 });

if (isUser(result)) {
    console.log(result.name); // 类型安全
} else {
    console.error('Invalid user data');
}

八、错误类型 #

8.1 定义错误类型 #

typescript
// src/types/errors.ts
export interface AppError {
    code: string;
    message: string;
    details?: Record<string, unknown>;
}

export class CommandError extends Error {
    code: string;
    details?: Record<string, unknown>;

    constructor(error: AppError) {
        super(error.message);
        this.code = error.code;
        this.details = error.details;
        this.name = 'CommandError';
    }
}

export function isAppError(value: unknown): value is AppError {
    return (
        typeof value === 'object' &&
        value !== null &&
        'code' in value &&
        'message' in value
    );
}

8.2 错误处理 #

typescript
import { CommandError, isAppError } from '../types/errors';

async function safeInvoke<T>(cmd: string, args: unknown): Promise<T> {
    try {
        return await invoke<T>(cmd, args);
    } catch (error) {
        if (isAppError(error)) {
            throw new CommandError(error);
        }
        throw new Error(String(error));
    }
}

九、最佳实践 #

9.1 类型组织 #

text
src/types/
├── commands.ts    # 命令类型
├── events.ts      # 事件类型
├── models.ts      # 数据模型
├── errors.ts      # 错误类型
└── index.ts       # 统一导出

9.2 类型复用 #

typescript
// src/types/models.ts
export interface BaseEntity {
    id: number;
    createdAt: string;
    updatedAt: string;
}

export interface User extends BaseEntity {
    name: string;
    email: string;
}

export interface Post extends BaseEntity {
    title: string;
    content: string;
    authorId: number;
}

9.3 泛型工具 #

typescript
// src/utils/types.ts
export type AsyncResult<T> = {
    data: T | null;
    loading: boolean;
    error: string | null;
};

export type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

export type PickByType<T, U> = {
    [P in keyof T as T[P] extends U ? P : never]: T[P];
};

十、总结 #

10.1 核心要点 #

要点 说明
类型定义 定义命令和事件类型映射
类型安全 封装类型安全的 invoke
类型同步 Rust 和 TypeScript 类型同步
类型守卫 运行时类型检查
错误处理 定义错误类型

10.2 下一步 #

现在你已经掌握了 TypeScript 支持,接下来让我们学习 本地存储,了解如何在 Tauri 应用中存储数据!

最后更新:2026-03-28