前言 :
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 的"导航仪":泛型让状态不再模糊
useState 和 useRef 是 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的低级错误。
四、性能优化的"类型护航"
当项目变大,useReducer、memo 登场。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,都是在为代码大厦加固地基。当你习惯了类型系统带来的智能提示与安全边界,便真正完成了从"码农"到"工程师"的思维跃迁。