You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TypeScript Portal示例代码报错,如何修复?

修复React Portal组件的TypeScript与ESLint冲突问题

问题情况

使用严格模式的TypeScript和ESLint开发React Portal组件时,遇到两类冲突错误:

  • 清理函数中使用void触发ESLint提示:Expected 'undefined' and instead saw 'void'
  • 移除void后,TypeScript抛出类型不兼容错误,提示清理函数返回值不符合EffectCallback要求,具体错误信息:
Argument of type '() => () => HTMLDivElement' is not assignable to parameter of type 'EffectCallback'.
  Type '() => HTMLDivElement' is not assignable to type 'void | Destructor'.
    Type '() => HTMLDivElement' is not assignable to type 'Destructor'.
      Type 'HTMLDivElement' is not assignable to type 'void | { [UNDEFINED_VOID_ONLY]: never; }'

错误根源

portalRoot?.removeChild(current)执行后会返回被移除的DOM元素(HTMLDivElement类型),但React的Effect清理函数要求返回void或符合Destructor的类型(本质要求无有效返回值)。直接使用void会触发ESLint的no-void规则,移除void则会因返回DOM元素导致TypeScript类型检查失败。

修复方案

方案一:用大括号包裹清理逻辑(推荐)

将清理函数改为带大括号的函数体,不显式返回任何值,默认返回undefined,同时满足ESLint和TypeScript要求:

import React, { useEffect, useRef, ReactNode } from 'react'
import { createPortal } from 'react-dom'

interface Props {
  children?: ReactNode
  portalId: string
}

export const Portal: React.FC<Props> = ({ children, portalId }) => {
  const el = useRef(document.createElement('div'))

  useEffect(() => {
    const portalRoot = document.querySelector(`#${portalId}`) as HTMLElement
    const current = el.current
    portalRoot.appendChild(current)
    
    // 用大括号包裹,执行移除操作但不返回值
    return () => {
      portalRoot?.removeChild(current)
    }
  }, [portalId])

  return createPortal(children, el.current)
}

方案二:逗号表达式强制返回undefined

如果偏好单行写法,可使用逗号表达式,执行移除操作后返回undefined:

// 替换清理函数行
return () => (portalRoot?.removeChild(current), undefined)

说明

两种方案均能确保清理函数返回undefined,既通过TypeScript的严格类型检查,又避免ESLint对void的报错。若项目中no-void规则并非强制,也可通过调整ESLint配置允许void的使用,但上述方案更符合严格模式下的代码规范。

内容的提问来源于stack exchange,提问作者Salvatore Argentieri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 15:26:06