ไทย

สำรวจแนวทางปฏิบัติที่ดีที่สุดในการใช้ TypeScript กับ React เพื่อสร้างเว็บแอปพลิเคชันที่แข็งแกร่ง ขยายขนาดได้ และบำรุงรักษาง่าย เรียนรู้เกี่ยวกับโครงสร้างโปรเจกต์ การออกแบบคอมโพเนนต์ การทดสอบ และการเพิ่มประสิทธิภาพ

TypeScript กับ React: แนวทางปฏิบัติที่ดีที่สุดสำหรับแอปพลิเคชันที่ขยายขนาดได้และบำรุงรักษาง่าย

TypeScript และ React เป็นการผสมผสานที่ทรงพลังสำหรับการสร้างเว็บแอปพลิเคชันสมัยใหม่ TypeScript นำการกำหนดชนิดข้อมูลแบบสแตติก (static typing) มาสู่ JavaScript ช่วยปรับปรุงคุณภาพของโค้ดและความสามารถในการบำรุงรักษา ในขณะที่ React นำเสนอแนวทางการสร้างส่วนติดต่อผู้ใช้ (user interface) แบบประกาศ (declarative) และแบบคอมโพเนนต์ (component-based) บล็อกโพสต์นี้จะสำรวจแนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ TypeScript กับ React เพื่อสร้างแอปพลิเคชันที่แข็งแกร่ง ขยายขนาดได้ และบำรุงรักษาง่าย เหมาะสำหรับผู้ชมทั่วโลก

ทำไมต้องใช้ TypeScript กับ React?

ก่อนที่จะลงลึกถึงแนวทางปฏิบัติที่ดีที่สุด เรามาทำความเข้าใจกันก่อนว่าทำไม TypeScript ถึงเป็นส่วนเสริมที่มีคุณค่าต่อการพัฒนา React:

การตั้งค่าโปรเจกต์ TypeScript React

การใช้ Create React App

วิธีที่ง่ายที่สุดในการเริ่มโปรเจกต์ TypeScript React ใหม่คือการใช้ Create React App พร้อมกับเทมเพลต TypeScript:

npx create-react-app my-typescript-react-app --template typescript

คำสั่งนี้จะตั้งค่าโปรเจกต์ React พื้นฐานพร้อมกับการกำหนดค่า TypeScript รวมถึง dependencies ที่จำเป็นและไฟล์ tsconfig.json

การกำหนดค่า tsconfig.json

ไฟล์ tsconfig.json คือหัวใจสำคัญของการกำหนดค่า TypeScript ของคุณ นี่คือการตั้งค่าที่แนะนำบางส่วน:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

ตัวเลือกสำคัญที่ควรพิจารณา:

แนวทางปฏิบัติที่ดีที่สุดสำหรับ React Components ด้วย TypeScript

การกำหนดไทป์ (Typing) ให้กับ Props ของคอมโพเนนต์

หนึ่งในแง่มุมที่สำคัญที่สุดของการใช้ TypeScript กับ React คือการกำหนดไทป์ให้กับ props ของคอมโพเนนต์อย่างเหมาะสม ใช้อินเทอร์เฟซ (interface) หรือ type alias เพื่อกำหนดรูปร่างของอ็อบเจกต์ props

interface MyComponentProps {
  name: string;
  age?: number; // prop ที่ไม่บังคับ
  onClick: () => void;
}

const MyComponent: React.FC = ({ name, age, onClick }) => {
  return (
    

สวัสดี, {name}!

{age &&

คุณอายุ {age} ปี

}
); };

การใช้ React.FC<MyComponentProps> ทำให้มั่นใจได้ว่าคอมโพเนนต์เป็น functional component และ props ถูกกำหนดไทป์อย่างถูกต้อง

การกำหนดไทป์ให้กับ State ของคอมโพเนนต์

หากคุณใช้ class components คุณจะต้องกำหนดไทป์ให้กับ state ของคอมโพเนนต์ด้วย กำหนดอินเทอร์เฟซหรือ type alias สำหรับอ็อบเจกต์ state และใช้ในการนิยามคอมโพเนนต์

interface MyComponentState {
  count: number;
}

class MyComponent extends React.Component<{}, MyComponentState> {
  state: MyComponentState = {
    count: 0
  };

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  render() {
    return (
      

จำนวน: {this.state.count}

); } }

สำหรับ functional components ที่ใช้ useState hook, TypeScript มักจะสามารถอนุมานไทป์ของตัวแปร state ได้ แต่คุณก็สามารถระบุไทป์อย่างชัดเจนได้เช่นกัน:

import React, { useState } from 'react';

const MyComponent: React.FC = () => {
  const [count, setCount] = useState(0);

  return (
    

จำนวน: {count}

); };

การใช้ Type Guards

Type guards คือฟังก์ชันที่จำกัดขอบเขตไทป์ของตัวแปรให้แคบลงภายในขอบเขตที่กำหนด มีประโยชน์เมื่อต้องจัดการกับ union types หรือเมื่อคุณต้องการให้แน่ใจว่าตัวแปรมีไทป์ที่เฉพาะเจาะจงก่อนที่จะดำเนินการใดๆ

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  side: number;
}

type Shape = Circle | Square;

function isCircle(shape: Shape): shape is Circle {
  return shape.kind === "circle";
}

function getArea(shape: Shape): number {
  if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2;
  } else {
    return shape.side ** 2;
  }
}

ฟังก์ชัน isCircle เป็น type guard ที่ตรวจสอบว่า Shape เป็น Circle หรือไม่ ภายในบล็อก if, TypeScript จะรู้ว่า shape เป็น Circle และอนุญาตให้คุณเข้าถึง property radius ของมันได้

การจัดการ Events

เมื่อจัดการกับ events ใน React ด้วย TypeScript สิ่งสำคัญคือต้องกำหนดไทป์ของอ็อบเจกต์ event ให้ถูกต้อง ใช้ event type ที่เหมาะสมจาก namespace ของ React

const MyComponent: React.FC = () => {
  const handleChange = (event: React.ChangeEvent) => {
    console.log(event.target.value);
  };

  return (
    
  );
};

ในตัวอย่างนี้ React.ChangeEvent<HTMLInputElement> ถูกใช้เพื่อกำหนดไทป์ให้กับอ็อบเจกต์ event สำหรับ change event บน input element ซึ่งทำให้สามารถเข้าถึง property target ซึ่งเป็น HTMLInputElement ได้

โครงสร้างโปรเจกต์

โปรเจกต์ที่มีโครงสร้างที่ดีมีความสำคัญอย่างยิ่งต่อการบำรุงรักษาและการขยายขนาด นี่คือโครงสร้างโปรเจกต์ที่แนะนำสำหรับแอปพลิเคชัน TypeScript React:

src/
├── components/
│   ├── MyComponent/
│   │   ├── MyComponent.tsx
│   │   ├── MyComponent.module.css
│   │   └── index.ts
├── pages/
│   ├── HomePage.tsx
│   └── AboutPage.tsx
├── services/
│   ├── api.ts
│   └── auth.ts
├── types/
│   ├── index.ts
│   └── models.ts
├── utils/
│   ├── helpers.ts
│   └── constants.ts
├── App.tsx
├── index.tsx
├── react-app-env.d.ts
└── tsconfig.json

ประเด็นสำคัญ:

การใช้ Hooks กับ TypeScript

React Hooks ช่วยให้คุณสามารถใช้ state และฟีเจอร์อื่นๆ ของ React ใน functional components ได้ TypeScript ทำงานร่วมกับ Hooks ได้อย่างราบรื่น ให้ความปลอดภัยของไทป์ (type safety) และประสบการณ์การพัฒนาที่ดีขึ้น

useState

ดังที่แสดงไว้ก่อนหน้านี้ คุณสามารถกำหนดไทป์ของตัวแปร state อย่างชัดเจนเมื่อใช้ useState:

import React, { useState } from 'react';

const MyComponent: React.FC = () => {
  const [count, setCount] = useState(0);

  return (
    

จำนวน: {count}

); };

useEffect

เมื่อใช้ useEffect ให้ระวัง dependency array TypeScript สามารถช่วยคุณจับข้อผิดพลาดได้หากคุณลืมใส่ dependency ที่ถูกใช้ภายใน effect

import React, { useState, useEffect } from 'react';

const MyComponent: React.FC = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `จำนวน: ${count}`;
  }, [count]); // เพิ่ม 'count' เข้าไปใน dependency array

  return (
    

จำนวน: {count}

); };

หากคุณละเว้น count จาก dependency array, effect จะทำงานเพียงครั้งเดียวเมื่อคอมโพเนนต์ mount และชื่อเรื่องของเอกสาร (document title) จะไม่อัปเดตเมื่อค่า count เปลี่ยนไป TypeScript จะเตือนคุณเกี่ยวกับปัญหานี้

useContext

เมื่อใช้ useContext คุณต้องระบุไทป์สำหรับค่าของ context

import React, { createContext, useContext } from 'react';

interface ThemeContextType {
  theme: string;
  toggleTheme: () => void;
}

const ThemeContext = createContext(undefined);

const ThemeProvider: React.FC = ({ children }) => {
  // เขียนโลจิกของธีมที่นี่
  return (
     {} }}>
      {children}
    
  );
};

const MyComponent: React.FC = () => {
  const { theme, toggleTheme } = useContext(ThemeContext) as ThemeContextType;

  return (
    

ธีม: {theme}

); }; export { ThemeProvider, MyComponent };

โดยการระบุไทป์สำหรับค่าของ context คุณจะมั่นใจได้ว่า useContext hook จะคืนค่าที่มีไทป์ที่ถูกต้อง

การทดสอบคอมโพเนนต์ TypeScript React

การทดสอบเป็นส่วนสำคัญของการสร้างแอปพลิเคชันที่แข็งแกร่ง TypeScript ช่วยเสริมการทดสอบโดยให้ความปลอดภัยของไทป์และปรับปรุงความครอบคลุมของโค้ด (code coverage)

การทดสอบระดับยูนิต (Unit Testing)

ใช้ testing framework เช่น Jest และ React Testing Library เพื่อทดสอบคอมโพเนนต์ของคุณในระดับยูนิต

// MyComponent.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';

describe('MyComponent', () => {
  it('renders the component with the correct name', () => {
    render();
    expect(screen.getByText('สวัสดี, John!')).toBeInTheDocument();
  });

  it('calls the onClick handler when the button is clicked', () => {
    const onClick = jest.fn();
    render();
    fireEvent.click(screen.getByRole('button'));
    expect(onClick).toHaveBeenCalledTimes(1);
  });
});

การตรวจสอบไทป์ของ TypeScript ช่วยจับข้อผิดพลาดในโค้ดทดสอบของคุณ เช่น การส่ง props ที่ไม่ถูกต้อง หรือการใช้ event handler ที่ผิดประเภท

การทดสอบแบบบูรณาการ (Integration Testing)

การทดสอบแบบบูรณาการจะตรวจสอบว่าส่วนต่างๆ ของแอปพลิเคชันทำงานร่วมกันอย่างถูกต้อง ใช้เครื่องมือเช่น Cypress หรือ Playwright สำหรับการทดสอบแบบ end-to-end

การเพิ่มประสิทธิภาพ (Performance Optimization)

TypeScript ยังสามารถช่วยในการเพิ่มประสิทธิภาพได้โดยการตรวจจับปัญหาคอขวดที่อาจเกิดขึ้นได้ตั้งแต่เนิ่นๆ ในกระบวนการพัฒนา

Memoization

ใช้ React.memo เพื่อ memoize functional components และป้องกันการ re-render ที่ไม่จำเป็น

import React from 'react';

interface MyComponentProps {
  name: string;
}

const MyComponent: React.FC = ({ name }) => {
  console.log('กำลังเรนเดอร์ MyComponent');
  return (
    

สวัสดี, {name}!

); }; export default React.memo(MyComponent);

React.memo จะ re-render คอมโพเนนต์ก็ต่อเมื่อ props มีการเปลี่ยนแปลงเท่านั้น ซึ่งสามารถปรับปรุงประสิทธิภาพได้อย่างมาก โดยเฉพาะสำหรับคอมโพเนนต์ที่ซับซ้อน

การแบ่งโค้ด (Code Splitting)

ใช้ dynamic imports เพื่อแบ่งโค้ดของคุณออกเป็นส่วนเล็กๆ และโหลดเมื่อต้องการ ซึ่งสามารถลดเวลาในการโหลดเริ่มต้นของแอปพลิเคชันของคุณได้

import React, { Suspense } from 'react';

const MyComponent = React.lazy(() => import('./MyComponent'));

const App: React.FC = () => {
  return (
    กำลังโหลด...
}> ); };

React.lazy ช่วยให้คุณสามารถ import คอมโพเนนต์แบบไดนามิก ซึ่งจะถูกโหลดเมื่อจำเป็นเท่านั้น คอมโพเนนต์ Suspense จะแสดง UI สำรองในขณะที่คอมโพเนนต์กำลังโหลด

สรุป

การใช้ TypeScript กับ React สามารถปรับปรุงคุณภาพ ความสามารถในการบำรุงรักษา และการขยายขนาดของเว็บแอปพลิเคชันของคุณได้อย่างมาก โดยการปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเหล่านี้ คุณสามารถใช้ประโยชน์จากพลังของ TypeScript เพื่อสร้างแอปพลิเคชันที่แข็งแกร่งและมีประสิทธิภาพ ซึ่งตอบสนองความต้องการของผู้ชมทั่วโลก อย่าลืมให้ความสำคัญกับการกำหนดไทป์ที่ชัดเจน การจัดโครงสร้างโปรเจกต์ที่ดี และการทดสอบอย่างละเอียดเพื่อให้แน่ใจว่าโปรเจกต์ของคุณจะประสบความสำเร็จในระยะยาว

แหล่งข้อมูลเพิ่มเติม