FJS26 · V50 · ONSDAG

Data utifrån

DOM:en, fetch och unknown. Kontrollera en gång, vid gränsen.

Chas Academy · Onsdag 9 december 2026


Idag

01Typa DOM:en02unknown och type guards03Gränsen04Zod05Efter passet
DATA UTIFRÅN2 / 23
”

Typerna finns bara medan du skriver koden. Det som kommer utifrån har TypeScript aldrig sett.

VAD KOMMER UTIFRÅN?


Allt som koden inte själv har skapat

Sidan

Element som kanske inte finns, eller är något annat än du tror.

Användaren

Formulär: value är alltid en sträng, även i ett nummerfält.

Nätverket

fetch: ett API kan ändras, svara med fel, eller skicka något annat.

Webbläsaren

localStorage och URL:en: vem som helst kan ha skrivit något där.

DATA UTIFRÅN4 / 23
01

Typa DOM:en

DOM


querySelector kan inte veta

Typen

const status = document.querySelector('#status');
// Element | null

status.textContent = 'Hej';
// Fel: 'status' is possibly 'null'

Kolla en gång

if (!status) {
  throw new Error('Hittade inte #status');
}
status.textContent = 'Hej'; // ok
DATA UTIFRÅN6 / 23

DOM


Tagg, id och instanceof

Tagg eller id

document.querySelector('ul');
// HTMLUListElement | null

document.querySelector('#name');
// Element | null
// Är det en input? TypeScript
// kan inte veta.

instanceof kontrollerar

const name = document
  .querySelector('#name');

if (!(name instanceof HTMLInputElement)) {
  throw new Error('Saknar #name');
}
name.value; // ok
DATA UTIFRÅN7 / 23

DOM


Ett löfte eller en kontroll?

querySelector<T>()

Du lovar TypeScript vad det är. Har någon gjort om fältet till en select märker du det först när koden kraschar.

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

instanceof

Kontrollerar när koden körs, och TypeScript narrowar efter kontrollen. Ett fel blir ett tydligt felmeddelande i stället för undefined längre fram.

DATA UTIFRÅN8 / 23

DOM


Events

TypeScript vet eventtypen

form.addEventListener('submit', (event) => {
  // event: SubmitEvent
  event.preventDefault();
});

value är alltid en sträng

priceInput.value;         // '29'
priceInput.valueAsNumber; // 29
DATA UTIFRÅN9 / 23

Övning

Typa formuläret

Gör del 1 i src/1-dom.ts. Hämta #status, menyn och formulärets fält, kontrollera dem med null-koll och instanceof, och lägg till en produkt i menyn när formuläret skickas.

Tid

15 min

Mål

Du kan hämta element från DOM:en utan as, och TypeScript vet vilken typ varje element har.

DATA UTIFRÅN10 / 23
02

unknown och
type guards

UNKNOWN


response.json() ljuger lite

any

const data = await response.json();
// data: any
data.produkter.map(...);
// inget fel nu, krasch sedan

unknown

const data: unknown = await response.json();
data.map(...);
// Fel: 'data' is of type 'unknown'
DATA UTIFRÅN12 / 23

TYPE GUARDS


En type guard bevisar typen

Hjälparen

const isRecord = (value: unknown)
  : value is Record<string, unknown> =>
  typeof value === 'object'
  && value !== null;

Guarden

const isProduct = (value: unknown)
  : value is Product =>
  isRecord(value)
  && typeof value.id === 'string'
  && typeof value.name === 'string'
  && typeof value.price === 'number'
  && isCategory(value.category);
DATA UTIFRÅN13 / 23

TYPE GUARDS


Narrowing med guarden

Ett värde

if (isProduct(data)) {
  data.price; // number
}

En lista

if (!Array.isArray(data)) {
  throw new Error('Inte en lista');
}
const products = data.filter(isProduct);
// Product[]
DATA UTIFRÅN14 / 23
03

Gränsen

Kontrollera på ett ställe.

GRÄNSEN


Där unknown blir till typer

OmvärldenfetchlocalStorageformulärURLunknownGränsenisProduct()eller ett Zod-schemafel? kasta eller hoppa överResten av appenrenderfilterräknaProduct[]Kontrollera en gång, vid gränsen. Innanför litar koden på typerna.
DATA UTIFRÅN16 / 23

GRÄNSEN


Hämta, kontrollera, visa

Gränsen

const fetchJson = async (url: string)
  : Promise<unknown> => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Fel ${response.status}`);
  }
  return response.json();
};

const parseProducts = (data: unknown)
  : Product[] => { /* ... */ };

Användningen

try {
  const products = parseProducts(
    await fetchJson('./products.json'),
  );
  showMenu(products);
} catch (error) {
  showStatus(
    error instanceof Error
      ? error.message
      : 'Något gick fel',
  );
}
DATA UTIFRÅN17 / 23
04

Zod

Samma kontroll, med ett schema.

ZOD


Ett schema i stället för en guard

Schemat

import { z } from 'zod';

const ProductSchema = z.object({
  id: z.string(),
  name: z.string(),
  price: z.number(),
  category: z.enum(['dryck', 'bröd', 'mat']),
});

type Product = z.infer<typeof ProductSchema>;

Kontrollen

const result = z.array(ProductSchema)
  .safeParse(data);

if (result.success) {
  result.data; // Product[]
} else {
  z.prettifyError(result.error);
}
DATA UTIFRÅN19 / 23

ZOD


Felen säger var

Med den trasiga filen

✖ Invalid input: expected number,
  received string
  → at [1].price
✖ Invalid input: expected string,
  received undefined
  → at [2].name
✖ Invalid option: expected one of
  "dryck"|"bröd"|"mat"
  → at [3].category

Jämför med guarden

Guarden säger bara false. Zod säger vilken post och vilket fält. Det är guld när ett API har ändrats.

DATA UTIFRÅN20 / 23

ZOD


Guard eller Zod?

Handskriven guard

Inga beroenden, och du ser exakt vad som kontrolleras. Blir lång när datan är stor, och typen och kontrollen kan glida isär.

Zod

Kort, bra felmeddelanden, och typen kommer ur schemat. Men det är ett beroende till: i code-alongen gick bundlen från 2 kB till 86 kB.

Grunden är densamma

Båda gör samma sak: tar unknown och ger typad data, vid gränsen. Förstå guarden först, så förstår du vad Zod gör åt dig.

DATA UTIFRÅN21 / 23
05

Efter passet

EFTER PASSET


Efter passet

  • Code-along: gör klart del 1–3, och kör npm run check.
  • Din egen kod: var kommer data utifrån in? Finns det något as eller any som borde vara en kontroll?
  • I morgon: den bedömda kodgranskningen på förmiddagen. Ta med laptop och se till att du är inloggad på GitHub.
  • Läs: Narrowing och DOM Manipulation i TypeScript-handboken.
DATA UTIFRÅN23 / 23