打字稿条件 return 基于道具的类型
Typescript conditional return type based on props
- 在 electron 应用程序中,我有一个组件可以呈现一个按钮并向
main
进程的任意通道发送消息。
main
进程做一些工作并 return 返回结果,其 return 类型取决于频道名称。
- 父组件还传递一个回调和通道名称。
const Button = ({ name, channel, callback }: ButtonProps) => {
const [isDisabled, setDisabled] = useState<boolean>(false);
const onClickHandler = () => {
setDisabled(true);
window.electron.ipcRenderer.sendMessage(channel);
};
useEffect(() => {
// type definition for the function below:
// on<T>(channel: string, func: (result: T) => void): () => void;
window.electron.ipcRenderer.on<ReturnType>(channel, (result) => {
setDisabled(false);
callback(result);
});
return () => {
window.electron.ipcRenderer.removeAllListeners(channel);
};
}, [channel, callback]);
return <button
type="button"
onClick={onClickHandler}
disabled={isDisabled}
>
{name}
</button>;
};
export default Button;
如何指定要传递给回调的 result
类型?
我是这样开始的:
type ReturnType = OpenDialogReturnValue | SaveDialogReturnValue;
type ButtonProps = {
name: string;
} & (
| {
channel: "OPEN_DIALOG";
callback: (result: OpenDialogReturnValue) => void;
}
| {
channel: "SAVE_EXAMPLE";
callback: (result: SaveDialogReturnValue) => void;
}
);
但是它在 callback(result)
;
行抛出类型错误
Argument of type 'ReturnType' is not assignable to parameter of type 'OpenDialogReturnValue & SaveDialogReturnValue'.
这里有什么问题?
有多种方法可以解决这个问题。
核心问题
您看到该错误的原因是,即使您已将 channel
和 callback
属性的不同值分成两个不同的对象,channel
和 callback
类型在 useEffect
函数内时仍未确定。这意味着 TypeScript 不知道 callback
应该有什么参数类型,并且会自动请求两者的交集
OpenDialogReturnValue
和 SaveDialogReturnValue
类型只是为了确保无论回调在运行时是什么——它肯定会得到一个它可以使用的对象参数。
解决方案
- 解决此问题的最简单方法是帮助 TypeScript 并使用类型转换告诉它信任我们。正如我们所知,TypeScript 要求我们为
callback
提供一个交集类型的参数,因为它不确定哪个 callback
函数将存在于运行时 ((result: OpenDialogReturnValue) => void) | (result: SaveDialogReturnValue) => void))
中。相反,我们可以告诉 TypeScript 只有一个 callback
并且参数类型不同 (results: OpenDialogReturnValue | SaveDialogReturnValue) => void
。这可以通过首先使用名为 Parameters
的实用程序类型提取 callback
可能具有的参数类型来完成,然后将其第一个参数强制转换为 callback
以分配一个名为 [= 的新变量31=]:
useEffect(() => {
type CallbackParameterTypes = Parameters<typeof callback>;
const typedCallback = callback as (results: CallbackParameterTypes[0]) => void;
window.electron.ipcRenderer.on<CallbackParameterTypes[0]>(channel, (result) => {
setDisabled(false);
typedCallback(result);
});
- 如果
channel
和callback
参数类型之间的关联无关紧要,那么上面可以简化为:
type ButtonProps = {
name: string;
channel: "OPEN_DIALOG" | "SAVE_EXAMPLE";
callback: (result: OpenDialogReturnValue | SaveDialogReturnValue) => void;
};
useEffect(() => {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
- 解决该问题的另一种方法是使用类型谓词,这可能难以维护,但是它们让 TypeScript 确切知道何时使用什么类型。要应用它们,您首先需要将联合对象重构为单独的接口;例如
OpenDialogObject
和 SaveExampleObject
。然后,您可以使用 is
类型谓词告诉 TypeScript 当前 props
对象遵循哪个特定接口,就像在 isOpenDialog
函数中所做的那样。函数本身使用 channel
属性 来决定对象的类型。然后可以在 useEffect
中使用它来帮助确定 props.callback
在运行时将具有的参数类型。然而,正如您所观察到的那样 - 语法可能会有点重复,尤其是当您有更多类型时。为了完整性;类型谓词函数只会在传递和推断完整的 props
对象而不是单个 channel
和 callback
属性时产生想要的结果。这样做的原因是因为这些 属性 类型不足以确定整个对象的类型:
interface OpenDialogObject {
channel: "OPEN_DIALOG";
callback: (result: OpenDialogReturnValue) => void;
}
interface SaveExampleObject {
channel: "SAVE_EXAMPLE";
callback: (result: SaveDialogReturnValue) => void;
}
type ButtonProps = {name: string;} & (OpenDialogObject | SaveExampleObject);
if (isOpenDialog(props)) {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
} else {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
}
- 在 electron 应用程序中,我有一个组件可以呈现一个按钮并向
main
进程的任意通道发送消息。 main
进程做一些工作并 return 返回结果,其 return 类型取决于频道名称。- 父组件还传递一个回调和通道名称。
const Button = ({ name, channel, callback }: ButtonProps) => {
const [isDisabled, setDisabled] = useState<boolean>(false);
const onClickHandler = () => {
setDisabled(true);
window.electron.ipcRenderer.sendMessage(channel);
};
useEffect(() => {
// type definition for the function below:
// on<T>(channel: string, func: (result: T) => void): () => void;
window.electron.ipcRenderer.on<ReturnType>(channel, (result) => {
setDisabled(false);
callback(result);
});
return () => {
window.electron.ipcRenderer.removeAllListeners(channel);
};
}, [channel, callback]);
return <button
type="button"
onClick={onClickHandler}
disabled={isDisabled}
>
{name}
</button>;
};
export default Button;
如何指定要传递给回调的 result
类型?
我是这样开始的:
type ReturnType = OpenDialogReturnValue | SaveDialogReturnValue;
type ButtonProps = {
name: string;
} & (
| {
channel: "OPEN_DIALOG";
callback: (result: OpenDialogReturnValue) => void;
}
| {
channel: "SAVE_EXAMPLE";
callback: (result: SaveDialogReturnValue) => void;
}
);
但是它在 callback(result)
;
Argument of type 'ReturnType' is not assignable to parameter of type 'OpenDialogReturnValue & SaveDialogReturnValue'.
这里有什么问题?
有多种方法可以解决这个问题。
核心问题
您看到该错误的原因是,即使您已将 channel
和 callback
属性的不同值分成两个不同的对象,channel
和 callback
类型在 useEffect
函数内时仍未确定。这意味着 TypeScript 不知道 callback
应该有什么参数类型,并且会自动请求两者的交集
OpenDialogReturnValue
和 SaveDialogReturnValue
类型只是为了确保无论回调在运行时是什么——它肯定会得到一个它可以使用的对象参数。
解决方案
- 解决此问题的最简单方法是帮助 TypeScript 并使用类型转换告诉它信任我们。正如我们所知,TypeScript 要求我们为
callback
提供一个交集类型的参数,因为它不确定哪个callback
函数将存在于运行时((result: OpenDialogReturnValue) => void) | (result: SaveDialogReturnValue) => void))
中。相反,我们可以告诉 TypeScript 只有一个callback
并且参数类型不同(results: OpenDialogReturnValue | SaveDialogReturnValue) => void
。这可以通过首先使用名为Parameters
的实用程序类型提取callback
可能具有的参数类型来完成,然后将其第一个参数强制转换为callback
以分配一个名为 [= 的新变量31=]:
useEffect(() => {
type CallbackParameterTypes = Parameters<typeof callback>;
const typedCallback = callback as (results: CallbackParameterTypes[0]) => void;
window.electron.ipcRenderer.on<CallbackParameterTypes[0]>(channel, (result) => {
setDisabled(false);
typedCallback(result);
});
- 如果
channel
和callback
参数类型之间的关联无关紧要,那么上面可以简化为:
type ButtonProps = {
name: string;
channel: "OPEN_DIALOG" | "SAVE_EXAMPLE";
callback: (result: OpenDialogReturnValue | SaveDialogReturnValue) => void;
};
useEffect(() => {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
- 解决该问题的另一种方法是使用类型谓词,这可能难以维护,但是它们让 TypeScript 确切知道何时使用什么类型。要应用它们,您首先需要将联合对象重构为单独的接口;例如
OpenDialogObject
和SaveExampleObject
。然后,您可以使用is
类型谓词告诉 TypeScript 当前props
对象遵循哪个特定接口,就像在isOpenDialog
函数中所做的那样。函数本身使用channel
属性 来决定对象的类型。然后可以在useEffect
中使用它来帮助确定props.callback
在运行时将具有的参数类型。然而,正如您所观察到的那样 - 语法可能会有点重复,尤其是当您有更多类型时。为了完整性;类型谓词函数只会在传递和推断完整的props
对象而不是单个channel
和callback
属性时产生想要的结果。这样做的原因是因为这些 属性 类型不足以确定整个对象的类型:
interface OpenDialogObject {
channel: "OPEN_DIALOG";
callback: (result: OpenDialogReturnValue) => void;
}
interface SaveExampleObject {
channel: "SAVE_EXAMPLE";
callback: (result: SaveDialogReturnValue) => void;
}
type ButtonProps = {name: string;} & (OpenDialogObject | SaveExampleObject);
if (isOpenDialog(props)) {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
} else {
window.electron.ipcRenderer.on<Parameters<typeof callback>[0]>(channel, (result) => {
setDisabled(false);
callback(result);
});
}