iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

大家好!今天要聊的主題其實是大家寫網頁、串 API 時的幾個老朋友:DOM、JSON、fetch、Promise。


1. DOM

DOM簡單來說,就是瀏覽器把 HTML 轉成樹狀結構,讓 JavaScript(JS) / TypeScript(TS) 可以去讀取、修改網頁內容。

在JS裡,常見的寫法:

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
  console.log('被點了!');
});

如果換成TS:

const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {  //Object is possibly 'null'
  console.log('被點了!');
});

因為 querySelector的回傳型別是 Element | null,TS會提醒開發者這個元素有可能不存在,不檢查一下可能會報錯。

解法通常有兩種:

1. 加上型別斷言

const btn = document.querySelector('.btn') as HTMLButtonElement;
btn.addEventListener('click', () => {
  console.log('被點了!');
});

2. 加上防呆判斷

const btn = document.querySelector<HTMLButtonElement>('.btn');
if (btn) {
  btn.addEventListener('click', () => {
    console.log('被點了!');
  });
}

小提醒:querySelector<HTMLButtonElement>()這種寫法使用了泛型,可以告訴TypeScript我預期抓到的是哪種元素。和型別斷言相比,這種寫法仍然會保留null的可能性,因此比較不容易忘記處理元素不存在的情況。

2. JSON

JSON大概是現在串API最常見的資料格式了,長得跟JS物件很像,但其實是「字串」需要轉換:

  • JSON.stringify():物件 → JSON 字串
  • JSON.parse():JSON 字串 → 物件
interface User {
  name: string;
  age: number;
}

const user: User = { name: '小明', age: 20 };

// 物件轉字串
const jsonStr = JSON.stringify(user);
console.log(jsonStr); // '{"name":"小明","age":20}'

// 字串轉物件
const parsed = JSON.parse(jsonStr);
console.log(parsed.name); // 小明

這裡要特別注意一個TS常見的錯誤:JSON.parse() 的回傳型別是 any,也就是說TS完全不會幫忙檢查裡面到底有沒有name這個屬性。所以比較嚴謹的做法,會搭配「型別斷言」或是額外寫一個檢查函式:

const parsed = JSON.parse(jsonStr) as User;

不過要記得,as User 只是「安慰自己」的寫法,實際上TS並不會真的去驗證資料格式對不對,如果後端傳回的資料不符合 User,可能要等到實際使用這些資料時才發現問題,這點要注意。

3. fetch

fetch 是瀏覽器內建的API,用來發送網路請求,取代以前XMLHttpRequest。基本用法長這樣:

fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('出錯啦:', error));

如果搭配async/await,會更好讀:

async function getUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('出錯啦:', error);
  }
}

我們通常會搭配泛型或型別斷言,讓後面的程式碼有型別檢查可以用:

interface User {
  id: number;
  name: string;
}

async function getUsers(): Promise<User[]> {
  const response = await fetch('https://api.example.com/users');
  const data = (await response.json()) as User[];
  return data;
}

這樣一來,之後在別的地方呼叫 getUsers(),編輯器就會知道回傳的是 User[],打錯屬性名稱馬上就會被抓出來。

還有一個常被忽略的小細節:fetch 只有在請求無法完成的某些情況下才會 reject,例如網路錯誤;如果伺服器回傳 404 或 500,fetch 本身並不會因此 reject。,如果伺服器回傳 404 或 500,fetch其實還是會「成功」,只是 response.ok 會是 false。所以嚴謹一點的寫法會這樣加判斷:

const response = await fetch(url);
if (!response.ok) {
  throw new Error(`HTTP 錯誤:${response.status}`);
}

4. Promise

講到fetch就一定要提Promise,因為fetch本身回傳的就是一個Promise。Promise 是JS用來處理「非同步」操作的機制。

一個 Promise 有三種狀態:

  • pending:進行中,還沒有結果
  • fulfilled:成功了,會接.then()
  • rejected:失敗了,會接.catch()
function delay(ms: number): Promise<string> {
  return new Promise((resolve, reject) => {
    if (ms < 0) {
      reject(new Error('時間不能是負數啦'));
      return;
    }
    setTimeout(() => {
      resolve(`等了 ${ms} 毫秒後完成!`);
    }, ms);
  });
}

delay(1000)
  .then(msg => console.log(msg))
  .catch(err => console.error(err));

在TS中,我們可以透過 Promise<T> 這個泛型,清楚標明「這個 Promise 成功之後會拿到什麼型別的資料」,像上面的Promise<string>就代表成功時會拿到一個字串。這對維護大型專案超級重要。

當然,現在大家更常用async/await這種寫法:

async function main() {
  const result = await delay(1000);
  console.log(result);
}

小結

這四個東西幾乎是每個前端專案都會用到的基本功,今天把它們串在一起複習一遍,感覺對之後要串真實 API 的章節會更有底氣。那我們明天見啦!


上一篇
[Day-22]Module
下一篇
[Day-24]Async與Await
系列文
從零開始學習TypeScript:菜鳥的入門介紹與概覽 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言