如何在React前端结合TypeScript处理多类型Journal数据并实现类型安全
如何在React前端结合TypeScript处理多类型Journal数据并实现类型安全
嘿,这个场景我太熟悉了!结合TypeScript和React处理多类型的Journal数据,核心是利用TS的类型系统做约束,同时让组件能自动识别不同类型的数据。我来一步步给你捋清楚怎么做:
1. 先搞定共享的类型定义
既然后端已经用TS定义了30种Journal的接口,最好把这些类型抽成一个共享的类型文件(比如types.ts),前后端共用。这里关键是用判别式联合类型,给每个Journal类型加一个唯一的type字段作为标识,这样TS能自动根据这个字段推断具体类型:
// types.ts - 前后端共用 // 先定义基础的公共字段 interface BaseJournal { id: string; createdAt: string; // 其他所有Journal都有的字段 } // 每个具体的Journal类型继承BaseJournal,并指定唯一的type值 interface DailyJournal extends BaseJournal { type: "daily"; // 固定值,作为判别符 dailyLog: string; moodRating: number; } interface WeeklyJournal extends BaseJournal { type: "weekly"; weeklyGoals: string[]; progressSummary: string; } // 把所有Journal类型合并成一个联合类型 type Journal = DailyJournal | WeeklyJournal | /* 剩下28种类型 */;
2. 给自定义Hook加上类型安全
你的自定义Hook负责从后端拿数据,直接让它返回我们定义好的Journal类型数组就行。这里有两个关键点:给状态指定类型,以及处理后端返回数据的类型校验:
// useJournalData.ts import { useState, useEffect } from 'react'; import { Journal } from './types'; export const useJournalData = () => { // 直接给state指定Journal[]类型,初始值是空数组 const [journals, setJournals] = useState<Journal[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchJournals = async () => { try { const res = await fetch('/api/journals'); // 这里先用类型断言告诉TS返回的是Journal数组 // 如果担心后端数据不符合类型,后面可以加运行时校验 const fetchedData = await res.json() as Journal[]; setJournals(fetchedData); } catch (err) { console.error('获取Journal数据失败:', err); } finally { setLoading(false); } }; fetchJournals(); }, []); return { journals, loading }; };
3. 让Journal组件的状态天生带类型
因为你的自定义Hook已经返回了类型化的journals,所以在Journal组件里直接用就行,不需要额外处理状态的类型——TS会自动推导:
// Journal.tsx import { useJournalData } from './useJournalData'; import DailyJournalRow from './DailyJournalRow'; import WeeklyJournalRow from './WeeklyJournalRow'; // 导入其他28种Row组件... const Journal = () => { const { journals, loading } = useJournalData(); if (loading) { return <div>加载中...</div>; } return ( <div className="journal-list"> {journals.map(journal => { // 这里TS会根据journal.type自动推断具体类型 switch (journal.type) { case "daily": return <DailyJournalRow key={journal.id} journal={journal} />; case "weekly": return <WeeklyJournalRow key={journal.id} journal={journal} />; // 依次处理剩下的28种类型... default: // 加一个兜底检查,确保所有类型都被覆盖 // 如果新增了类型但没处理,TS会直接报错 const _unhandledType: never = journal; return <div>未知的Journal类型: {journal.type}</div>; } })} </div> ); }; export default Journal;
4. 给行组件加上严格的类型约束
每个行组件只接受对应的Journal类型,这样在组件内部访问字段时完全是类型安全的,TS会自动提示可用字段,也不会出现访问不存在字段的错误:
// DailyJournalRow.tsx import { DailyJournal } from './types'; // 组件Props只接受DailyJournal类型 interface DailyJournalRowProps { journal: DailyJournal; } const DailyJournalRow = ({ journal }: DailyJournalRowProps) => { return ( <div className="journal-row"> <h4>每日日志 #{journal.id}</h4> <p>心情评分: {journal.moodRating}</p> <p>{journal.dailyLog}</p> {/* 这里访问journal的字段都是安全的,TS会阻止你访问不存在的字段 */} </div> ); }; export default DailyJournalRow;
可选:加运行时校验更稳妥
如果担心后端返回的数据不符合TS类型(比如字段缺失、类型错误),可以用zod这类库做运行时校验,同时还能自动生成TS类型:
// 用zod重构types.ts import { z } from 'zod'; const BaseJournalSchema = z.object({ id: z.string(), createdAt: z.string(), }); const DailyJournalSchema = BaseJournalSchema.extend({ type: z.literal("daily"), dailyLog: z.string(), moodRating: z.number(), }); const WeeklyJournalSchema = BaseJournalSchema.extend({ type: z.literal("weekly"), weeklyGoals: z.array(z.string()), progressSummary: z.string(), }); const JournalSchema = z.union([DailyJournalSchema, WeeklyJournalSchema]); // 自动生成TS类型 type Journal = z.infer<typeof JournalSchema>; // 在Hook里用schema解析数据 const fetchedData = JournalSchema.array().parse(await res.json());
这样如果后端返回的数据不符合定义,会直接抛出错误,避免脏数据流入组件。
备注:内容来源于stack exchange,提问作者Andrew M
相关产品推荐
相关产品推荐

