Default exports er 💩

React-utvikler Kristofer Giltvedt Selbekk i Bekk mener vi bør slutte med denne kodestilen med en gang.

Jeg vet ikke om du visste det, men default exports er så 2015. Jeg anbefaler at du dropper dem fra kodebasen din til fordel for named exports.


En kjapp recap av exports

Før jeg starter full tirade-modus, så er det greit å ha med seg flesteparten. Så la oss ta en kjapp intro til tematikken jeg skal prate om.

I 2015 kom TC39-komitéen med en haug flotte nyheter til JavaScript. Mens pilfunksjoner og nye måter å scope variabler på fikk mesteparten av oppmerksomheten, ble folk også veldig glad i det nye modulsystemet! Der man før måtte skrive JavaScripten sin i én stor fil, eller lage intrikate systemer for emulere at man ikke hadde én stor fil, kunne man nå plutselig ha forskjellige filer, med kode i som man eksporterte, og så importerte. Det gjorde ting utrolig mye enklere!

Det ble bestemt at man skulle ha to forskjellige typer å tilgjengeliggjøre kode fra en fil — såkalte “default export”-er og “named exports”. Her er hvordan man gjør det:

// math.js
export default function add(a, b) { return a + b }
export function subtract(a, b) { return a - b }

For å bruke denne koden i en annen fil (som i dette eksempelet ligger i samme mappe), så skriver man følgende:

import add from './math'
import { subtract } from './math'
// eventuelt
import add, { subtract } from './math'

Tanken var vel at det man bruker mest, eller det som er “åpenbart logisk” å bruke fra den filen, burde være en default export, mens andre ting burde være named exports. Mine antakelser her altså.

Hvorfor er default exports the worst?

Som du kanskje la merke til i brukseksempelet ovenfor, så er det ikke alltid veldig logisk hvorfor noe skal ha én syntaks, mens noe skal ha en annen. Hvorfor er add hoved-eksporten, mens subtract er “nedgradert” til navngitt eksport?

Et annet, litt mer realistisk eksempel, kan være en React-komponent. Her ser jeg ofte følgende standard i TypeScript-prosjekter:

export type BoxProps = { 
  //.. 
}
export default function Box(props: BoxProps) {
  // ..
}

For å bruke denne koden, ser koden din plutselig slik ut:

import Box, { BoxProps } from './Box'

For det første synes jeg denne syntaksen er forvirrende, og det synes IDEer også. VSCode sliter f.eks. mye mer med å importere default exports enn named exports (merk: min erfaring, dette er sikkert løsbart på sikt).

For det andre så blir det fort enda mer avansert om du skulle være så uheldig å lage en fil med flere enn én komponent i — som er et mønster jeg ofte bruker:

export type BoxProps = { 
  //.. 
}
export default function Box(props: BoxProps) {
  // ..
}
export type BoxHeadingProps = { 
  //.. 
}
export function BoxHeading(props: BoxProps) {
  // ..
}

Nå ser plutselig bruken slik ut:

import Box, { BoxProps, BoxHeading, BoxHeadingProps } from './Box'

Man kan selvfølgelig argumentere for at Box -komponenten er “hovedkomponenten”, og derfor burde ha litt ekstra viktighet i APIet, men den viktigheten kan oppnås med navngivning istedenfor.

En annen litt klønete ting med default exports er at du ikke kan eksportere konstanter.

export default function yo(name) { console.log(`yo ${name}`) } // ok
export default const yo = name => console.log(`yo ${name}`) // error

Hvorfor de har gjort dette tullete skillet vet fåglarna, men det er noe jeg til stadighet snubler i.

Bruk named exports

Med named exports, derimot, er alt gull og grønne skoger. Å auto-importere funksjoner fungerer uten problemer, det er ingen rar syntaks-forskjell mellom én import og en annen, og du kan fint eksportere hva enn du vil som named exports!

Default exports har selvfølgelig sine bruksområder de også, men de er som regel i eksterne biblioteker med én enkelt funksjon (som f.eks. classnames -biblioteket). Selv her kan man argumentere for at det er vel så greit å bruke named exports her også, men jeg skal ikke være sur og gammel.

Så bruk named exports i kodebasen din. Du og kollegaene dine kommer til å takke deg i tiden som kommer.