преобразование функции слияния, созданной с использованием Object.entries.reduce, в машинописный текст - PullRequest
0 голосов
/ 08 февраля 2020

У меня есть функция слияния, которая объединяет 2 объекта глубиной 2 уровня (может быть до n-уровня, но для меня мне понадобилось только два) объектов.

const merge = (a, b) => {
    return Object.entries(a).reduce((agg, [key, value]) => ({
        ...agg,
        [key]: {
             ...agg[key],
            ...value,
        }
    }), b)
}

Так что, если у меня есть

const a = {
  'key1': {
    'a1': 1,
  },
   'key2': {
    'a2': 1,
  }
}

const b = {
  'key1': {
    'b1': 1,
  },
   'key3': {
    'b3': 1,
  }
}
merge(a, b)

Вывод будет { key1: { b1: 1, a1: 1 }, key3: { b3: 1 }, key2: { a2: 1 } }

Я создал интерфейс, очень похожий на Object.assign.

interface Merge<T, U>{
    (a: T, b: U): T & U
}

и функцию слияния

const merge: Merge<MyState, MyState> = (a, b) => {
    return Object.entries(a).reduce((agg, [key, value]) => ({
        ...agg,
        [key]: {
            ...(agg[key]), // Assuming it is always object
            ...value,
        }
    }), b)
}

Я получаю 2 ошибки

  1. На линии ...agg[key]
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'MyState'.
  No index signature with a parameter of type 'string' was found on type 'MyState'.ts(7053)
На следующий ...value
Spread types may only be created from object types.ts(2698)

Есть идеи как это исправить?

1 Ответ

1 голос
/ 09 февраля 2020

Будет трудно сделать этот тип безопасным , потому что TypeScript не может действительно обработать операцию типа более высокого порядка, в результате чего обратный вызов добавляет одно возвращаемое свойство к возвращаемому значению каждый раз, когда он вызывается, и в итоге возвращает значение типа T & U. Первый вызов обратного вызова будет иметь agg типа U и возвращать U & Pick<T, K> для некоторого синглтона K extends keyof T, а последний вызов обратного вызова будет иметь agg типа U & Omit<T, L> для какого-то другого синглтона L extends keyof T и возврат T & U. Это может быть забавное упражнение, чтобы увидеть, как близко мы можем подойти, но гораздо проще и продуктивнее просто сказать: проверка безопасности типов этой функции выше уровня оплаты компилятора.

С другой стороны, должно быть достаточно просто использовать некоторые разумные утверждения типа , чтобы сообщить компилятору, какие типы следует ожидать:

const merge = <T extends Record<keyof T, object>, U extends Record<keyof U, object>>(
  a: T, b: U
) => {
  return (
    Object.entries(a) as Array<[keyof T, T[keyof T]]>
  ).reduce((agg, [key, value]) => ({
    ...agg,
    [key]: {
      ...agg[key],
      ...value,
    }
  }), b as T & U)
}

Здесь я использовал два утверждения. Во-первых, Object.values() вернет Array<[keyof T, T[keyof T]]>, что более или менее верно; каждый элемент массива будет парой ключей и значений T. Во-вторых, b и, следовательно, agg равно T & U. Это в значительной степени неверно, но в итоге становится в основном правдой. Я говорю «в основном», потому что операторы распространения только приблизительно ведут себя как пересечения, особенно если у вас есть необязательные свойства или примитивные типы, поэтому действуйте на свой страх и риск. В любом случае утверждение b как T & U также подразумевает, что merge() возвращает T & U, что вам и нужно.

Также обратите внимание, что я ограничил T и U быть object s, которые содержат object s, так что вы случайно не используете merge(), где расширение на два слоя было бы ошибкой:

merge({ a: "" }, { a: { b: 1 } }); // error!
//      ~ <-- string is not object

Хорошо, надеюсь, это поможет; удачи!

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

...