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

使用next/Image组件触发TypeScript类型错误的解决方法

问题描述

将原生<img>标签替换为Next.js的NextImage组件后,出现TypeScript类型错误:类型不兼容,width属性的string类型无法赋值给SafeNumber类型。但代码中并未直接使用width属性,不清楚SafeNumber的来源,求解决方式。

组件代码

import React, { FC, ImgHTMLAttributes, useEffect, useRef, useState } from "react";
import NextImage from "next/image";
import checkInViewIntersectionObserver from "@/utils/isInViewPortIntersectionObserver";
import PlaceIcon from "./PlaceIcon";

export interface NcImageProps extends ImgHTMLAttributes<HTMLImageElement> {
  containerClassName?: string;
}

const NcImage: FC<NcImageProps> = ({
  containerClassName = "",
  alt = "nc-imgs",
  src = "",
  className = "object-cover w-full h-full",
  ...args
}) => {
  const isMounted = useRef(false);
  const _containerRef = useRef(null);

  const [__src, set__src] = useState("");
  const [imageLoaded, setImageLoaded] = useState(false);

  useEffect(() => {
    let _imageEl: HTMLImageElement | null = null;

    isMounted.current = true;

    const _imageOnViewPort = () => {
      if (!src) {
        _handleImageLoaded();
        return true;
      }
      _imageEl = new Image();
      if (_imageEl) {
        _imageEl.src = src;
        _imageEl.addEventListener("load", _handleImageLoaded);
      }
      return true;
    };

    const _handleImageLoaded = () => {
      if (!isMounted.current) return;
      setImageLoaded(true);
      set__src(src);
    };

    const _checkInViewPort = () => {
      if (!_containerRef.current) return;
      checkInViewIntersectionObserver({
        target: _containerRef.current,
        options: {
          root: null,
          rootMargin: "0%",
          threshold: 0,
        },
        freezeOnceVisible: true,
        callback: _imageOnViewPort,
      });
    };

    const _initActions = async () => {
      _checkInViewPort();
    };

    _initActions();
    return () => {
      isMounted.current = false;
    };
  }, [src]);

  const renderLoadingPlaceholder = () => {
    return (
      <div
        className={`${className} flex items-center justify-center bg-neutral-200 dark:bg-neutral-6000 text-neutral-100 dark:text-neutral-500`}
      >
        <div className="h-2/4 max-w-[50%]">
          <PlaceIcon />
        </div>
      </div>
    );
  };

  return (
    <div className={`nc-NcImage ${containerClassName}`} data-nc-id="NcImage" ref={_containerRef}>
      {__src && imageLoaded ? (
        <NextImage src={__src} className={className} alt={alt} {...args} />
      ) : (
        renderLoadingPlaceholder()
      )}
    </div>
  );
};

export default NcImage;

错误信息

Type '{ crossOrigin?: "" | "anonymous" | "use-credentials" | undefined; decoding?: "async" | "auto" | "sync" | undefined; height?: string | number | undefined; loading?: "eager" | "lazy" | undefined; ... 263 more ...; alt: string; }' is not assignable to type '{ src: string | StaticImport; alt: string; width?: SafeNumber | undefined; height?: SafeNumber | undefined; fill?: boolean | undefined; loader?: ImageLoader | undefined; ... 11 more ...; lazyRoot?: string | undefined; }'.
  Types of property 'width' are incompatible.
    Type 'string | number | undefined' is not assignable to type 'SafeNumber | undefined'.
      Type 'string' is not assignable to type 'SafeNumber | undefined'.ts(2322)

解决方法

1. 问题本质

你的NcImageProps继承了原生ImgHTMLAttributes,其中width和height允许string类型(比如"100px"),但Next.js的NextImage组件要求这两个属性必须是SafeNumber——这是Next内部定义的类型,等价于number | undefined,不接受字符串值。你通过...args把包含width/height的原生属性传递给NextImage,触发了类型冲突。

另外从代码看,你是用CSS类(className="object-cover w-full h-full")控制图片尺寸,根本不需要传递width和height给NextImage,所以直接排除这两个属性即可。

2. 具体方案

方案一:修改组件接口,排除冲突属性

直接在NcImageProps中去掉原生img的width和height属性:

export interface NcImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'width' | 'height'> {
  containerClassName?: string;
}

方案二:传递属性时过滤冲突字段

如果不想修改接口,可以在传递给NextImage前,把width和height从args中剔除:

// 在return部分修改
const { width, height, ...nextImageArgs } = args;
return (
  <div className={`nc-NcImage ${containerClassName}`} data-nc-id="NcImage" ref={_containerRef}>
    {__src && imageLoaded ? (
      <NextImage src={__src} className={className} alt={alt} {...nextImageArgs} />
    ) : (
      renderLoadingPlaceholder()
    )}
  </div>
);

3. 关于SafeNumber

SafeNumber是Next.js内部为NextImage定义的类型,目的是确保图片尺寸是明确的数值(用于图片优化、布局计算),不允许像原生img那样接受字符串形式的尺寸(比如"50%"、"auto")。如果确实需要给NextImage设置尺寸,必须传数字类型的值,或者使用fill属性配合父容器的CSS尺寸来实现自适应。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 12:51:18