在 TypeScript 中使用 Vue3 Context Hook

51 阅读2分钟

1. 创建类型化的 Context Hook

首先创建 useContext.ts 文件:

import { provide, inject, reactive, readonly, InjectionKey } from 'vue';

/**
 * 创建类型化的 Vue Context
 * @param contextName 上下文名称(用于调试)
 * @param defaultValue 默认值
 * @returns [Provider组件, useContext Hook]
 */
```import { provide, inject, reactive, readonly, type VNode } from 'vue';
import type { InjectionKey, DefineComponent, PropType } from 'vue'

export function createContext<T extends object>(contextName: string, defaultValue: T) {
  const ContextSymbol = Symbol(contextName) as InjectionKey<T>;

  const Provider: DefineComponent<{
    value: T;
  }> = {
    name: `${contextName}Provider`,
    props: {
      value: {
        type: Object as PropType<T>,
        required: true,
      },
    },
    setup(props: { value: T }, { slots }: { slots: { default?: () => VNode[] | VNode } }) {
      // 添加泛型约束,确保 T 是对象类型
      const state = reactive<T>(props.value);
      provide(ContextSymbol, readonly(state));
      return () => slots.default?.();
    },
  };

  const useContext = (): T => {
    const context = inject(ContextSymbol);
    if (!context) {
      console.warn(`Context ${contextName} not found`);
      return defaultValue;
    }
    return context;
  };

  return [Provider, useContext] as const;
}
## 2. 创建具体的类型化 Context
创建 `useCountContext.ts` 定义具体的 Context 类型:

```// src/hooks/useCountContext.ts
import { createContext } from './useContext';

// 定义 Context 类型
interface CountContextType {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset?: (value: number) => void;
}

// 默认值
const defaultValue: CountContextType = {
  count: 0,
  increment: () => {},
  decrement: () => {},
  reset: (value: number) => {},
};

// 创建类型化的 CountContext
const [CountProvider, useCount] = createContext<CountContextType>('CountContext', defaultValue);

export { CountProvider, useCount };

3. 在组件中使用 (Provider)

<script setup lang="ts">
import { ref } from 'vue';
import { CountProvider } from '@/hooks/useCountContext';
import ChildComponent from './ChildComponent.vue';

const count = ref(0);

// 确保符合 CountContextType 类型
const contextValue = {
  count,
  increment: () => count.value++,
  decrement: () => count.value--,
  reset: (value: number) => {
    count.value = value;
  },
};
</script>

<template>
  <div class="parent">
    <h2>父组件 (Provider)</h2>
    <p>当前计数: {{ count }}</p>
    
    <CountProvider :value="contextValue">
      <ChildComponent />
    </CountProvider>
  </div>
</template>

4. 在组件中使用 (Consumer)

<script setup lang="ts">
import { useCount } from '@/hooks/useCountContext';

// 自动推断出 CountContextType 类型
const { count, increment, decrement, reset } = useCount();

const handleReset = () => {
  reset?.(0); // 使用可选链,因为 reset 在默认值中是可选的
};
</script>

<template>
  <div class="child">
    <h3>子组件 (Consumer)</h3>
    <p>从 Context 获取的计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <button @click="handleReset">重置</button>
  </div>
</template>

5. 嵌套 Context 示例

<script setup lang="ts">
import { ref } from 'vue';
import { CountProvider } from '@/hooks/useCountContext';
import ChildComponent from './ChildComponent.vue';

// 父级 Context
const parentCount = ref(0);
const parentContext = {
  count: parentCount,
  increment: () => parentCount.value++,
  decrement: () => parentCount.value--,
  reset: (value: number) => {
    parentCount.value = value;
  },
};

// 子级 Context (覆盖部分方法)
const childCount = ref(100);
const childContext = {
  count: childCount,
  increment: () => (childCount.value += 5), // 不同的实现
  decrement: () => (childCount.value -= 5),
  // 不提供 reset 方法
};
</script>

<template>
  <div class="nested-example">
    <h2>嵌套 Context 示例</h2>
    
    <CountProvider :value="parentContext">
      <ChildComponent />
      
      <CountProvider :value="childContext">
        <ChildComponent />
      </CountProvider>
    </CountProvider>
  </div>
</template>

6. 高级用法 - 带参数的 Context

import { createContext } from './useContext';

interface User {
  id: string;
  name: string;
  email: string;
}

interface AuthContextType {
  user: User | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const defaultValue: AuthContextType = {
  user: null,
  isAuthenticated: false,
  login: async () => {},
  logout: () => {},
};

const [AuthProvider, useAuth] = createContext<AuthContextType>('AuthContext', defaultValue);

export { AuthProvider, useAuth };

进阶技巧(Auth Context 的典型实现示例):

const useAuthProvider = () => {
  const user = ref<User | null>(null);
  const isAuthenticated = computed(() => !!user.value);

  const login = async (email: string, password: string) => {
    const res = await api.login(email, password); // 实际API调用
    user.value = res.data.user;
  };

  const logout = () => {
    await api.logout();
    user.value = null;
  };

  return { 
    user: readonly(user), // 防止直接修改
    isAuthenticated,
    login,
    logout
  };
};

// 使用时
const auth = useAuthProvider();
<AuthProvider :value="auth">