使用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

