TypeScript学习-第12章:与主流框架结合

TypeScript学习-第12章:与主流框架结合

上一章咱们把tsconfig.json玩得明明白白,TS编译规则总算尽在掌握。可一进入React/Vue项目,又瞬间破防:组件Props传参随心所欲,类型错误藏到运行时才暴露;Hooks状态乱改,不知道该标注什么类型;框架API和TS类型打架,最后只能用any"摆烂"......其实TS和主流框架本是"黄金搭档",前者给后者的灵活性套上"类型安全锁",后者让TS的类型能力落地到真实业务。今天咱们就手把手搞定TS与React、Vue3的结合,从此在框架项目中告别any,写出又稳又优雅的代码。

一、TypeScript + React:给组件加"类型身份证"

React的核心是组件和Hooks,TS与React结合的关键,就是给Props、状态、事件、Hooks都加上精准类型,让组件的"输入输出"和内部逻辑都有迹可循。咱们从核心场景逐个突破。

1. 组件Props类型约束:拒绝"乱传参"

Props是组件的"输入参数",用TS给Props定义类型,就像给组件贴了"身份证",传错参数直接编译报错,再也不用靠console.log调试传参问题。核心是用接口(interface)定义Props结构,支持可选属性、默认值等场景。

tsx 复制代码
// 1. 基础Props类型:必选+可选属性
import React from 'react';

// 定义Props接口
interface UserCardProps {
  name: string; // 必选属性
  age: number;  // 必选属性
  avatar?: string; // 可选属性(用?标记)
  tags: string[]; // 数组类型
}

// 函数组件接收Props,标注类型
const UserCard: React.FC<UserCardProps> = (props) => {
  const { name, age, avatar = '/default-avatar.png', tags } = props;
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}({age}岁)</h3>
      <div className="tags">
        {tags.map(tag => <span key={tag}>{tag}</span>)}
      </div>
    </div>
  );
};

// 使用组件:传参错误会直接报错
<UserCard name="张三" age={25} tags={['前端', 'TS']} /> // 合法
<UserCard name="李四" age="25" tags={['前端']} /> // 报错:age应为number类型

小提示:React.FC(FunctionComponent)是TS提供的函数组件类型,会自动包含children属性;若组件不需要children,也可直接标注函数参数类型(如 (props: UserCardProps) => JSX.Element),更灵活。

2. 事件处理类型:精准标注事件对象

React事件处理是高频场景,直接用any标注事件对象会丢失类型提示,TS提供了对应事件类型(如React.MouseEvent、React.ChangeEvent),精准标注事件源和事件属性。

tsx 复制代码
const InputDemo: React.FC = () => {
  // 输入框change事件:标注为ChangeEvent<HTMLInputElement>
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log('输入内容:', e.target.value); // 自动提示target、value属性
  };

  // 按钮点击事件:标注为MouseEvent<HTMLButtonElement>
  const handleBtnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault(); // 事件对象方法自动提示
    console.log('按钮被点击');
  };

  return (
    <div>
      <input type="text" onChange={handleInputChange} />
      <button onClick={handleBtnClick}>点击我</button>
    </div>
  );
};

核心规律:React事件类型格式为XXXEvent<事件源元素类型>,常见事件类型:ChangeEvent(输入变化)、MouseEvent(鼠标操作)、FormEvent(表单提交)。

3. Hooks类型标注:让状态和副作用有约束

React Hooks与TS结合时,大部分场景TS能自动推导类型,复杂场景需手动标注,核心覆盖useState、useRef、useEffect三大常用Hook。

tsx 复制代码
const HooksDemo: React.FC = () => {
  // 1. useState:简单类型自动推导,复杂类型可手动标注
  const [count, setCount] = React.useState(0); // 自动推导count为number
  const [user, setUser] = React.useState<{ name: string; age: number } | null>(null); // 手动标注联合类型

  // 2. useRef:分两种场景标注(DOM元素/普通值)
  // DOM元素场景:标注为HTMLDivElement,初始值为null
  const boxRef = React.useRef<HTMLDivElement>(null);
  // 普通值场景:标注为number,初始值为0
  const timerRef = React.useRef<number>(0);

  // 3. useEffect:依赖项类型由外部状态推导,无需额外标注
  React.useEffect(() => {
    timerRef.current = window.setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);

    // 操作DOM:需先判断非空(strict模式下必做)
    if (boxRef.current) {
      boxRef.current.style.color = 'red';
    }

    return () => clearInterval(timerRef.current);
  }, []);

  return <div ref={boxRef}>计数:{count}</div>;
};

二、TypeScript + Vue3:组合式API的类型魔法

Vue3的组合式API(Composition API)天生对TS友好,<script setup lang="ts"> 语法可直接开启TS支持,核心是给ref、reactive、Props、Emits等标注类型,让组合逻辑更安全。

1. 组合式API类型标注:ref/reactive/computed

ref用于基础类型状态,reactive用于复杂对象状态,computed依赖状态推导类型,TS能自动适配大部分场景,复杂场景需手动标注泛型。

vue 复制代码
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';

// 1. ref:基础类型自动推导,复杂类型标注泛型
const count = ref(0); // 自动推导为Ref<number>
const username = ref<string | null>(null); // 手动标注联合类型

// 2. reactive:传入对象自动推导类型,无需额外标注
interface User {
  name: string;
  age: number;
  hobbies: string[];
}
const user = reactive<User>({
  name: '张三',
  age: 25,
  hobbies: ['前端', 'TS']
});

// 3. computed:根据依赖状态自动推导返回类型
const fullName = computed(() => {
  return `姓名:${user.name}`; // 自动推导为ComputedRef<string>
});

// 状态操作:类型错误直接报错
count.value = '123'; // 报错:应为number类型
user.hobbies.push(123); // 报错:hobbies元素应为string类型
</script>

<template>
  <div>
    <p>计数:{{ count }}</p>
    <p>用户名:{{ username }}</p>
    <p>完整姓名:{{ fullName }}</p>
  </div>
</template>

核心提醒:ref包裹的状态需通过 .value 访问,TS会自动提示该属性;reactive包裹的对象直接访问属性,类型推导更直观。

2. 组件Props与Emits类型约束

Vue3中用defineProps定义Props,defineEmits定义事件,结合TS泛型可精准标注Props类型和事件参数类型,替代传统的props/emits选项。

vue 复制代码
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

// 1. 定义Props类型:用泛型标注接口
interface CardProps {
  title: string;
  desc?: string; // 可选属性
  size: 'small' | 'middle' | 'large'; // 联合类型约束
}
const props = defineProps<CardProps>();

// 2. 定义Emits类型:标注事件名和参数类型
const emits = defineEmits<{
  (e: 'click', id: number): void; // 事件名click,参数为number
  (e: 'change', value: string): void; // 事件名change,参数为string
}>();

// 触发事件:参数类型错误报错
const handleClick = () => {
  emits('click', 123); // 合法
  emits('click', '123'); // 报错:参数应为number类型
};
</script>

<template>
  <div class={`card card-${size}`}>
    <h3>{{ title }}</h3>
    <p v-if="desc">{{ desc }}</p>
    <button @click="handleClick">触发事件</button>
  </div>
</template>

3. Pinia类型封装:状态管理的类型安全

Pinia是Vue3官方推荐的状态管理库,天生对TS友好,无需额外类型标注即可获得完整类型提示,核心是定义Store时传入状态类型,自动推导Actions和Getters类型。

ts 复制代码
// store/userStore.ts
import { defineStore } from 'pinia';

// 定义状态类型
interface UserState {
  name: string;
  age: number;
  isLogin: boolean;
}

// 定义Store:自动推导状态、Actions类型
export const useUserStore = defineStore('user', {
  // 状态:标注为UserState类型
  state: (): UserState => ({
    name: '张三',
    age: 25,
    isLogin: false
  }),
  // Getters:自动推导返回类型
  getters: {
    fullName: (state) => `用户:${state.name}`, // 推导为string
    isAdult: (state) => state.age >= 18 // 推导为boolean
  },
  // Actions:参数和返回值自动推导类型
  actions: {
    login(name: string, age: number) {
      this.name = name;
      this.age = age;
      this.isLogin = true;
    },
    logout() {
      this.isLogin = false;
    }
  }
});

// 组件中使用:完整类型提示
<script setup lang="ts">
import { useUserStore } from '@/store/userStore';
const userStore = useUserStore();

userStore.login('李四', 28); // 自动提示参数类型
console.log(userStore.fullName); // 自动提示Getters属性
</script>

三、实战:用TS写一个React TodoItem组件

咱们选React框架,写一个完整的TodoItem组件,整合Props、状态、事件处理的类型标注,实现"添加-删除"功能,展示TS在真实组件中的落地。

tsx 复制代码
// components/TodoItem.tsx
import React, { useState } from 'react';

// 1. 定义Todo类型接口
export interface Todo {
  id: number;
  content: string;
  completed: boolean;
}

// 2. 定义组件Props类型
interface TodoItemProps {
  todo: Todo;
  onDelete: (id: number) => void; // 删除事件:参数为todo的id
  onToggle: (id: number) => void; // 切换完成状态事件
}

// 3. 函数组件:标注Props类型,无多余children用普通函数标注
const TodoItem = (props: TodoItemProps): React.ReactElement => {
  const { todo, onDelete, onToggle } = props;
  // 本地状态:输入框编辑内容
  const [editContent, setEditContent] = useState(todo.content);

  // 输入框变化事件:标注ChangeEvent
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setEditContent(e.target.value);
  };

  // 切换完成状态事件
  const handleToggle = () => {
    onToggle(todo.id);
  };

  // 删除事件
  const handleDelete = () => {
    onDelete(todo.id);
  };

  return (
    <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={handleToggle}
      />
      <input
        type="text"
        value={editContent}
        onChange={handleInputChange}
        style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
      />
      <button onClick={handleDelete} style={{ marginLeft: '10px' }}>
        删除
      </button>
    </div>
  );
};

export default TodoItem;

// 页面中使用组件
// pages/TodoList.tsx
import React, { useState } from 'react';
import TodoItem, { Todo } from '../components/TodoItem';

const TodoList: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([
    { id: 1, content: '学习TS与React结合', completed: false },
    { id: 2, content: '写一个Todo组件', completed: true }
  ]);

  // 删除Todo
  const handleDelete = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  // 切换Todo完成状态
  const handleToggle = (id: number) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  return (
    <div className="todo-list">
      <h2>TS + React TodoList</h2>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onDelete={handleDelete}
          onToggle={handleToggle}
        />
      ))}
    </div>
  );
};

export default TodoList;

实战亮点:组件拆分清晰,Todo类型全局复用,Props和事件参数都有精准类型约束,传参错误、状态修改错误都会在编译时暴露,大幅降低运行时风险。

四、避坑指南与深度总结

  • React避免滥用React.FC:React.FC会强制添加children属性,若组件不需要children,直接标注函数参数类型更简洁,避免冗余类型。

  • Vue3 ref与reactive选型:基础类型(string/number)用ref,复杂对象用reactive;ref需通过.value访问,TS会自动提示,无需刻意记忆。

  • 事件类型不要用any:React和Vue都提供了内置事件类型,按需查找对应类型(如React.FormEvent、Vue的Event),既能获得提示又能保证安全。

  • 框架类型声明优先用官方包:React/Vue/Pinia的类型声明已内置或在@types包中,无需手动编写,安装依赖后自动获得类型提示。

  • 严格模式贯穿始终:框架项目的tsconfig.json务必开启strict: true,否则TS的类型校验会打折扣,很多潜在问题无法暴露。

最后总结:TS与主流框架结合的核心,不是"多写类型标注",而是"用类型精准覆盖框架的核心场景"------React的Props、Hooks、事件,Vue3的组合式API、Store,都通过类型把"模糊的约定"变成"明确的约束"。这样既能享受框架的灵活性,又能借助TS的类型安全,让大型项目的开发、维护更高效。至此,咱们的TS学习也从基础语法落地到了框架实战,从此可以自信地用TS开发各类前端项目了!

相关推荐
萧曵 丶5 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
今天只学一颗糖5 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
testpassportcn6 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
Amumu121386 小时前
Vue3扩展(二)
前端·javascript·vue.js
NEXT066 小时前
JavaScript进阶:深度剖析函数柯里化及其在面试中的底层逻辑
前端·javascript·面试
牛奶8 小时前
你不知道的 JS(上):原型与行为委托
前端·javascript·编译原理
牛奶8 小时前
你不知道的JS(上):this指向与对象基础
前端·javascript·编译原理
牛奶8 小时前
你不知道的JS(上):作用域与闭包
前端·javascript·电子书
游乐码9 小时前
c#变长关键字和参数默认值
学习·c#
pas1369 小时前
45-mini-vue 实现代码生成三种联合类型
前端·javascript·vue.js