microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.94k stars 12.47k forks source link

Suggestion: Support for const<T> #37694

Closed nojvek closed 4 years ago

nojvek commented 4 years ago

Search Terms

const extends type, const typed object

Suggestion

const<T> syntax would mean I want the const inferred type, but ensure that it extends T.

Use Case

when writing a normal object definition e.g const css = {border: '1px solid red', padding: '2px'}, typescript correctly infers the type of css as {border: string, padding: string}.

However sometimes you still want the inferred type to be a const, but ensure it extends another interface. e.g

import {Properties as CssProperties} from 'csstype';
const css: const<CssProperties> = {border: '1px solid red', padding: '2px'};

// inferred type is still `{border: string, padding: string}`, but typescript/vscode gives great code completion when writing the object definition.

I am working with css in js. Where the css tree is written using js/ts syntax.

export const jss ={
  navBreadcrumb: {
    display: `flex`,
  },
  crumb: {
    padding: `0.5rem`,
    cursor: `pointer`,

    '&:hover': {
      backgroundColor: theme.colorGrayLightest,
      color: theme.colorBlue,
    },
  },
  crumbArrow: {
    height: `100%`,
    alignSelf: `center`,
  },
};

The top level keys are class names, the values are css properties. Consumed in tsx file as

import {jss} from './index.jss';

function classNames<T>(jss: T): {[C in keyof T]: string} {
   return Object.keys(jss);
}

const styles = classNames(jss);
function render(props) {
   const {crumbs} = props;
   <div class={styles.navBreadcrumb}>{crumbs.map(crumb => <span class={styles.crumb}></span>)}</div>
}

What this achieves is that typescript gives great completion when typing styles.X and enforces that the class name is actually defined in jss.ts file. This means the usage in tsx is bound to definition in jss.ts file. Also ctrl+click styles.xyz correctly takes to where the css is defined.

What would be nice to be added

Right now with just const jss ={...}, TS can't enforce that the properties are CSSProperties.

but doing the below would be awesome.

import {Properties as CssProperties} from 'csstype';
const jss: const<{className: string]: CssProperties}> = {....};

This would mean jss would fully retain the const shape and doing {[C in keyof T]: string} would still work.

Currently one way to achieve this would be two write a dummy function that does nothing but just signals typescript that T extends SomeType e.g

import {Properties as CssProperties} from 'csstype';

export type JSS = {
  // recursive definition so we can have completion for @media, @keyframes, &:hover e.t.c
  [className: string]: CssProperties<string | number> | JSS;
};

/**
 * Helper passthrough function that ensures that jss is typed
 * and vscode gives fantastic code completion
 */
export function asJss<T extends JSS>(jss: T): T {
  return jss; // <---- NOTE: how this function just returns back jss.
}

and you'd use it like


const jss = asJss({
   myClassA: {.... css properties... }
   myClassB: {.... css properties ...}
})

What is the ask ?

Rather than having to pass everything to an identity function like

/**
 * Helper passthrough function that ensures that jss is typed
 * and vscode gives fantastic code completion
 */
export function asJss<T extends JSS>(jss: T): T {
  return jss; // <---- NOTE: how this function just returns back the input (jss).
}

use const<T> syntax instead

const jss: const<JSS> = {
   myClassA: {.... css properties... }
   myClassB: {.... css properties ...}
}

Checklist

My suggestion meets these guidelines:

RyanCavanaugh commented 4 years ago

I think this is just #7481 - you'd write { border: 1 } as const __some_op__ CSSproperties ?

nojvek commented 4 years ago

I guess. Does it involve making a new operator though ? You already have the x extends Y syntax for function generics. I’d imagine it would be somewhat similar.

Feel free to close it if it’s already on your radar.

RyanCavanaugh commented 4 years ago

I think a new operator is the only right way to do it. The proposed syntax above is hard to reason about in higher-order cases (where it doesn't apply anyway), so the type hinting should be taking place more at an expression level

typescript-bot commented 4 years ago

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.