0Pricing
React Academy · 课时

为 Context 与自定义钩子定义类型

使用默认值创建类型化 Context,并为自定义钩子编写显式返回类型。

为 Context 与自定义钩子定义类型 是 CoddyKit 上的免费 React Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。

使用 createContext 创建类型化上下文

将上下文类型和默认值传递给 createContext。当上下文必须在提供者内部使用时,请将 null 作为默认值,并通过守卫处理它。

interface AuthContextType {
  user: User | null;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

守卫钩子模式

创建一个读取上下文的自定义钩子,并在钩子于提供者外部使用时抛出错误。这样可以避免在每个调用位置检查 null。

export function useAuth(): AuthContextType {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
}

为提供者指定类型

提供者组件会包装子元素并提供上下文值。请明确指定值对象的类型,让 TypeScript 验证它是否与上下文类型匹配。

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (creds: Credentials) => {
    const u = await apiLogin(creds);
    setUser(u);
  };

  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

带默认值的上下文

当存在合理的默认值(例如主题)时,请直接将其传递给 createContext,这样上下文永远不会为 null,也不需要守卫钩子。

interface ThemeContextType { theme: 'light' | 'dark'; toggle: () => void; }

const ThemeContext = createContext<ThemeContextType>({
  theme: 'light',
  toggle: () => {},
});

export const useTheme = () => useContext(ThemeContext);

为自定义钩子的返回值指定类型

请明确标注自定义钩子的返回类型,或者让 TypeScript 自动推断。明确的类型可以充当文档,并防止结构意外发生变化。

interface UseCounterReturn {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

function useCounter(initial = 0): UseCounterReturn {
  const [count, setCount] = useState(initial);
  return {
    count,
    increment: () => setCount(c => c + 1),
    decrement: () => setCount(c => c - 1),
    reset: () => setCount(initial),
  };
}

元组返回类型

返回一对值(如 useState)的钩子使用元组。请使用 as const 或明确的元组类型,以便正确推断解构结果。

function useToggle(initial = false): [boolean, () => void] {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue(v => !v), []);
  return [value, toggle];
}

// Destructuring correctly infers types:
const [isOpen, toggleOpen] = useToggle();

泛型自定义钩子

自定义钩子也可以使用泛型。泛型 useLocalStorage 钩子能够以完整的类型安全性存储任意可序列化类型。

function useLocalStorage<T>(key: string, initialValue: T): [T, (val: T) => void] {
  const [stored, setStored] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch { return initialValue; }
  });

  const setValue = (val: T) => {
    setStored(val);
    localStorage.setItem(key, JSON.stringify(val));
  };

  return [stored, setValue];
}

为 useReducer 指定类型

将操作类型定义为可辨识联合,并将状态结构定义为接口。TypeScript 会在 reducer 中强制执行有效的操作类型。

interface State { count: number; status: 'idle' | 'loading'; }
type Action =
  | { type: 'INCREMENT' }
  | { type: 'SET_STATUS'; payload: State['status'] };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'INCREMENT': return { ...state, count: state.count + 1 };
    case 'SET_STATUS': return { ...state, status: action.payload };
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, status: 'idle' });

为 useCallback 与 useMemo 指定类型

TypeScript 会根据工厂函数为 useCallback 和 useMemo 推断类型。只有在推断失败时才需要明确标注。

const handleChange = useCallback(
  (e: React.ChangeEvent<HTMLInputElement>) => {
    onChange(e.target.value);
  },
  [onChange]
);

const sorted = useMemo(
  () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
  [items]
); // inferred as typeof items

带类型的上下文选择器模式

类型化的选择器钩子接收一个选择器函数,并只返回所需的数据切片,同时保留完整的类型推断。

function useAuthSelector<T>(selector: (ctx: AuthContextType) => T): T {
  const ctx = useAuth();
  return selector(ctx);
}

// Usage — TypeScript infers User | null:
const user = useAuthSelector(ctx => ctx.user);

避免在钩子中使用 any

请不要在自定义钩子中使用 any。请使用泛型、结合类型守卫的 unknown,或具体接口,以保持类型链完整。

// Bad:
function useFetch(url: string): { data: any; loading: boolean } { ... }

// Good:
function useFetch<T>(url: string): { data: T | null; loading: boolean } {
  const [data, setData] = useState<T | null>(null);
  // ...
  return { data, loading };
}

快速检查

为 React 上下文指定类型时,守卫钩子模式的用途是什么?

回顾

使用明确的接口和检查 null 的钩子为上下文指定类型。使用接口或元组类型标注自定义钩子的返回类型,为可复用钩子使用泛型,并使用可辨识联合操作为 useReducer 指定类型,从而在编译时发现无效的分发操作。

常见问题解答

「为 Context 与自定义钩子定义类型」课时是免费的吗?

是的 — 「为 Context 与自定义钩子定义类型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。

「为 Context 与自定义钩子定义类型」这节课中我会学到什么?

使用默认值创建类型化 Context,并为自定义钩子编写显式返回类型。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「为 Context 与自定义钩子定义类型」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Academy 课中编写并运行代码吗?

能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为属性与组件返回值定义类型
  2. 在 TypeScript 中为事件与引用定义类型
  3. 泛型组件与实用工具类型
  4. 为 Context 与自定义钩子定义类型
← 返回 React Academy