TS 入门:给 React 穿上“防弹衣”

前言

JavaScript 像是一位随性的艺术家,自由但易错;TypeScript 则是一位严谨的工程师,用类型系统为我们筑起防线。

很多新手觉得 TS 繁琐,那是还没掌握"正确姿势"。今天,我不讲枯燥理论,直接通过实战场景,带你把 TS 融入 React 的血脉。

一、组件的"身份证":Props 精准定义

在 JS 中,Props 靠"口头约定";在 TS 中,Props 必须有"身份证"。

传错参数?漏传必填项?运行时才报错?NO!

使用 interface 定义契约,利用 React.ReactNode 兼容所有内容。

typescript 复制代码
// 第一步:定义契约
interface AaaProps {
  name: string;        // 必填:必须是字符串
  age?: number;        // 可选:注意那个问号 '?'
  content: React.ReactNode; // 万能容器:字符串/JSX/Fragment 都能装
}

// 第二步:应用契约 (推荐写法)
function Aaa({ name, content }: AaaProps) {
  return <div> Hi, {name} | {content}</div>;
}

// 第三步:安全使用
export default function App() {
  // TS 会立刻拦截:如果忘记传 name,或者 content 传了数字,直接标红!
  return <Aaa name="玉米🌽" content={<span>我是内容</span>} />;
}
  • ? 的作用:明确区分"可有可无"和"必须拥有"。
  • React.ReactNode :比 any 安全,比 string 灵活,它是 React 内容的"最大公约数"。
  • 解构赋值 :直接在函数参数中解构 { name },代码更清爽,TS 依然能自动推断类型。

二、Hooks 的"导航仪":泛型让状态不再模糊

useStateuseRef 是 React 的左右手,但在 TS 中,如果不加泛型 <T>,它们就像失去了导航的船。

场景 A:状态初始化

typescript 复制代码
// 没给初始值时,TS 默认它是 undefined
const [num, setNum] = useState<number>(); 
// 类型推断:number | undefined

// 给了初始值,TS 就知道它永远是 number
const [count, setCount] = useState<number>(0); 
// 类型推断:number

场景 B:Ref 的双重身份

useRef 既能抓 DOM,也能存数据。怎么区分?看泛型!

typescript 复制代码
// 身份 1:DOM 捕手
const inputRef = useRef<HTMLInputElement>(null);
// current 可能是 HTMLInputElement 或 null

// 身份 2:数据储物柜 (不触发重渲染)
const storeRef = useRef<{ num: number }>(null);

// 安全赋值
if (storeRef.current) {
  storeRef.current.num = 2; // TS 知道这里有 num 属性
}

泛型就像一个**"模具"**。

  • 倒入 HTMLInputElement,它就是抓 Input 的夹子。
  • 倒入 { num: number },它就变成了存数据的盒子。
  • 如果不指定模具,TS 就只能给你一团模糊的橡皮泥(any 或推断错误)。

三、打通任督二脉:ForwardRef 的类型接力

父组件想操作子组件的 DOM?forwardRef 是桥梁,但 TS 需要知道这座桥通向哪里。

核心三步走

typescript 复制代码
// 定义子组件:明确 Ref 的目标是 input
const Child = forwardRef<HTMLInputElement>((props, ref) => {
  return <input ref={ref} placeholder="请聚焦我" />;
});

// 父组件声明:Ref 类型必须与子组件一致
const parentRef = useRef<HTMLInputElement>(null);

// 安全调用:使用可选链 '?.' 防止 null 报错
useEffect(() => {
  parentRef.current?.focus(); 
}, []);

如果子组件说"我要 Input",父组件却传了个 div 的 ref,TS 编译器会直接亮红灯。这种端到端的类型检查 ,彻底杜绝了 Cannot read property 'focus' of null 的低级错误。

四、性能优化的"类型护航"

当项目变大,useReducermemo 登场。TS 能让你的优化逻辑无懈可击。

状态建模:Action 联合类型

这是 TS 最强大的特性之一:判别联合类型

typescript 复制代码
// 定义状态
interface State { result: number; }

// 定义动作 (关键!限制 type 的取值)
type Action = 
  | { type: 'add'; num: number } 
  | { type: 'minus'; num: number };

// Reducer:TS 会自动根据 type 推断 action 的结构
function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'add': 
      // 这里 TS 知道 action 一定有 num
      return { result: state.result + action.num };
    case 'minus':
      return { result: state.result - action.num };
    default:
      return state;
  }
}

配合 Memo 优化

typescript 复制代码
// 缓存计算结果
const count = useMemo(() => res.result * 10, [res.result]);

// 缓存函数引用 (防止子组件无效渲染)
const cb = useCallback(() => 666, []);

// 子组件:只有 props 变了才渲染
const Child = memo(({ count }: { count: number }) => (
  <h2>计算结果:{count}</h2>
));

如果你手误写了 dispatch({ type: 'delete' ... }),TS 会立刻告诉你:"没有 delete 这个动作!"。这在 JS 中可能要等到用户点击按钮报错了才能发现。

结语

TypeScript 初看是束缚,实则是赋予你重构底气的"防弹衣"。它让你从"运行后报错"的被动救火,转向"编写时即知"的主动掌控,将潜在的隐患扼杀在编译阶段。

不必追求一步到位,渐进式地收窄每一个 any,都是在为代码大厦加固地基。当你习惯了类型系统带来的智能提示与安全边界,便真正完成了从"码农"到"工程师"的思维跃迁。

相关推荐
换日线°2 小时前
3D 旋转立方体效果(摇塞子)
前端·3d·vue
大雷神2 小时前
HarmonyOS APP<玩转React>开源教程十一:组件化开发概述
前端·react.js·harmonyos
Flutter笔记2 小时前
独立开发了一个睡眠记录 App:SleepDiary / 睡眠声音日记
前端
YimWu2 小时前
面试官:能聊聊 oh-my-opencode 这个插件都有啥内容吗?
前端·agent·ai编程
前端付豪2 小时前
AI Tutor v4:学习路径推荐(Learning Path)
前端·python·llm
爱吃的小肥羊2 小时前
等了整整一年,Midjourney V8今天终于开放测试了!
前端
玉米Yvmi2 小时前
给 JS穿上铠甲:TypeScript 基础核心概念详解(类型/接口/泛型)
前端·javascript·typescript
搞个锤子哟2 小时前
js并发请求,且限制并发请求数量实现方案
前端
米丘2 小时前
vue-router v5.x createRouter 是创建路由实例?
前端·vue.js