Удалить модификатор только для чтения для вызываемой подписи

#typescript

#typescript

Вопрос:

Функция изначально не позволяет изменять свойства только для чтения (например, name в ES6):

 let foo = function (n: number) {
    return n;
}

foo.name = 'not foo'; // Cannot assign to 'name' because it is a read-only property
  

Чтобы обойти это, Writable использовался тип утилиты из ссылки:

 type Writable<T> = {
  -readonly [K in keyof T]: T[K];
};
  

1. Только для чтения name не зависит от пересечения:

 let writableFoo: typeof foo amp; { name: string } = foo;
writableFoo.name = 'not foo'; // Cannot assign to 'name' because it is a read-only property
  

2. Writable не получает name из типа функции и не вызывается:

 let writableFoo: Writable<typeof foo> = foo;
writableFoo.name = 'not foo'; // Property 'name' does not exist on type 'Writable<(n: number) => number>'
writableFoo(1); // This expression is not callable
  

3. Writable получает name от Function , но по-прежнему не вызывается:

 let writableFoo: Writable<Function> = foo;
writableFoo.name = 'not foo';
writableFoo(1); // This expression is not callable
  

4. Omit использует подпись индекса и также не вызывается:

 let writableFoo: Omit<typeof foo, 'name'> amp; { name: string } = foo;
writableFoo.name = 'not foo';
writableFoo(1); // This expression is not callable
  

Цель здесь состоит в том, чтобы ввести, writableFoo чтобы сохранить writableFoo возможность вызова и разрешить name изменение, предпочтительно без изменения других свойств с помощью Writable . Он не пытается решить конкретную проблему кодирования, но исследует проблемы с указанным типом.

Почему 1 не влияет на readonly модификатор по типу пересечения?

Почему 2 не получает, name несмотря на то, что он распознается как typeof foo as foo.name ?

Как 2-4 могут получить подпись вызова при удалении readonly модификатора из name ?

Комментарии:

1. Если я посмотрю на MDN , он указывает, что если вы хотите его изменить, вам нужно будет использовать Object.defineProperty , поскольку он настраивается, вы можете переопределить его таким образом

2. @Icepickle Спасибо, это правильно (но это не будет работать для записываемого неконфигурируемого свойства). Как я уже отмечал, цель состоит не в том, чтобы решить проблему кодирования, а в том, чтобы исследовать проблемы с типизацией TS.

3. Почему 1 не влияет на модификатор только для чтения по типу объединения? typeof foo amp; { name: string } создает пересечение вместо объединения typeof foo | { name: string }

4. @NareshPingale Спасибо, что заметили, исправил термин. Я ожидал, что это будет пересечение, объединение там не сработает.

5.Даже если вы это сделаете foo.name = 'not foo'; foo.name , все равно будет напечатано «foo», так какова цель здесь?

Ответ №1:

С помощью этого вы можете как вызывать, так и записывать name

 interface Foo extends Function{
    name: string
}
const d: Foo = function(){}
d.name ='not foo'
d()
  

Спасибо. Технически это решает проблему, но функция игнорирует
подпись функции, d(‘должна вызывать ошибку TS’, ‘для непредвиденных
аргументы ‘) (это не упоминалось в вопросе, но, похоже, является
разумное требование). Также сам const способен устранить ошибку
потому что он обрабатывается иначе, чем let

 type  Incrementer = (x: number)=>string
interface Foo extends Incrementer{
    name: string
}
let d: Foo = (x)=>'a' x
d.name ='not foo'
d(1)
d('s') // string not expceted must be number
  

Если вам нужен общий ответ, вот ваш Writable

 interface Writable<T extends (...args: any) => any> {
    (...arg: Parameters<T>): ReturnType<T>;

    name: string
}

type  Incrementer = (x: number, y: boolean) => string

let d: Writable<Incrementer> = (x, y) => 'a'   x   y
d.name = 'not foo' // no error
d(1, true) // no error
d('d', true)  // 'd' is not assignable to number
d(2, 1) // 1 is not assignable to boolean
  

Комментарии:

1. Спасибо. Это технически решает проблему, но Function игнорирует подпись функции, d('should cause TS error', 'for unexpected args') (это не упоминалось в вопросе, но, по-видимому, является разумным требованием). Также const сам по себе способен удалять ошибку, потому что она обрабатывается иначе, чем let .

2. Хороший ответ, поддержанный. Просто хотелось бы добавить, что вы можете написать вызываемую подпись прямо внутри объявления интерфейса, например: interface Foo { (n: number): number; name: string } нет необходимости extends .

3. @EstusFlask проверьте последнюю правку. Я улучшил ответ общим ответом

4. Спасибо, это было полезно. Я оставлю вопрос открытым, потому что меня все еще интересует объяснение относительно внутренних компонентов TS, почему вызываемые подписи ведут себя так, как они делают здесь (части «почему» из вопроса). Если есть способ вывести подпись без явного указания типа функции, это было бы предпочтительным решением здесь.