React中如何基于已有组件自定义Input并解决void元素报错
错误原因
报错直接触发点是input属于HTML空元素(void element),既不允许嵌套子节点,也不能接收children属性,你的写法存在三个逻辑问题:
- 错误嵌套:
styled(TextField)生成的StyledTextField本身就会渲染基础TextField组件,你又把<TextField />作为子元素传入StyledTextField,相当于给组件传递了非法的children属性 - 属性透传:styled-components默认会把所有接收的props透传给底层组件,你的基础
TextField又会把所有props透传给最终的<input>DOM,导致input拿到了非法的children属性直接抛错 - 额外隐患:自定义的
textAlign属性如果不做拦截,会直接透传到DOM上,触发React的无效DOM属性警告
正确实现代码
最简派生写法(无额外附属元素场景)
如果只是给基础输入框扩展样式,不需要加图标、错误提示等额外元素,直接按如下写法修改派生组件即可:
import React from "react"; import styled, { css } from "styled-components"; import { TextField } from "~/components"; export type ProductFieldProps = { name: string; value?: string; textAlign?: "left" | "center" | "right"; }; const StyledProductField = styled(TextField).withConfig({ // 拦截自定义属性,不要透传到DOM shouldForwardProp: (prop) => !["textAlign"].includes(prop), })<{ textAlign: string }>` ${({ theme, textAlign }) => css` text-align: ${textAlign}; &:hover { border-color: ${theme.color.hover}; } `} `; export const ProductField: React.FC<ProductFieldProps> = ({ name, value, textAlign = "center", }) => { // 直接渲染样式包装后的组件,不要嵌套基础组件作为子元素 return <StyledProductField name={name} value={value} textAlign={textAlign} />; };
带附属元素的写法
如果需要给输入框加前缀图标、错误提示、标签等附属内容,新增独立的外层容器做布局,不要把附属内容作为input的子节点:
import React from "react"; import styled, { css } from "styled-components"; import { TextField } from "~/components"; export type ProductFieldProps = { name: string; value?: string; textAlign?: "left" | "center" | "right"; error?: string; }; // 外层容器只负责布局,不直接包装input const FieldWrapper = styled.div` position: relative; width: 100%; `; const StyledTextField = styled(TextField).withConfig({ shouldForwardProp: (prop) => !["textAlign"].includes(prop), })<{ textAlign: string }>` ${({ theme, textAlign }) => css` text-align: ${textAlign}; &:hover { border-color: ${theme.color.hover}; } `} `; const ErrorTip = styled.span` color: ${({ theme }) => theme.color.error}; font-size: 12px; `; export const ProductField: React.FC<ProductFieldProps> = ({ name, value, textAlign = "center", error, }) => { return ( <FieldWrapper> <StyledTextField name={name} value={value} textAlign={textAlign} /> {/* 附属内容和input平级,放在外层容器里 */} {error && <ErrorTip>{error}</ErrorTip>} </FieldWrapper> ); };
基础组件兼容说明
你当前写的基础TextField组件已经满足扩展要求:组件会把所有传入props(包括styled-components注入的className)透传给内部input元素,样式可以正常注入生效,不需要额外修改。
内容的提问来源于stack exchange,提问作者Alpin Cleopatra
相关产品推荐
相关产品推荐

