Maksimer brukbarheit og haldbarheit med utleding av komponent-APIer i ReactJS og TypeScript

Hallo bloggen, da var eg på TypeScript kjøret igjen. I dette innlegget så vil eg dele ein ganske snasen løysing på komponering av komponenter bak eit utleda (eng. innferred), typesikkert komponent-API.

eit veldig lite rullebrett, også kallt fingerbrett

Dette fingerbrettet eg plukket opp i ferien har ingenting med TS å gjera, men det er ganske gøy!

Utfordringa #

På oppdrag for Meteorologisk Institutt så støtte eg på ein interessant utfordring og det var å forena to vidt forskjellige <Link/> komponenter i ReactJS.

Me byrja med å bruka MUI sin Link komponent over heile fjøla, men det blei raskt tydeleg at dette trigget unødig omlasting av nettlesar når ein navigerte rundt i enkeltside-applikasjonen (SPA).

Så, korleis kan me mikse desse komponentene uten å måtta skrive ein haug med ekstra kode?

Løysing #

Her er det endelege resultatet som blei vår eiga Link.tsx komponent i kodebasen vår. Ta ein kikk og så fortsetjer me med ein gjennomgang etter kodeblokken.

import { Link as MuiLink } from "@mui/material"
import React from "react"
import { Link as RouterLink } from "react-router-dom"

// Remove 'href' requirement, and require only 'to' property
type MuiProps = Exclude<Parameters<typeof MuiLink>[0], "href">

type RouterLinkProps = Parameters<typeof RouterLink>[0]

type Props = MuiProps & RouterLinkProps

/**
 * Link combines Material UI's link with react-router-dom. This
 * gives us styling and zero-refresh navigation.
 *
 * Learn more: {@link https://mui.com/guides/routing/#link}
 */
export const Link = React.forwardRef(({ to, ...rest }: Props, ref) => {
  // If the link is a plain http link we trigger a normal browser navigation.
  if (typeof to === "string" && to.startsWith("http")) {
    return <MuiLink ref={ref as any} underline="always" {...rest} href={to} />
  }
  return (
    <MuiLink
      ref={ref as any}
      underline="always"
      {...rest}
      to={to}
      component={RouterLink}
    />
  )
})

Etter litt sirlig lesing av dokumentasjonen til både MUI og RR så fann eg ut at APIet på MUI sin Link komponent inneheld ein component property som let oss overstyre den faktiske komponenten som MUI rendrer på sida.

Verdt å merka seg i frå koden over er:

  • Me utleder typer ved hjelp av typeof nøkkelordet.
  • Etter typeof så bruker me TS sine utility types til å bygga typene me ønsker.
  • JSDoc kommentar over komponentnamnet med link til meir info. Anbefaler alle å dokumentera kort og tydeleg formålet til komponentene sine. Gi kollegaene dine ein raud tråd å følga om dei ønsker å læra meir.
  • Den endelege Props typen gir konsumenter av denne komponenten tilgang på dei originale typedefinisjonane med tilhørande JSDoc kommentar om dei finst.
  • Me bruker to property til å halda både eksterne og interne lenker. Dersom koden kjenner igjen ein ekstern lenke så bruker me kun MUI sin Link component.
    • Me kunne fortsatt å skilja mellom href og to i APIet på denne komponenten, men det hadde kanskje blitt ein meir komplisert løysing. Eg fekk det iallfall ikkje til på ein enkel måte.
  • …rest parameteren lar oss samle saman “resten” av properties og berre sende dei vidare ned i treet. Sidan MUI tilbyr prop forwarding så vil RR-spesifikke properties ende opp på den innerste RR Link komponten. Merk: MUI noterer at ein må vere litt obs på prop-kollisjonar når ein lener seg på prop forwarding som me gjer her.
  • Sidan APIet på Link komponenten er nærast ein rein kopi av dei underliggande APIene så kan brukarane stort sett berre fortsetje å forholda seg til MUI og RR sin dokumentasjonen på nett.

Brekkande endringar er tilsynelatande ei kvardagsaffære i JS-verda, men her har me gjort mest mogleg for å skapa ein komponent som gjer få antakingar om framtida.

Kven veit kva for properties framtidige versjonar av MUI eller RR vil trengje? Forhåpentlegvis så vil det ikkje vere mykje kode å skriva om og så vil me ha TS til hjelp for å rydda unna følgefeil under kompilering.

Vidare lesnad #

Takk for merksemda!