#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, почему вызываемые подписи ведут себя так, как они делают здесь (части «почему» из вопроса). Если есть способ вывести подпись без явного указания типа функции, это было бы предпочтительным решением здесь.