У мене є бібліотека, яка експортує тип утиліти, подібний до наступного:
type Action<Model extends object> = (data: State<Model>) => State<Model>;
Цей тип утиліти дозволяє оголосити функцію, яка буде виконуватись як "дія". Він отримує загальний аргумент - це Modelте, що дія буде діяти проти.
dataАргумент «дії», потім набирається з іншим типом корисності цього експорту I;
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
Тип Stateутиліти в основному приймає вхідний Modelзагальний, а потім створює новий тип, де Actionбули видалені всі властивості, що мають тип .
Наприклад, ось основна реалізація вищезазначених користувачів;
interface MyModel {
counter: number;
increment: Action<Model>;
}
const myModel = {
counter: 0,
increment: (data) => {
data.counter; // Exists and typed as `number`
data.increment; // Does not exist, as stripped off by State utility
return data;
}
}
Наведене працює дуже добре. 👍
Однак є випадок, з яким я борюся, зокрема, коли визначається визначення загальної моделі, а також фабрична функція для створення примірників загальної моделі.
Наприклад;
interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}
function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}
У наведеному вище прикладі я очікую, що dataаргумент буде введено там, де doSomethingдія була видалена, а загальне valueвластивість все ще існує. Однак це не так - valueмайно також було видалено нашою службою State.
Я вважаю, що причина цього полягає в тому, що він Tє загальним, не застосовуючи до нього жодних обмежень типу / звуження, і тому система типів вирішує, що він перетинається з Actionтипом і згодом видаляє його з dataтипу аргументу.
Чи є спосіб обійти це обмеження? Я провів деякі дослідження і сподівався, що існує якийсь механізм, за яким я можу заявити, що Tце будь-який, окрім як "" Action. тобто обмеження негативного типу.
Уявіть собі:
function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {
Але цієї функції не існує для TypeScript.
Хтось знає про спосіб, як я міг би змусити це працювати, як я очікую?
Для налагодження тут є повний фрагмент коду:
// Returns the keys of an object that match the given type(s)
type KeysOfType<A extends object, B> = {
[K in keyof A]-?: A[K] extends B ? K : never
}[keyof A];
// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
// My utility function.
type Action<Model extends object> = (data: State<Model>) => State<Model>;
interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}
function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}
З цим прикладом коду можна пограти тут: https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14