如何让TypeScript识别React中AbortController的类型收窄?
我有一个封装了应用API的类,核心代码如下:
export class Api { ... static requestData = async ( abortController: React.MutableRefObject<AbortController | null> ) => { // 取消之前的请求 if (abortController.current) { abortController.current.abort(); } // 为新请求创建控制器 abortController.current = new AbortController(); try { const response = await instance.get( "request/url/string", { signal: abortController.current.signal, }, ); if (response.success) { // 处理成功响应 } else { // 处理已知错误 } } catch (err: unknown) { const signal = abortController.current.signal; if (abortController.current.signal.aborted) { // 处理已取消的请求 } else { // 处理未知错误 } } };
Axios实例通过axios.create定义:
const instance = axios.create({ baseURL: "base-url-path", });
我想把取消旧请求并创建新AbortController的逻辑抽成独立函数复用,但遇到了TypeScript类型问题:
尝试1:抽为类内静态函数
调用后TypeScript报错:'abortController.current' is possibly 'null'.ts(18047)
public static abortPreviousAndSetUpNewController = ( abortController: React.MutableRefObject<AbortController | null> ) => { // 取消之前的请求 if (abortController.current) { abortController.current.abort(); } // 为新请求创建控制器 abortController.current = new AbortController(); };
尝试2:使用is关键字做类型收窄
出现语法错误:A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
public static abortPreviousAndSetUpNewController = ( abortController: React.MutableRefObject<AbortController | null> ): abortController is React.MutableRefObject<AbortController> => { // 取消之前的请求 if (abortController.current) { abortController.current.abort(); } // 为新请求创建控制器 abortController.current = new AbortController(); }
我想让TypeScript知道这个函数执行后,abortController.current的类型已经从AbortController | null变成了AbortController,并且尽可能不用类型断言。
包版本:axios 0.24.0、react 16.14.0
方案1:类型谓词函数+返回布尔值
类型谓词需要返回布尔值来告知TypeScript类型收窄结果,我们在函数末尾返回true,同时明确返回类型:
public static abortPreviousAndSetUpNewController = ( abortController: React.MutableRefObject<AbortController | null> ): abortController is React.MutableRefObject<AbortController> => { if (abortController.current) { abortController.current.abort(); } abortController.current = new AbortController(); // 返回true确认类型收窄完成 return true; };
调用后TypeScript会自动识别current非空:
static requestData = async ( abortController: React.MutableRefObject<AbortController | null> ) => { Api.abortPreviousAndSetUpNewController(abortController); // 此时不会再报current可能为null的错误 try { const response = await instance.get( "request/url/string", { signal: abortController.current.signal }, ); // ...后续逻辑 } catch (err: unknown) { // ...错误处理 } };
方案2:函数重载声明
通过重载明确调用前后的类型变化,实现时直接修改并返回原ref:
// 重载声明:输入含null的ref,输出确定有控制器的ref public static abortPreviousAndSetUpNewController( abortController: React.MutableRefObject<AbortController | null> ): React.MutableRefObject<AbortController>; // 实际实现逻辑 public static abortPreviousAndSetUpNewController( abortController: React.MutableRefObject<AbortController | null> ) { if (abortController.current) { abortController.current.abort(); } abortController.current = new AbortController(); return abortController; }
调用时可以直接使用返回的ref,类型自动收窄:
static requestData = async ( abortController: React.MutableRefObject<AbortController | null> ) => { const controllerRef = Api.abortPreviousAndSetUpNewController(abortController); try { const response = await instance.get( "request/url/string", { signal: controllerRef.current.signal }, ); // ... } catch (err) { // ... } };
方案3:返回新创建的控制器实例
直接返回函数内创建的控制器,调用时用返回值而非原ref的current,彻底规避类型问题:
public static abortPreviousAndSetUpNewController = ( abortController: React.MutableRefObject<AbortController | null> ): AbortController => { if (abortController.current) { abortController.current.abort(); } const newController = new AbortController(); abortController.current = newController; return newController; };
调用示例:
static requestData = async ( abortController: React.MutableRefObject<AbortController | null> ) => { const controller = Api.abortPreviousAndSetUpNewController(abortController); try { const response = await instance.get( "request/url/string", { signal: controller.signal }, ); // ... } catch (err) { // ... } };
以上三种方案都不需要类型断言,能让TypeScript正确识别类型变化,其中方案1最贴合你的需求——直接修改原ref并完成类型收窄。
内容的提问来源于stack exchange,提问作者Harrison

