前言
写中大型React项目不用TS,后期维护直接崩溃。 运行时一堆类型报错、父子传参乱写、事件类型模糊,改bug占用大量开发时间。
读完这篇你能学会:
React.FC函数组件完整类型定义interface约束父子组件props、自定义回调事件- React合成事件
ChangeEvent标准类型写法 useState/useEffect业务实战与副作用处理- 单向数据流标准分层写法,区分公共状态/组件私有状态
全文配套完整可运行代码,复制到Vite+TS项目直接跑,附带开发踩坑点总结。
一、为什么企业项目强制 React + TypeScript
- TS提供静态类型校验,编译阶段拦截大部分低级错误,不用等到页面运行才暴露bug
- 内置完整React类型声明:
React.FC、ReactNode、ChangeEvent,IDE自动提示 - 大型多人协作项目,props、接口、事件有明确约束,减少沟通成本
- 代码可读性拉满,不用猜参数是什么类型、回调需要传什么参数
React本身就是TS编写,官方生态对TS支持完善,是现在企业前端标准技术栈。
二、项目基础入口:Vite+TS标准main.tsx
新建项目默认入口,严格遵循React18 createRoot渲染写法,引入全局样式与根组件。
tsx
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
// !非空断言,确定页面存在root节点
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
三、核心基础:React.FC 函数组件类型详解
1. React.FC 底层原理
ts
// React源码内置类型
type FC<P = {}> = FunctionComponent<P>;
FC是FunctionComponent简写,泛型<P>用来约束组件props类型- 默认泛型值
{},无props组件可直接写React.FC - 函数组件返回值强制限定为
ReactNode,不会出现非法返回值
2. interface vs type 怎么选(开发规范)
- interface:专门约束对象、组件props,支持扩展、继承,优先用于组件参数定义
- type:类型别名,基础类型、联合类型、简单类型映射使用
四、实战分层:父子组件完整类型化案例
场景说明
页面分为三层:
- 根组件App:持有全局用户名状态,模拟异步加载后端数据
- 输入子组件NameEditComponent:私有表单状态,向外抛出修改回调
- 展示子组件Hello:接收父组件传入的名称数据,纯展示无内部状态
遵循单向数据流:父组件存公共状态,通过props传给子组件;子组件通过自定义事件回调修改父状态。
4.1 底层展示组件 Hello.tsx(纯展示组件)
tsx
// src/components/Hello.tsx
import * as react from 'react';
// interface约束组件接收的props
interface Props{
userName:string;
editingName:string;
}
// 类型别名简化书写
type PropsType = Props;
// 泛型传入Props,约束入参
const Hello:React.FC<PropsType> = (props) => {
return (
<h1 >Hello {props.userName} , 编辑中:{props.editingName}</h1>
);
}
export default Hello;
4.2 表单编辑子组件 NameEditComponent.ts(带自定义事件)
重点:自定义提交回调、输入框原生ChangeEvent完整类型约束
tsx
// src/components/NameEditComponent.ts
import * as react from 'react';
import { useState } from 'react';
// 定义子组件props:初始值 + 提交回调
interface props {
initialUserName: string;
// 自定义事件类型:接收字符串参数,无返回值
onNameSubmit:(userName:string)=>void;
}
const NameEditComponent:React.FC<props> = (props) => {
// 组件私有状态:仅当前表单使用,不需要提升到父组件
const [enditingName,setEditingName] = useState(props.initialUserName);
// React标准合成事件:HTMLInputElement输入框类型
const onChange = (event:React.ChangeEvent<HTMLInputElement>) => {
setEditingName(event.target.value);
}
// 触发父组件传入的回调,更新全局状态
const onNameSubmit = () => {
props.onNameSubmit(enditingName);
}
return (
<>
<label>更新名字:</label>
<input type="text" value={enditingName} onChange={onChange} />
<button onClick={onNameSubmit}>Change</button>
</>
);
}
export default NameEditComponent;
4.3 根组件 App.tsx(全局状态 + useEffect副作用)
核心知识点:useEffect 模拟接口异步请求、父子组件联动、状态分层
tsx
// src/App.tsx
import * as react from 'react';
import { useState, useEffect } from 'react';
import Hello from "./components/Hello";
const App:React.FC = () => {
// 全局公共状态,多个子组件共享
const [name, setName] = useState("胡文强");
const [editingName, setEditingName] = useState("defaultUserName");
// 模拟后端异步接口请求
const loadUserName = () =>{
setTimeout(() => {
setName("name from async call");
setEditingName("editing name from async call");
}, 2000);
}
// useEffect副作用:组件挂载后执行,仅执行一次
useEffect(() => {
loadUserName();
}, []);
// 接收子组件回调,更新全局用户名
const setUserNameState= ()=>{
setName(editingName);
}
return (
<>
<Hello userName={name} editingName={editingName} />
<button onClick={setUserNameState}>Change Name</button>
</>
)
}
export default App;
4.4 拓展优化版 App2.tsx(节流优化输入)
高频输入场景增加节流防抖,防止频繁更新渲染,引入lodash节流函数
tsx
// src/App2.tsx
import * as react from 'react';
import { throttle } from 'lodash-es';
import { useState } from 'react';
import NameEditComponent from "./components/NameEditComponent";
const now_age=18;
const App:React.FC = () => {
const [username, setUsername] = useState("胡文强");
// 节流处理输入,5000ms仅触发一次更新,减少渲染开销
// const setUsernameState = throttle((event:React.ChangeEvent<HTMLInputElement>) => {
// setUsername(event.target.value);
// }, 5000);
return (
<div>
<NameEditComponent initialUserName={username} onNameSubmit={setUsername} />
</div>
);
}
export default App;
五、核心知识点拆解
5.1 单向数据流设计规范
- 公共共享状态:统一放在父组件App,通过props向下传递给所有子组件
- 子组件私有状态 :仅当前组件使用(如输入框临时编辑值),使用
useState内部维护 - 修改父状态规则:子组件不能直接修改父props,通过父传入的回调函数触发更新
5.2 React合成事件类型
React.ChangeEvent<HTMLInputElement>
- React封装的统一事件对象,替代原生
event - 泛型指定触发元素类型:
HTMLInputElement/HTMLButtonElement等 - 自动提示
event.target.value,TS不会报类型不存在
5.3 useEffect副作用使用场景
- 组件挂载后请求接口、初始化数据、定时器、订阅事件都属于副作用
- 依赖数组为空
[]:仅组件首次挂载执行一次 - 页面优先渲染静态内容,后台异步加载数据,提升用户感知速度
六、开发高频踩坑提醒
- props不写interface 无类型约束,IDE无提示,传参写错编译不会报错,上线全是bug。
- 事件不使用React内置ChangeEvent 直接写
(e)=>{}丢失类型,访问e.target报类型错误。 - 私有状态提升到父组件 大量冗余状态流转,组件耦合严重,维护成本翻倍。
- useEffect依赖数组漏写/乱写 依赖缺失会出现无限循环渲染,依赖过多频繁重复执行请求。
- 输入框无节流优化 实时输入高频触发setState,页面频繁重渲染,低端设备卡顿。
七、全文总结
React.FC<PropsInterface>是企业TS函数组件标准写法,用interface约束props入参;- 遵循单向数据流:父存公共状态,子通过回调修改上层数据;
- 表单输入统一使用
React.ChangeEvent<HTMLInputElement>规范事件类型; - 异步接口、初始化逻辑全部放入
useEffect副作用钩子; - 区分组件私有状态和全局共享状态,降低组件耦合;
- 高频输入场景搭配节流函数,优化页面渲染性能。