Type Systems & Props Design - Exploring PropTypes, TypeScript, Flow & Reason

Type Systems & Props Design - Exploring PropTypes, TypeScript, Flow & Reason

Badly designed props can lead to components that are frustrating to use. While there are a couple patterns to make components more pleasant to use, ultimately it needs a type system in combination with a good editor integration for an even better developer experience. Yet not all type systems are the same. They offer different features and therefore lead to different experiences.

Afcee4ad6e383e26799ff05681d1a2a5?s=128

Nikolaus Graf

November 30, 2018
Tweet

Transcript

  1. Type Systems & Props Design 
 Exploring PropTypes, TypeScript, Flow

    & Reason
  2. Let’s start without types

  3. <Button primary={true} small={true} />

  4. <Button primary small />

  5. <Button secondary large />

  6. <Button primary secondary small large />

  7. <Button variant="primary" size="small" />

  8. <Arrow down />

  9. <Arrow up down />

  10. <Arrow up down />

  11. <Arrow direction="down" />

  12. <Arrow direction="left" />

  13. <Arrow direction="notRight"/>

  14. None
  15. A or B or C

  16. easy to fix … is it though?

  17. Type systems to the rescue!

  18. <Arrow direction="up" /> PropTypes.string.isRequired;

  19. PropTypes.string.isRequired; <Arrow direction="up" />

  20. PropTypes.oneOf(["down", "up"]).isRequired; <Arrow direction="up" />

  21. None
  22. None
  23. export enum Direction { Up = "up", Down = "down"

    } interface Props { direction: Direction; } import { Direction } from "./Arrow"; <Arrow direction={Direction.Left} />
  24. interface Props { direction: "up" | "down"; } <Arrow direction="down"

    />
  25. None
  26. None
  27. None
  28. interface Props { direction: "up" | "down"; } <Arrow direction="down"

    />
  29. None
  30. None
  31. <Arrow direction=Down /> type direction = Down | Up;

  32. None
  33. A{x} or B{x, y} or C

  34. Draft{content}
 Post{content, publishedAt}

  35. None
  36. interface DraftPost { status: "draft"; content: String; } interface PublishedPost

    { status: "published"; content: String; publishedAt: Date; } type Props = DraftPost | PublishedPost;
  37. export default function Post(props: Props) { switch (props.status) { case

    "draft": return <div>{props.content}</div>; case "published": return <div>{props.content}{props.publishedAt}</div>; } }
  38. None
  39. Discriminated Unions

  40. Draft{status, content}
 Post{status, content, publishedAt}

  41. interface DraftPost { status: "draft"; content: String; } interface PublishedPost

    { status: "published"; content: String; publishedAt: Date; } type Post = DraftPost | PublishedPost; interface Props { post: Post; }
  42. <Post post={{ status: "published", content: "Unicorns & Cats", publishedAt: new

    Date() }} />
  43. None
  44. type DraftPost = { status: "draft", content: string }; type

    PublishedPost = { status: "published", content: string, publishedAt: Date }; type Props = DraftPost | PublishedPost;
  45. export default function Post(props: Props) { switch (props.status) { case

    "draft": return <div>{props.content}</div>; case "published": return <div>{props.content}{props.publishedAt}</div>; default: return null; } }
  46. None
  47. type DraftPost = {| status: "draft", content: string |}; type

    PublishedPost = {| status: "published", content: string, publishedAt: Date |}; type Props = DraftPost | PublishedPost;
  48. None
  49. Disjoint Unions

  50. Draft{status, content}
 Post{status, content, publishedAt}

  51. <Post post={{ status: "published", content: "Unicorns & Cats", publishedAt: new

    Date() }} />
  52. None
  53. type post = | Draft(string) | Published(string, int);

  54. let component = ReasonReact.statelessComponent("Arrow"); let make = (~post: post, _children)

    => { ...component, render: _self => switch (post) { | Draft(content) => <div> content->s </div> | Published(content, timestamp) => <div> content->s timestamp->string_of_int->s </div> }, };
  55. None
  56. Variants

  57. Draft(content)
 Post(content, publishedAt)

  58. Render Props

  59. <Query<ProfileData> query={getProfile}> {({ data, loading, error }) => { if

    (loading) return "Loading …"; if (error) return "Loading …"; if (!data) return null; return <div>{data.user.name}</div>; }} </Query>;
  60. <Query<ProfileData> query={getProfile}> {({ result }) => { switch (result.type) {

    case "loading": return "Loading …"; case "error": return "Sorry, something went wrong"; case "data": return <div>{result.data.user.name}</div>; default: return assertNever(result); } }} </Query>;
  61. None
  62. <GetUserQuery variables=userQuery##variables> ...{ ({result}) => switch (result) { | Loading

    => <div> "Loading"->s </div> | Error(error) => <div> error##message->s </div> | Data(response) => <div> response##user##name->s </div> } } </GetUserQuery>;
  63. None
  64. notifyOnNetworkStatusChange

  65. Usage & Complexity Type your Code

  66. The End @nikgraf