Skip to content

Latest commit

 

History

History
179 lines (131 loc) · 8.07 KB

avoid-strings.md

File metadata and controls

179 lines (131 loc) · 8.07 KB

Item 35: Prefer More Precise Alternatives to String Types

Things to Remember

  • Avoid "stringly typed" code. Prefer more appropriate types where not every string is a possibility.
  • Prefer a union of string literal types to string if that more accurately describes the domain of a variable. You'll get stricter type checking and improve the development experience.
  • Prefer keyof T to string for function parameters that are expected to be properties of an object.

Code Samples

interface Album {
  artist: string;
  title: string;
  releaseDate: string;  // YYYY-MM-DD
  recordingType: string;  // E.g., "live" or "studio"
}

💻 playground


const kindOfBlue: Album = {
  artist: 'Miles Davis',
  title: 'Kind of Blue',
  releaseDate: 'August 17th, 1959',  // Oops!
  recordingType: 'Studio',  // Oops!
};  // OK

💻 playground


function recordRelease(title: string, date: string) { /* ... */ }
recordRelease(kindOfBlue.releaseDate, kindOfBlue.title);  // OK, should be error

💻 playground


type RecordingType = 'studio' | 'live';

interface Album {
  artist: string;
  title: string;
  releaseDate: Date;
  recordingType: RecordingType;
}

💻 playground


const kindOfBlue: Album = {
  artist: 'Miles Davis',
  title: 'Kind of Blue',
  releaseDate: new Date('1959-08-17'),
  recordingType: 'Studio'
// ~~~~~~~~~~~~ Type '"Studio"' is not assignable to type 'RecordingType'
};

💻 playground


function getAlbumsOfType(recordingType: string): Album[] {
  // ...
}

💻 playground


/** What type of environment was this recording made in? */
type RecordingType = 'live' | 'studio';

💻 playground


function pluck(records, key) {
  return records.map(r => r[key]);
}

💻 playground


function pluck(records: any[], key: string): any[] {
  return records.map(r => r[key]);
}

💻 playground


function pluck<T>(records: T[], key: string): any[] {
  return records.map(r => r[key]);
  //                      ~~~~~~ Element implicitly has an 'any' type
  //                             because type '{}' has no index signature
}

💻 playground


type K = keyof Album;
//   ^? type K = keyof Album
//      (equivalent to "artist" | "title" | "releaseDate" | "recordingType")

💻 playground


function pluck<T>(records: T[], key: keyof T) {
  return records.map(r => r[key]);
}

💻 playground


const releaseDates = pluck(albums, 'releaseDate');
//    ^? const releaseDates: (string | Date)[]

💻 playground


function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
  return records.map(r => r[key]);
}

💻 playground


const dates = pluck(albums, 'releaseDate');
//    ^? const dates: Date[]
const artists = pluck(albums, 'artist');
//    ^? const artists: string[]
const types = pluck(albums, 'recordingType');
//    ^? const types: RecordingType[]
const mix = pluck(albums, Math.random() < 0.5 ? 'releaseDate' : 'artist');
//    ^? const mix: (string | Date)[]
const badDates = pluck(albums, 'recordingDate');
//                             ~~~~~~~~~~~~~~~
// Argument of type '"recordingDate"' is not assignable to parameter of type ...

💻 playground