Typescript: Suggestion: automatically mark named parameters with defaults as optional

Created on 19 Mar 2019  ·  3Comments  ·  Source: microsoft/TypeScript

Search Terms

default destructuring named parameters object optional automatically

Suggestion

When using "named parameters", if a parameter has a default (provided when destructuring), TypeScript should automatically mark that named parameter as optional. Example:

const myFn = ({ foo, bar = 'default' }: { foo: number; bar: string; }) => {
    bar
}

// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
myFn({ foo: 1 });

You might ask "why not just manually mark the parameter as optional?" Good question! Because:

  • When modifying defaults, we must remember to manually modify the parameter types to match any defaults we have added/removed.
  • Sometimes the parameter types are not easily modifyable, e.g. when it comes from a third party (example below).
// We can't easily modify `SomeThirdPartyObject` here to mark `bar` as optional.
const myFn = ({ foo, bar = 'default' }: SomeThirdPartyObject) => {}
// Workaround
const myFn = ({ foo, bar = 'default' }: Omit<SomeThirdPartyObject, 'bar'> & Partial<Pick<SomeThirdPartyObject, 'bar'>>) => {}
myFn({ foo: 1 })

Use Cases

React function components are a very common use case for "named parameters" and destructuring with defaults.

import * as React from 'react';

type Props = { foo: number; bar: string; };

const MyComponent: React.FunctionComponent<Props> = ({
    foo,
    bar = 'default',
}) => null;

// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
<MyComponent foo={1} />;
const MyComponent = ({
    foo,
    bar = 'default',
}: Props) => null;

// No error since `bar` is marked as automatically optional.
// (Currently this errors because `bar` is still marked as required.)
<MyComponent foo={1} />;

(defaultProps is supposed to solve this exact problem, but it doesn't currently work for function components: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695).

Examples

Checklist

My suggestion meets these guidelines:

  • [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.
Declined Suggestion

Most helpful comment

I appreciate the symmetry with parameter defaults this would create, but I have probably a dozen objections to this.

What if you didn't want the property to be optional, i.e. you want an explicit undefined from the consumer? There'd be no way to opt out.

This is problematic for .d.ts generation when the type annotation isn't an anonymous object type.

As a first principle, if the user writes a type annotation, we shouldn't defy that annotation without some extremely good motivation.

There's an argument that destructuring default is just sugar for an additional conditional assignment at the top of the function body, which would imply the parameter type shouldn't change, though again this isn't preserved by the bare parameter default behavior.

All 3 comments

I have some hangups about this. I routinely check my required parameters. Just because TypeScript says it is required doesn't mean that constraint is respected on the JavaScrpt side. I personally love the idea of forcing required parameters and providing defaults (along with some more logic) to protect on the JS consumer side. I think making people explicitly state what they want serves the two thoughts fine as is. Also, you are talking about overriding the intended behaviour of a third-party design object. Is that wise?

I appreciate the symmetry with parameter defaults this would create, but I have probably a dozen objections to this.

What if you didn't want the property to be optional, i.e. you want an explicit undefined from the consumer? There'd be no way to opt out.

This is problematic for .d.ts generation when the type annotation isn't an anonymous object type.

As a first principle, if the user writes a type annotation, we shouldn't defy that annotation without some extremely good motivation.

There's an argument that destructuring default is just sugar for an additional conditional assignment at the top of the function body, which would imply the parameter type shouldn't change, though again this isn't preserved by the bare parameter default behavior.

We have the same problem, especially for factory constructors. We came with this workaround. Say we have the interface Message:

export interface Message {
  fieldName?: string;
  status: string;
  statusText: string;
  type: "ERROR" | "SUCCESS" | "WARNING" | "REDIRECT";
  userMessage: string;
  userTitle: string;
}

And you have a factory constructor, that just provides some defaults:

export function createMessage({
  userMessage = '',
  userTitle = '',
  status = '',
  statusText = '',
  ...rest
}: OptionalKeys<
  Message,
  'userMessage' | 'userTitle' | 'status' | 'statusText'
>): Message {
  return {
    userTitle,
    userMessage,
    type,
    status,
    statusText,
    ...rest,
  };
}

We use this OptionalKeys type utility, to make sure the default values are marked as optional:

export type OptionalKeys<T extends object, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

manekinekko picture manekinekko  ·  3Comments