TypeScript ក្នុងការអនុវត្តជាក់ស្តែង

មាតិកា

  1. រៀបចំផែនការបំប្លែង
  2. ការកំណត់ Type ឲ្យទិន្នន័យគម្រោង
  3. Narrowing
  4. Discriminated Union
  5. ការកំណត់ Type ឲ្យ DOM Query
  6. ការកំណត់ Type ឲ្យ fetch និង JSON
  7. ហេតុអ្វី Type មិនមែនជា Validation
  8. Build ទៅ dist/ និង Load វា
  9. Linting និង Formatting
  10. កន្លែងត្រូវទៅបន្ត
  11. សង្ខេបចុងបញ្ចប់នៃស៊េរី
  12. សាកល្បងដោយខ្លួនឯង

1. រៀបចំផែនការបំប្លែង

ទំព័ររបស់ Dara ឥឡូវមានបញ្ជីគម្រោងដែលអាចត្រងបាន, ប៊ូតុងប្តូរ theme, validator សម្រាប់ទម្រង់ទំនាក់ទំនង និងបញ្ជី repo ពី GitHub។ ទាំងអស់ជា JavaScript។ អ្នកមិនចាំបាច់សរសេរអ្វីៗឡើងវិញក្នុងពេលតែមួយទេ។ លំដាប់សុវត្ថិភាពគឺ៖

  1. បន្ថែម TypeScript និង tsconfig.json (ពី មូលដ្ឋានគ្រឹះ TypeScript)។
  2. ផ្លាស់ទី JavaScript ចូល folder src/ ហើយប្តូរឈ្មោះឯកសារពី .js ទៅ .ts។
  3. កែកំហុសដែល compiler រាយការណ៍ ម្តងមួយឯកសារ។
  4. Compile ទៅ dist/ ហើយប្តូរ index.html ឲ្យចង្អុលទៅលទ្ធផល។

Folder នឹងមើលទៅដូចនេះ៖

dara-profile/
├── index.html
├── styles.css
├── tsconfig.json
├── package.json
├── src/
│   ├── types.ts
│   ├── projects.ts
│   └── main.ts
└── dist/            (បង្កើតដោយ tsc)

ប្រើ tsconfig.json ពីជំពូកមុន៖ strict បើក, rootDir ជា src, outDir ជា dist, module ជា ES2022។

2. ការកំណត់ Type ឲ្យទិន្នន័យគម្រោង

ចាប់ផ្តើមជាមួយទិន្នន័យ ព្រោះឯកសារផ្សេងទៀតទាំងអស់ពឹងផ្អែកលើវា។ ដាក់រូបរាងក្នុង src/types.ts៖

export type Tag = "html" | "css" | "javascript";

export type Project = {
  title: string;
  year: number;
  tag: Tag;
  url?: string;
};

export type Repo = {
  name: string;
  html_url: string;
  stargazers_count: number;
  description: string | null;
};

បន្ទាប់មកប្រើវាក្នុង src/projects.ts៖

import type { Project, Tag } from "./types.js";

export const projects: Project[] = [
  { title: "Profile Page", year: 2025, tag: "html" },
  { title: "Style Guide", year: 2025, tag: "css", url: "https://example.com" },
  { title: "Quiz App", year: 2026, tag: "javascript" },
];

export function filterProjects(list: Project[], tag: Tag | "all"): Project[] {
  if (tag === "all") {
    return list;
  }
  return list.filter((project) => project.tag === tag);
}

ពត៌មានលម្អិតពីរដែលគួរកត់សម្គាល់៖

  • import type នាំចូលតែ type ប៉ុណ្ណោះ។ វាបាត់ពីលទ្ធផលដែលបាន compile។
  • Path នៃ import បញ្ចប់ដោយ .js ទោះបីឯកសារជា .ts ក៏ដោយ។ Browser នឹង load ឯកសារ .js ដែលបាន compile ហើយ TypeScript មិនសរសេរ path ឡើងវិញឲ្យអ្នកទេ។

ឥឡូវការវាយខុសដូចជា tag: "javscript" គឺជាកំហុស compile ហើយ filterProjects(projects, "js") ក៏ដូចគ្នា។

3. Narrowing

Union type ដូចជា string | number | null អនុញ្ញាតលទ្ធភាពច្រើន ប៉ុន្តែប្រតិបត្តិការនីមួយៗត្រូវការ type ជាក់លាក់មួយ។ Narrowing មានន័យថាពិនិត្យតម្លៃ ដើម្បីឲ្យ TypeScript ដឹងថាអ្នកស្ថិតក្នុងករណីណា។ នៅក្នុងការពិនិត្យនោះ type ក្លាយជាច្បាស់លាស់ជាងមុន។

function show(value: string | number | null): string {
  if (value === null) {
    return "none";
  }
  if (typeof value === "string") {
    return value.toUpperCase();   // value ជា string នៅទីនេះ
  }
  return value.toFixed(1);        // នៅសល់តែ number
}

វិធីទូទៅក្នុងការ narrow៖

ការពិនិត្យឧទាហរណ៍Narrow
typeoftypeof x === "string"Primitive type
Truthinessif (url) { ... }លុប null, undefined, "", 0
Equalityx === nullលុបតម្លៃនោះ
in"meow" in petជ្រើស object type ដែលមាន property នោះ
instanceoferror instanceof ValidationErrorinstance របស់ class

ការពិនិត្យ in ជ្រើសរើសរវាង object type៖

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function speak(pet: Cat | Dog): void {
  if ("meow" in pet) {
    pet.meow();   // pet ជា Cat
  } else {
    pet.bark();   // pet ជា Dog
  }
}

Truthiness ងាយស្រួលសម្រាប់តម្លៃស្រេចចិត្ត ប៉ុន្តែមានការព្រមានមួយ៖ 0 និង "" ក៏ជា falsy ដែរ ដូច្នេះសម្រាប់លេខ និងអក្សរ សូមពេញចិត្តការពិនិត្យច្បាស់លាស់ដូចជា value !== undefined។

function label(url?: string): string {
  if (url) {
    return url.toLowerCase();   // url ជា string នៅទីនេះ
  }
  return "no link";
}

អ្នកអាចសរសេរ function narrowing ផ្ទាល់ខ្លួនជាមួយ type predicate។ Return type value is Tag ប្រាប់ TypeScript ថាលទ្ធផល true មានន័យយ៉ាងណា៖

export function isTag(value: string): value is Tag {
  return value === "html" || value === "css" || value === "javascript";
}

4. Discriminated Union

ពេលអ្នកមានស្ថានភាពតិចតួចដែលនីមួយៗផ្ទុកទិន្នន័យខុសគ្នា សូមឲ្យស្ថានភាពនីមួយៗមាន property literal រួមគ្នា ដែលជាធម្មតាហៅថា status ឬ kind។ នេះហៅថា discriminated union។ បញ្ជី repo របស់ Dara មានបីស្ថានភាព៖

type LoadState =
  | { status: "loading" }
  | { status: "success"; repos: string[] }
  | { status: "error"; message: string };

function describe(state: LoadState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return `${state.repos.length} repos`;   // repos មាននៅទីនេះ
    case "error":
      return `Error: ${state.message}`;       // message មាននៅទីនេះ
  }
}

នៅក្នុង case នីមួយៗ TypeScript ដឹងច្បាស់ថាអ្នកមានរូបរាងណា ដូច្នេះ state.repos ត្រូវបានអនុញ្ញាតតែក្នុង "success" ប៉ុណ្ណោះ។ ប្រសិនបើក្រោយមកអ្នកបន្ថែមស្ថានភាពទីបួន ហើយភ្លេចដោះស្រាយវា compiler អាចព្រមានអ្នក ព្រោះ function នឹងមិនត្រឡប់ string គ្រប់ផ្លូវទៀតទេ។ លំនាំនេះជំនួស variable isLoading និង error ដាច់ដោយឡែកជាច្រើន ដោយតម្លៃតែមួយដែលមិនអាចស្ថិតក្នុងបន្សំដែលមិនអាចកើតមាន។

5. ការកំណត់ Type ឲ្យ DOM Query

DOM ពី DOM និង Event គឺជាកន្លែងដែល TypeScript ជួយ និងរំខានអ្នកចាប់ផ្តើមច្រើនបំផុត។ querySelector អាចរកមិនឃើញអ្វី ដូច្នេះវាត្រឡប់ Element | null៖

const input = document.querySelector("#name");
input.value;   // Error: 'input' is possibly 'null'.
               // Also: Property 'value' does not exist on type 'Element'.

មានបញ្ហាពីរ៖ វាអាចជា null ហើយ Element ទូទៅមិនមាន value។ ត្រូវកែទាំងពីរ។ ប្រាប់ TypeScript ពីប្រភេទ element ដោយ generic បន្ទាប់មកដោះស្រាយ null៖

const nameInput = document.querySelector<HTMLInputElement>("#name");

if (nameInput) {
  console.log(nameInput.value.trim());   // HTMLInputElement
}

សម្រាប់ element ដែលត្រូវតែមាន សូមឲ្យវាបរាជ័យឮៗ ដែលក៏ប្រសើរជាង bug ស្ងាត់ៗ៖

const list = document.querySelector<HTMLUListElement>("#project-list");

if (!list) {
  throw new Error("Missing #project-list in the HTML.");
}
// ពីទីនេះទៅ list ជា HTMLUListElement មិនមែន null

Element type ទូទៅ៖ HTMLInputElement, HTMLButtonElement, HTMLFormElement, HTMLUListElement។ ជាមួយ querySelectorAll អ្នកទទួលបានបញ្ជីដែលអ្នក loop៖

const filterButtons = document.querySelectorAll<HTMLButtonElement>("[data-tag]");

filterButtons.forEach((button) => {
  button.addEventListener("click", () => {
    const value = button.dataset.tag ?? "all";
    if (value === "all" || isTag(value)) {
      render(value);   // ឥឡូវ value ជា Tag | "all"
    }
  });
});

នៅទីនេះ dataset.tag ជា string | undefined ដូច្នេះយើងផ្តល់តម្លៃលំនាំដើម ហើយប្រើ isTag ដើម្បី narrow string ធម្មតាទៅជា Tag។ Optional chaining ដំណើរការដូចធម្មតាសម្រាប់ element ដែលអាចបាត់៖

document.querySelector("#theme-toggle")?.addEventListener("click", () => {
  document.documentElement.classList.toggle("dark");
});

ចៀសវាងប្រើ non-null assertion ! (ដូចក្នុង document.querySelector("#x")!) និង type cast as HTMLInputElement ជាទម្លាប់។ ទាំងពីរប្រាប់ TypeScript ឲ្យទុកចិត្តអ្នក ហើយប្រសិនបើអ្នកខុស អ្នកនឹងជួបការគាំងដូចគ្នានឹងក្នុង JavaScript។

6. ការកំណត់ Type ឲ្យ fetch និង JSON

await response.json() មាន type any ព្រោះ TypeScript មិនអាចមើលចូលក្នុង network response បាន។ គំនិតដំបូងរបស់អ្នកចាប់ផ្តើមគឺពិពណ៌នាទិន្នន័យ ហើយទុកចិត្តវា៖

async function loadRepos(user: string): Promise<Repo[]> {
  const response = await fetch(`https://api.github.com/users/${user}/repos`);
  if (!response.ok) {
    throw new Error(`GitHub returned ${response.status}`);
  }
  const data: unknown = await response.json();
  if (!Array.isArray(data)) {
    throw new Error("Unexpected response from GitHub.");
  }
  return data as Repo[];
}

សូមកត់សម្គាល់ផ្នែកនីមួយៗ៖ return type Promise<Repo[]> គឺជា generic ដែលឥឡូវអ្នកស្គាល់។ យើងរក្សាទុក JSON ជា unknown មិនមែន any ទេ ដូច្នេះ TypeScript បង្ខំឲ្យពិនិត្យ។ យើងពិនិត្យ response.ok (ពី Asynchronous JavaScript) ហើយបញ្ជាក់ថាយើងបានទទួល array មុនពេលប្រើវា។

បន្ទាត់ចុងក្រោយ data as Repo[] គឺជា type assertion៖ អ្នកសន្យាជាមួយ TypeScript ថាទិន្នន័យមានរូបរាងនោះ។ វាគឺជាចំណុចខ្សោយបំផុត ហើយផ្នែកបន្ទាប់ពន្យល់ហេតុអ្វី។

7. ហេតុអ្វី Type មិនមែនជា Validation

នេះគឺជាគំនិតសំខាន់បំផុតក្នុង TypeScript៖

Type ត្រូវបានលុបចោលពេលកូដត្រូវបាន compile។ JavaScript ដែលដំណើរការក្នុង browser មិនមាន type អ្វីទាំងអស់។

មើលអ្វីដែល tsc បង្កើតសម្រាប់បន្ទាត់ខាងលើ។ Browser ទទួលបាន JavaScript ធម្មតាដែលគ្រាន់តែ return data។ ប្រសិនបើ GitHub ផ្លាស់ប្តូរ response ថ្ងៃស្អែក ហើយ html_url បាត់ គ្មានអ្វីក្នុងទំព័រដែលកំពុងដំណើរការនឹងកត់សម្គាល់ទេ។ Type Repo របស់អ្នកនិយាយថា field មាន ប៉ុន្តែទិន្នន័យពិតនិយាយផ្ទុយ ហើយទំព័របង្ហាញ undefined ឬគាំង។

ដូច្នេះ៖

ប្រភពទិន្នន័យទុកចិត្ត type ឬទេ?
កូដ និង constant របស់អ្នកផ្ទាល់បាទ/ចាស compiler ពិនិត្យវា
Network response, JSON.parse, localStorageទេ ត្រូវពិនិត្យពេល runtime
Input របស់អ្នកប្រើពីទម្រង់ទេ ត្រូវ validate (ដូចទម្រង់ទំនាក់ទំនង)

ដើម្បីធ្វើឲ្យទិន្នន័យមានសុវត្ថិភាព សូម ពិនិត្យរូបរាងពេល runtime ហើយឲ្យការពិនិត្យនោះ narrow type។ Type predicate ធ្វើការទាំងពីរ៖

function isRepo(value: unknown): value is Repo {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value &&
    typeof value.name === "string" &&
    "html_url" in value &&
    typeof value.html_url === "string"
  );
}

បន្ទាប់មក data.filter(isRepo) រក្សាតែធាតុដែលត្រូវគ្នាពិតប្រាកដ។ សម្រាប់គម្រោងធំជាងនេះ មនុស្សប្រើ library សម្រាប់ validate ដូចជា Zod ដែលបង្កើតទាំងការពិនិត្យ runtime និង type ពីការពិពណ៌នាតែមួយ។ គោលការណ៍នៅតែដដែល៖ type ការពារកូដដែលអ្នកសរសេរ; ការពិនិត្យ runtime ការពារអ្នកពីពិភពខាងក្រៅ។

8. Build ទៅ dist/ និង Load វា

Compile គម្រោងទាំងមូល៖

npx tsc

វាអាន tsconfig.json ហើយសរសេរ dist/main.js, dist/projects.js និង dist/types.js។ បន្ថែម script ទៅ package.json ដើម្បីងាយចងចាំពាក្យបញ្ជា៖

{
  "scripts": {
    "build": "tsc",
    "watch": "tsc --watch"
  }
}

ឥឡូវ npm run build compile ម្តង ហើយ npm run watch compile ឡើងវិញរាល់ពេលអ្នករក្សាទុក។ នៅក្នុង index.html សូម load ឯកសារ ដែលបាន compile ជា module ដើម្បីឲ្យបន្ទាត់ import របស់វាដំណើរការ៖

<script type="module" src="dist/main.js"></script>

បើកទំព័រជាមួយ Live Server (module script មិន load ពីអាសយដ្ឋាន file:// ធម្មតាក្នុង browser ភាគច្រើនទេ)។ ប្រសិនបើមានអ្វីខុស DevTools នៅតែដំណើរការ៖ ពិនិត្យ Console និង tab Network។ ចងចាំថា browser ដំណើរការ dist/*.js ដូច្នេះកំហុសចង្អុលទៅឯកសារដែលបាន compile។

Commit src/ និង tsconfig.json របស់អ្នក។ ថាតើគួរ commit dist/ ឬអត់ ពឹងផ្អែកលើរបៀបដែលអ្នកផ្សព្វផ្សាយ៖ GitHub Pages បម្រើឯកសារពី repository ដូច្នេះសម្រាប់ site របស់ Dara វាគួរតែត្រូវបាន commit។ គម្រោងធំៗជាទូទៅមិនរាប់បញ្ចូល dist/ ហើយ build ពេល deploy។

9. Linting និង Formatting

ឧបករណ៍តូចពីរជួយឲ្យកូដស្អាតស្រួលរក្សា៖

  • Prettier គឺជា formatter។ វាសរសេរកូដរបស់អ្នកឡើងវិញជាមួយចន្លោះ សញ្ញាសម្រង់ និងការបំបែកបន្ទាត់ដែលស្ថិតស្ថេរ ដូច្នេះអ្នកមិនចាំបាច់ជជែកអំពីរចនាបថទេ។
  • ESLint គឺជា linter។ វារកមើលកំហុសដែលអាចកើតមាន ដែលនៅតែជាកូដត្រឹមត្រូវ ដូចជា variable ដែលមិនបានប្រើ ហើយមាន plugin សម្រាប់ TypeScript (typescript-eslint)។
npm i -D prettier eslint
npx prettier --write src

ការរៀបចំ ESLint ត្រូវការជំហានមួយចំនួន ដូច្នេះសូមធ្វើតាមការណែនាំថ្មីបំផុតរបស់វានៅ eslint.org។ VS Code ក៏អាចដំណើរការ Prettier រាល់ពេលអ្នករក្សាទុកឯកសារ។ Compiler ចាប់កំហុស type, ESLint ចាប់ទម្លាប់គួរឲ្យសង្ស័យ ហើយ Prettier គ្រប់គ្រងការរៀបចំ។ រួមគ្នា ពួកវាចាប់បញ្ហាជាច្រើនមុនពេលអ្នកបើក browser។

10. កន្លែងត្រូវទៅបន្ត

អ្នកមានមូលដ្ឋានគ្រឹះហើយ។ ជ្រើសរើសទិសដៅដែលត្រូវនឹងអ្វីដែលអ្នកចង់សាងសង់៖

គោលដៅជំហានបន្ទាប់
សាងសង់ interface អន្តរកម្មធំៗReact ជាមួយ TypeScript។ ជំនាញ component និងការកំណត់ type ឲ្យទិន្នន័យក្នុងជំពូកនេះអាចអនុវត្តបានដោយផ្ទាល់។
សាងសង់ server និង APINode.js ជាមួយ framework។ សាកល្បង NestJS Crash Course ដែលសរសេរដោយ TypeScript។
រក្សាទុក និងសាកសួរទិន្នន័យSQL និងមូលដ្ឋានទិន្នន័យដូចជា PostgreSQL
បន្តរៀនតាមលំដាប់roadmap Start Here រាយផ្លូវនីមួយៗ

ទោះជ្រើសអ្វីក៏ដោយ សូមបន្តធ្វើគម្រោងតូចមួយ។ ការអានជួយបាន ប៉ុន្តែការរៀនពិតប្រាកដកើតឡើងពេលអ្នកសាងសង់អ្វីមួយ ហើយកែអ្វីដែលខូច។

11. សង្ខេបចុងបញ្ចប់នៃស៊េរី

អ្នកបានចាប់ផ្តើមពី console.log("Hello") ហើយបញ្ចប់ដោយកម្មវិធីដែលមាន type។ តាមផ្លូវ៖

ជំពូកអ្វីដែលអ្នកបានរៀន
1-3JavaScript និង TypeScript ជាអ្វី; តម្លៃ, variable, type, operator និងលក្ខខណ្ឌ
4-6Loop, function, scope, array និង object
7Module, Node និង npm
8-9DOM, event និងកូដ asynchronous ជាមួយ fetch
10កំហុស, try/catch និងការ debug
11-12TypeScript, narrowing និងការកំណត់ type ឲ្យកូដ browser ពិតប្រាកដ

ទំព័ររបស់ Dara ឥឡូវអាចត្រងគម្រោង ប្តូរ theme validate ទម្រង់ទំនាក់ទំនង និង load repository ពី GitHub ដោយមាន compiler ពិនិត្យតក្កវិជ្ជា។ អ្នកអាចអានសារកំហុសដោយមិនភ័យ រក bug ដោយ breakpoint និងញែកភាពខុសគ្នារវាងអ្វីដែល type សន្យា និងអ្វីដែលកម្មវិធីទទួលបានពិតប្រាកដ។

12. សាកល្បងដោយខ្លួនឯង

  1. បំប្លែង filter។ ផ្លាស់ទី filter គម្រោងរបស់អ្នកចូល src/ ប្តូរឈ្មោះវាទៅ .ts ហើយកែគ្រប់កំហុសរបស់ compiler។ តើកំហុសមួយណាមានប្រយោជន៍បំផុត?
  2. បន្ថែម field។ បន្ថែម featured: boolean ទៅ type Project។ តើ compiler បង្ខំឲ្យអ្នកកែអ្វីខ្លះ? កែគ្រប់កន្លែង។
  3. Narrow union។ សរសេរ formatDate(value: string | number | Date) ដែលត្រឡប់ string ដែលអាចអានបានសម្រាប់ប្រភេទនីមួយៗ ដោយប្រើ typeof និង instanceof។
  4. បង្កើតគំរូបញ្ជី repo។ ប្រើ discriminated union LoadState សម្រាប់បញ្ជី GitHub។ បង្ហាញ "Loading..." ឈ្មោះ repo ឬសារកំហុស អាស្រ័យលើ state.status។
  5. បំបែក type ដោយចេតនា។ ឲ្យ loadRepos ចង្អុលទៅ URL ដែលត្រឡប់រូបរាងផ្សេង។ តើទំព័រធ្វើអ្វី ហើយហេតុអ្វី compiler មិនព្រមាន? បន្ទាប់មកប្រើ isRepo ដើម្បីការពារវា។
  6. Build និងផ្សព្វផ្សាយ។ ដំណើរការ npm run build load dist/main.js ជា module ក្នុង index.html ហើយសាកល្បងគ្រប់លក្ខណៈពិសេស។ ចុះផ្សាយទៅ GitHub Pages ដូចអ្វីដែលអ្នកបានធ្វើនៅចុងស៊េរី HTML & CSS។

ឥឡូវអ្នកអាចសរសេរ JavaScript បន្ថែម TypeScript ទៅវា និង debug ទាំងពីរ។ បន្តសាងសង់អ្វីដែលតូចៗនិងពិតប្រាកដ ហើយត្រឡប់មកស៊េរីនេះវិញគ្រប់ពេលដែលអ្នកត្រូវការរំលឹក។

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!