TypeScript определяет тип вывода generi c на основе типа ввода - PullRequest
3 голосов
/ 21 марта 2020

Для моего проекта React-Native я пытаюсь получить как можно больше автозаполнения и проверки типов. Одна из проблем заключается в настройке типов используемой библиотеки стилей.

Расширенная таблица стилей выглядит следующим образом:

const styles = createStyles({
  variable1: 100,
  variable2: "10rem",

  header: {
    width: "100%",
    height: 40,
    alignContent: "center",
    flex: "$flexAll",
    margin: "$gapMD"
  }
})

Когда я определяю стили, каждое значение стиля должно быть не только принять его оригинальный тип, но также string, function et c.

Когда библиотека завершит обработку таблицы стилей, результатом будет обычная таблица стилей React-Native. Поэтому результат функции должен содержать те же свойства, что и входные данные функции, но свойства должны быть сопоставлены с исходными типами стилей.

Например, flex должно быть number, а не number | string | function | etc.

Вот что я получил до сих пор:

import { ImageStyle, TextStyle, ViewStyle } from "react-native"
import EStyleSheet from "react-native-extended-stylesheet"

type Function<K> = () => K

type AllStyles = ImageStyle & TextStyle & ViewStyle
type StyleSet<T> = { [P in keyof T]: AllStyles }

type EValue<T> = T | string & {}
type EVariable<K> = EValue<K> | Function<EValue<K>>
type EStyle<T> = { [P in keyof T]: EVariable<T[P]> }

type EAnyStyle = EStyle<ImageStyle> | EStyle<TextStyle> | EStyle<ViewStyle>
type EStyleSet<T> = { [P in keyof T]: number | string | EAnyStyle | EStyleSet<T> }

export const createStyles = <T>(styles: EStyleSet<T>) =>
                            EStyleSheet.create(styles) as StyleSet<T>

К сожалению, автозаполнение не работает полностью, и я чувствую, что мои определения становятся слишком сложными. Тип результата также не совсем правильный.

Я действительно надеюсь, что есть мастер TypeScript, который может помочь мне заставить это работать.

I ' Мы установили Песочницу, которую можно использовать для проверки некоторых типов:
https://codesandbox.io/s/typescript-style-mania-h62cv

1 Ответ

1 голос
/ 03 апреля 2020

пожалуйста, дайте мне знать, если это правильное направление:

import {FlexStyle, ImageStyle, TextStyle, ViewStyle} from './react-native';

///////////////////////////////////////////////////////
// MOCK

const EStyleSheet = { create: obj => obj };

///////////////////////////////////////////////////////
// TYPES

// thanks ts-essentials.
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
type Builtin = Primitive | Function | Date | Error | RegExp;
type ExtendTypes<T, E> = T extends Builtin
    ? T | E
    : T extends Map<infer K, infer V>
        ? Map<ExtendTypes<K, E>, ExtendTypes<V, E>>
        : T extends ReadonlyMap<infer K, infer V>
            ? ReadonlyMap<K, ExtendTypes<V, E>>
            : T extends WeakMap<infer K, infer V>
                ? WeakMap<K, ExtendTypes<V, E>>
                : T extends Set<infer U>
                    ? Set<ExtendTypes<U, E>>
                    : T extends ReadonlySet<infer U>
                        ? ReadonlySet<ExtendTypes<U, E>>
                        : T extends WeakSet<infer U>
                            ? WeakSet<ExtendTypes<U, E>>
                            : T extends Array<infer U>
                                ? Array<ExtendTypes<U, E>>
                                : T extends Promise<infer U>
                                    ? Promise<ExtendTypes<U, E>>
                                    : T extends {}
                                        ? { [K in keyof T]: ExtendTypes<T[K], E> }
                                        : T;


type AllStyles = ImageStyle & TextStyle & ViewStyle;
type StyleSet<T> = Pick<{
  header: ViewStyle;
  font: TextStyle;
}, Extract<'header' | 'font', keyof T>>;

const createStyles = <T extends {
  // I would add precise definition for the properties here too.
  // header?: ExtendTypes<ViewStyle, Function | String>;
  // font?: ExtendTypes<TextStyle, Function | String>;
  [key: string]: string | number | Function | ExtendTypes<AllStyles, Function | String>; // Capital to keep string unions.
}>(styles: T): StyleSet<T> =>
  EStyleSheet.create(styles);

///////////////////////////////////////////////////////
// TEST

const styles = createStyles({
  variable1: 100,
  variable2: "10rem",

  header: {
    width: "100%",
    height: 40,
    alignItems: "flex-start",
    alignSelf: "$alignSelf", // autocomplete, but also allow custom values
    flex: "$flexAll",
    margin: "$gapMD",
    // autocomplete should work here, but doesn't
    // now it works
  },
  font: {
    fontSize: 20
  }
});

const imageStyle: ImageStyle = {
  alignItems: "center"
};

// Valid
console.log("header", styles.header);
console.log("header.fontSize", styles.font.fontSize);
console.log("imageStyle.alignItems", imageStyle.alignItems);

// Invalid: ViewStyle doesn't have textTransform
// now it works
console.log("header.textTransform", styles.header.textTransform);

// Invalid: TextStyle doesn't have resizeMode
// now it works
console.log("font.resizeMode", styles.font.resizeMode);
...