React + TypeScript 开发中的黄金法则

引言

React 和 TypeScript 是现代前端开发中最受欢迎的组合之一。React 提供了灵活的组件化开发方式,而 TypeScript 则通过静态类型检查提升了代码的可靠性和可维护性。然而,在实际开发中,如何高效地结合两者并遵循最佳实践,是每个开发者都需要面对的问题。

本文总结了 React + TypeScript 开发中的 50 条最佳实践与规范,涵盖了代码风格、类型定义、性能优化、常见问题解决方案等多个方面,帮助开发者编写高质量、可维护的代码。

React + TypeScript 开发规范与最佳实践

本文总结了 React + TypeScript 开发中的 50 条规范与经验,涵盖注释、代码风格、类型定义、性能优化等多个方面,帮助开发者编写高质量、可维护的代码。


1. 注释

1.1 文件顶部注释

  • 描述文件功能、作者和创建日期。
tsx 复制代码
// 文件功能:用户信息展示组件
// 作者:GitHub Copilot
// 日期:2023-10-01

1.2 模块注释

  • 在模块或函数前添加注释,说明功能和参数。
tsx 复制代码
/**
 * 获取用户信息
 * @param id 用户 ID
 * @returns 用户信息
 */
const getUser = (id: number) => { /* ... */ };

1.3 业务代码注释

  • 对复杂逻辑进行详细说明。
tsx 复制代码
// 检查用户是否已登录,如果未登录则跳转到登录页
if (!isLoggedIn) {
  redirectToLogin();
}

1.4 变量注释

  • 对关键变量的用途进行简要说明。
tsx 复制代码
// 用户的唯一标识符
const userId: number = 123;

2. 引用组件顺序

  • 顺序:先引入核心库(如 React),再引入第三方库,最后引入本地组件。
  • 分组:使用空行分隔不同类型的引入。
tsx 复制代码
// 核心库
import React from 'react';

// 第三方库
import { Button } from 'antd';

// 本地组件
import UserCard from './UserCard';

3. 引号

  • 统一使用单引号,避免混用。
tsx 复制代码
const message = 'Hello, React + TypeScript!';

4. 缩进

  • 使用 2 个空格缩进,保持一致性。
tsx 复制代码
const App: React.FC = () => {
  return (
    <div>
      <h1>React + TypeScript</h1>
    </div>
  );
};

5. 分号

  • 每行语句后必须加分号,避免自动插入分号的潜在问题。
tsx 复制代码
const name = 'React';
const age = 10;
console.log(`${name} is ${age} years old.`);

6. 括号

  • 代码块必须使用大括号,即使只有一行代码。
tsx 复制代码
if (age > 18) {
  console.log('Adult');
}

7. 空格

  • 操作符两侧加空格 ,如 a + b
  • 函数参数间加空格 ,如 function myFunc(a, b)
tsx 复制代码
const add = (a: number, b: number): number => {
  return a + b;
};

8. 换行

  • 超过 80 字符换行,逻辑分块换行。
tsx 复制代码
const longString =
  'This is a very long string that exceeds the recommended line length of 80 characters.';

9. 数组、对象

  • 数组 :使用 [] 定义数组,避免使用 new Array()
  • 对象:属性按逻辑分组,避免无序。
tsx 复制代码
const numbers: number[] = [1, 2, 3];

const user = {
  id: 1,
  name: 'John Doe',
  age: 30,
};

10. 命名

  • 变量/函数:使用驼峰命名法。
  • 组件:使用大驼峰命名法。
  • 常量:全大写,单词间用下划线分隔。
tsx 复制代码
const userName = 'John';

const getUserName = (): string => {
  return userName;
};

const UserCard: React.FC = () => {
  return <div>{userName}</div>;
};

11. 类型断言

  • 尽量避免 as any,优先使用明确的类型断言。
tsx 复制代码
const inputElement = document.getElementById('input') as HTMLInputElement;
inputElement.value = 'Hello';

12. interface 声明顺序

  • 先声明基础类型,再扩展类型
tsx 复制代码
interface BaseProps {
  id: number;
}

interface UserProps extends BaseProps {
  name: string;
  age: number;
}

13. TS 好用的相关工具泛型

  • Partial:将类型的所有属性变为可选。
  • Pick:从类型中选取部分属性。
  • Omit:从类型中排除部分属性。
tsx 复制代码
type PartialUser = Partial<UserProps>; // 所有属性变为可选

type UserName = Pick<UserProps, 'name'>; // 仅选取 name 属性

type UserWithoutAge = Omit<UserProps, 'age'>; // 排除 age 属性

14. TS 一些好用的小 Tips

  • 非空断言 :确保值不为 nullundefined
tsx 复制代码
const userName: string | null = 'John';
console.log(userName!); // 非空断言

15. 规范其他

  • 代码风格统一:使用 ESLint 和 Prettier。
  • 路径别名:通过

tsconfig.json

配置。

json 复制代码
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@hooks/*": ["hooks/*"]
    }
  }
}

16. State 声明

  • 仅当初始 state 需要从 props 计算得到时,才在构造函数中声明 state
  • 其他情况下使用静态属性声明 state
tsx 复制代码
class MyComponent extends React.Component {
  state = {
    count: 0,
  };
}

17. 渲染默认值

  • 为可能为空的数据提供默认值
tsx 复制代码
const UserCard: React.FC<{ name?: string }> = ({ name = 'Guest' }) => {
  return <p>姓名: {name}</p>;
};

18. 不确定的属性

  • 避免滥用 ... 展开运算符,确保属性存在。
tsx 复制代码
const props = { name: 'John', age: 30 };
const user = { ...props, gender: 'male' }; // 确保属性存在

19. 数据格式转换

  • 通过工具函数集中处理
tsx 复制代码
const formatUser = (user: UserProps): string => {
  return `${user.name}, ${user.age} years old`;
};

20. 判断条件真假

  • 避免直接使用非布尔值作为条件,明确判断真假。
tsx 复制代码
if (userName !== '') {
  console.log('Valid name');
}

21. 简单组件可以使用函数代替

  • 对于无状态组件,优先使用函数组件
tsx 复制代码
const Greeting: React.FC = () => <h1>Hello, World!</h1>;

22. 对于常用的属性进行缓存

  • 避免重复计算属性值
tsx 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

23. Input 输入框使用 trim()

  • 去除用户输入的多余空格
tsx 复制代码
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = e.target.value.trim();
  console.log(value);
};

24. 使用 location 跳转前需要先转义

  • 防止 URL 注入攻击
tsx 复制代码
const safeUrl = encodeURIComponent('/path/to/resource

');


window.location.href = safeUrl;

25. 使用 react-router

  • 推荐使用 useNavigate 替代 history.push
tsx 复制代码
const navigate = useNavigate();
navigate('/home');

26. 同时开发,数据请求 API 目录 Git 冲突方案

  • 将 API 请求模块拆分为多个文件,按功能划分
tsx 复制代码
// userApi.ts
export const getUser = () => { /* ... */ };

// productApi.ts
export const getProduct = () => { /* ... */ };

27. 组件嵌套过深

  • 通过 Context 或自定义 Hook 减少嵌套
tsx 复制代码
const UserContext = createContext<User | null>(null);

28. 代码过滤掉未考虑到的情况

  • 为所有分支提供默认处理
tsx 复制代码
switch (status) {
  case 'success':
    // ...
    break;
  case 'error':
    // ...
    break;
  default:
    console.error('Unhandled status');
}

29. 异步请求需要 try-catch

  • 捕获错误,避免程序崩溃
tsx 复制代码
const fetchData = async () => {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
};

30. setState 有三种用法

  • 直接赋值、函数式更新、合并更新
tsx 复制代码
this.setState({ count: this.state.count + 1 });
this.setState((prevState) => ({ count: prevState.count + 1 }));

31. setState 可能是同步的

  • React 的 setState 在某些情况下是异步的 (如事件处理函数中),但在其他情况下(如 setTimeout 中)可能是同步的。
  • 解决方案 :不要依赖 setState 的立即更新结果,使用回调函数获取最新状态。
tsx 复制代码
this.setState((prevState) => ({ count: prevState.count + 1 }));

32. 不要在 setState 前面加 await

  • 原因await 会导致 setState 的执行顺序发生变化,可能引发状态更新问题。
  • 解决方案:将异步逻辑与状态更新分开处理。
tsx 复制代码
const handleClick = async () => {
  const data = await fetchData();
  this.setState({ data });
};

33. 阻止事件默认行为

  • 使用 event.preventDefault() 阻止默认行为,如表单提交或链接跳转。
tsx 复制代码
const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  console.log('Form submitted');
};

34. 在 componentWillUnmount 中去除副作用

  • 在组件卸载时清理副作用,如取消订阅、清除计时器等。
tsx 复制代码
useEffect(() => {
  const timer = setInterval(() => console.log('Tick'), 1000);
  return () => clearInterval(timer); // 清理计时器
}, []);

35. key 的使用

  • key 必须唯一 ,避免使用索引作为 key,以防止列表重新渲染问题。
tsx 复制代码
const items = ['Apple', 'Banana', 'Cherry'];
items.map((item) => <li key={item}>{item}</li>);

36. for-in 中一定要有 hasOwnProperty 的判断

  • 避免直接读取原型链上的属性
tsx 复制代码
const obj = { a: 1, b: 2 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key, obj[key]);
  }
}

37. 第三方库函数的使用

  • 优先使用类型定义完善的第三方库 ,如 lodashaxios
  • 避免直接修改第三方库的返回值
tsx 复制代码
import _ from 'lodash';
const sorted = _.sortBy([3, 1, 2]);

38. 防止 XSS 攻击

  • 避免直接插入 HTML ,使用 React 的 dangerouslySetInnerHTML 时需谨慎。
tsx 复制代码
const SafeComponent: React.FC<{ content: string }> = ({ content }) => {
  return <div>{content}</div>; // 避免直接插入 HTML
};

39. 在组件中获取真实 DOM

  • 使用 useRef 获取 DOM 元素
tsx 复制代码
const inputRef = useRef<HTMLInputElement>(null);

const focusInput = () => {
  if (inputRef.current) {
    inputRef.current.focus();
  }
};

return <input ref={inputRef} />;

40. 减少魔法数字

  • 将魔法数字提取为常量,提升代码可读性。
tsx 复制代码
const MAX_RETRIES = 3;

for (let i = 0; i < MAX_RETRIES; i++) {
  console.log('Retrying...');
}

41. 如果需要优化 React 性能(一般用不到)

  • 使用 React.memouseMemouseCallback 优化性能
tsx 复制代码
const MemoizedComponent = React.memo(({ value }: { value: string }) => {
  return <p>{value}</p>;
});

42. Event 事件对象类型

  • 为事件对象指定正确的类型 ,如 React.MouseEventReact.ChangeEvent
tsx 复制代码
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
};

43. 使用私有属性取代 state 状态

  • 对于不需要触发重新渲染的状态,使用私有属性代替 state
tsx 复制代码
const MyComponent = () => {
  let privateCounter = 0;

  const increment = () => {
    privateCounter++;
    console.log(privateCounter);
  };

  return <button onClick={increment}>Increment</button>;
};

44. 代码细粒度的思考

  • 将复杂逻辑拆分为多个小函数或组件,提升代码可读性和复用性。
tsx 复制代码
const calculateTotal = (items: number[]) => items.reduce((a, b) => a + b, 0);

45. if-else 等判断太多了,后期难以维护

  • 使用对象映射或策略模式替代过多的 if-else 判断
tsx 复制代码
const actions = {
  success: () => console.log('Success'),
  error: () => console.log('Error'),
};

const handleAction = (type: string) => {
  actions[type]?.();
};

46. 不要使用 renderXXX,要使用函数式组件

  • 推荐使用函数式组件代替类组件中的 renderXXX 方法
tsx 复制代码
const RenderUser = () => <div>User Component</div>;

47. a 标签安全问题

  • 为外部链接添加 rel="noopener noreferrer",防止安全问题
tsx 复制代码
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
  External Link
</a>

48. void 0 替代 undefined

  • 使用 void 0 代替 undefined,避免被重写的风险
tsx 复制代码
const value = void 0;

  • 推荐使用第三方库(如 js-cookie)操作 cookie,避免安全问题
tsx 复制代码
import Cookies from 'js

-cookie

';

Cookies.set('token', '12345');
const token = Cookies.get('token');

50. 代码检查插件

  • 使用 ESLint 和 Prettier 统一代码风格
  • 推荐规则eslint-plugin-react@typescript-eslint.
bash 复制代码
# 安装依赖
npm install eslint prettier eslint-plugin-react @typescript-eslint/eslint-plugin --save-dev

总结

通过遵循以上规范和最佳实践,可以显著提升 React + TypeScript 项目的代码质量和开发效率。这些规范涵盖了从代码风格到性能优化的方方面面,适用于团队协作和个人开发。希望本文能为你的开发工作提供帮助!

相关推荐
大怪v4 小时前
前端佬们,装起来!给设计模式【祛魅】
前端·javascript·设计模式
sunly_4 小时前
Flutter:页面滚动,导航栏背景颜色过渡动画
开发语言·javascript·flutter
vvilkim4 小时前
Vue.js 插槽(Slot)详解:让组件更灵活、更强大
前端·javascript·vue.js
学无止境鸭4 小时前
uniapp报错 Right-hand side of ‘instanceof‘ is not an object
前端·javascript·uni-app
程序饲养员4 小时前
Javascript中export后该不该加default?
前端·javascript·前端框架
一只韩非子5 小时前
一句话告诉你什么叫编程语言自举!
前端·javascript·后端
虾球xz5 小时前
游戏引擎学习第170天
javascript·学习·游戏引擎
拉不动的猪5 小时前
首屏优化资源加载先后顺序---------以及def/async的使用
前端·javascript·面试
拉不动的猪5 小时前
小识electron
前端·javascript·html
苍曦6 小时前
中电金信25/3/18面前笔试(需求分析岗+数据开发岗)
前端·javascript·需求分析