Вы можете сделать это с условным типом.С его помощью вы можете проверить наличие свойства и иметь или не иметь дополнительного свойства:
interface IPluginSpec {
name: string;
state?: any;
}
type IPluginOpts<PluginSpec extends IPluginSpec> = PluginSpec extends Record<'state', infer State> ? {
name: PluginSpec['name'];
initialState: State;
} : {
name: PluginSpec['name']
}
function createPlugin<PluginSpec extends IPluginSpec>(
opts: IPluginOpts<PluginSpec>,
) {
console.log('create plugin', opts);
}
interface IPluginOne {
name: 'pluginOne';
}
// Ok
createPlugin<IPluginOne>({
name: 'pluginOne',
// nothing to add
});
interface IPluginTwo {
name: 'pluginTwo';
state: number;
}
createPlugin<IPluginTwo>({
name: 'pluginTwo',
initialState: 0,
});
Для более сложного подхода вы можете использовать пересечение с общей частью и каждой необязательной частьюв своем собственном условном выражении:
interface IPluginSpec {
name: string;
state?: any;
config?: any;
}
type IPluginOpts<PluginSpec extends IPluginSpec> = {
name: PluginSpec['name']
}
& (PluginSpec extends Record<'state', infer State> ? { initialState: State; } : {})
& (PluginSpec extends Record<'config', infer Config> ? { initialConfig: Config; } : {})
Условный тип очень полезен для вызывающих.Проблема состоит в том, что внутри реализации typcript не может по-настоящему рассуждать об условных типах (поскольку T
неизвестно).
Лучшее решение - сохранить публичную подпись (с условными типами) и упрощенную подпись реализации (без условных типов).Это позволит вам реализовать функцию без утверждений типа, в то же время давая вызывающей стороне желаемое поведение:
function createPlugin<PluginSpec extends IPluginSpec>(opts: IPluginOpts<PluginSpec>)
function createPlugin<PluginSpec extends IPluginSpec>(opts: {
name: string
initalState: PluginSpec['state'],
initialConfig: PluginSpec['config'],
}) {
if (opts.initalState) {
opts
}
}