Типизированный поиск вложенных свойств - PullRequest
0 голосов
/ 15 марта 2019

Я пытаюсь создать типобезопасную версию функции get lodash (см. Здесь).Моя идея состоит в том, чтобы создать функцию, которая сможет возвращать правильный тип, если вложенное свойство доступно, иначе не определено.

interface Nested {
  value: 'some' | 'type';
}
interface SomeComplexType {
  foo: string;
  bar: number;
  nested: Nested;
}

const testObject: SomeComplexType = {
  foo: 'foo value',
  bar: 1234,
  nested: {
    value: 'type'
  }
};

// The type of someValue should be: 'some' | 'type'
const someValue = lookup(testObject, 'nested.value');
console.log('Value found:', someValue);

Сейчас у меня есть следующее:

function get<T, K extends keyof T>(object: T, key: K): T[K] | undefined {
  return object[key];
}

function lookup<T, K extends keyof T>(object: T, path: string) {
  const parts = path.split('.');
  const property = parts.shift() as K; // TODO autoinfer is possible?
  const value = get(object, property);

  if (!parts.length || value === undefined) {
    return value;
  }

  const newPath = parts.join('.');
  return lookup(value, newPath);
}

НоЯ застрял с типом возврата поиска.Машинописный текст в строгом режиме говорит:

src / lookup.ts: 14: 10 - ошибка TS7023: «поиск» неявно имеет тип возврата «любой», поскольку он не имеет аннотации возвращаемого типа и на него ссылаютсяпрямо или косвенно в одном из его выражений возврата.

Есть идеи?

Ответы [ 3 ]

2 голосов
/ 15 марта 2019

К сожалению, то, что вы пытаетесь сделать, немного выходит за рамки возможностей TypeScript, по крайней мере на момент написания этой статьи.Основные проблемы:

  1. TypeScript не сможет понять, что именно вы делаете с этой строкой.TypeScript может проверить, соответствует ли данный строковый литерал ключам объекта, но кроме этого это не может помочь.поэтому мы не можем исправить ваш as K; // TODO autoinfer is possible? комментарий
  2. Вы получаете TS7023, потому что вы возвращаетесь в объект, и так как мы могли бы возвращаться куда угодно в объект, TypeScript не уверенгде вы собираетесь остановиться, поэтому он вынужден выводить тип возвращаемого значения как any.

Чтобы решить # 2 выше, мы можем попробовать изменить объявление функции следующим образом:

function lookup<T, K extends keyof T>(object: T, path: string): T[K] | undefined {

Но тогда TypeScript выдаст TS2322: Type 'T[K][keyof T[K]] | undefined' is not assignable to type 'T[K] | undefined' в нашем операторе возврата.По сути, TypeScript говорит, что «вложенные дочерние типы не совпадают с родительскими типами, поэтому то, что вы написали, неверно».

Это сказало, другие, прежде чем вы попытались это сделать, и некоторые подошли довольно близко,хотя, насколько я могу судить, не существует идеальной реализации.Вы можете прочитать некоторые различные решения здесь в этом выпуске GitHub: https://github.com/Microsoft/TypeScript/issues/12290 Большинство из них прибегают к использованию массивов, а не строк, так как тогда TypeScript может легче видеть отдельные фрагменты.

В сторону

Я хотел бы упомянуть, что проблема, которую вы пытаетесь решить, не совсем понятна в мире TypeScript.Если у вас есть определенное определение объекта, к которому вы пытаетесь получить доступ - просто получите к нему прямой доступ!Нет необходимости в дополнительном уровне косвенности.TypeScript проверит тип доступа к объекту.

Если вам нужно динамически вернуть определенное поле на более позднем этапе, вы можете создать где-нибудь анонимную функцию и позже вызвать ее, чтобы сделать это, и TypeScriptможете проверить вашу анонимную функцию.

Если вам действительно нужен динамический доступ на основе строк к случайному объекту, для которого у вас нет типизации, тогда any на самом деле правильный тип возврата для вашего lookupфункция.Поскольку, если ваш код не имеет определенной гарантии того, что это за тип, он, вероятно, может быть чем угодно.

1 голос
/ 18 июня 2019

Я попробовал несколько разных идей, в том числе: https://github.com/pimterry/typesafe-get и хотя я мог бы поделиться своим собственным взглядом на проблему.

Вместо строк я использую функцию стрелки, завернутую в try / catch, чтобы разрешить переход на запасной вариант. Таким образом, я могу искать как аргументы доступа, так и возвращаемый тип и иметь возможность предоставить свой запасной вариант:

type CallbackFn<T extends any[] = [], R extends any = void> = (...args: T) => R;

const get = <T extends any, R extends any, F extends any = null>(obj: T, accessor: CallbackFn<[T], R>, fallback: F = null): R | F => {
  if (!obj || typeof accessor !== 'function') {
    return fallback;
  }

  try {
    return accessor(obj);
  } catch (e) {
    return fallback;
  }
};

Для этого добавлено git-репо с более подробной информацией: https://github.com/jan-rycko/get-by-accessor

0 голосов
/ 15 марта 2019

Похоже, что ключ к тому, чтобы сделать вашу функцию возможной, заключается в том, что TypeScript применяет операцию поиска типов (T[K]) произвольное число раз для разрешения типов, подобных T[K][U][V]. Для этого потребуется, чтобы компилятор мог выполнять сам код ( a la prepack ). Я просто не думаю, что это возможно в данный момент. Я думаю, что вам, возможно, придется обойтись вручную, вызывая функции get с соответствующим количеством уровней для извлечения нужного вам типа. Например, тип out здесь 'some' | 'type', как и ожидалось.

const out = get(get(testObject, 'nested'), 'value');

Ссылка на игровую площадку

...