React - 为子组件添加类

36

我正在使用React,并且有一个组件只需要渲染子元素并根据条件为它们添加一个类。 最佳实现方法是什么?


这个应该能帮到你:https://dev59.com/gYXca4cB1Zd3GeqPDBOn - Cosmin Ababei
我的建议是尝试在CSS层面上解决它。如果您正在使用Tailwind,请添加“tailwind variants”插件。 - Mohsen
6个回答

40

一周前我想通了,这就是我想要的:

export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props);

        this.modifyChildren = this.modifyChildren.bind(this);
    }

    modifyChildren(child) {
        const className = classNames(
            child.props.className,
            {...otherClassses}
        );

        const props = {
            className
        };

        return React.cloneElement(child, props);
    }
    render() {
        return (<div>
            {React.Children.map(this.props.children, child => this.modifyChildren(child))}
        </div>);
    }
}

2
我尝试了这个解决方案,但是从渲染函数中得到一个错误,“child未定义”。如果我在那里更改为匿名函数,而不是调用类方法(例如this.modifyChildren),则错误消失,一切正常。这适用于“fat arrow”和传统的JS匿名函数。当我们的代码几乎相同时,我无法弄清楚为什么它对您有效,而对我无效。 - Dave Munger
1
发现了一篇可能会帮助其他人的文章 - https://learn.co/lessons/react-this-props-children,看起来是一个非常相似的解决方案 :) - Ash

24

出于简化考虑:

const StyleInjector = ({ children }) => {
   const StyledChildren = () =>
    React.Children.map(children, child =>
      React.cloneElement(child, {
        className: `${child.props.className} ${PUT_YOUR_CLASS_HERE}`
      })
    );

  return <StyledChildren />;
};

如果你想从外部设置一个额外的类,可以使用{children, className}替换StyleInjector的args,并将PUT_YOUR_CLASS_HERE替换为className。 使用示例:<StyleInjector className="classsss"><li>dd1</li><li>dd2</li></StyleInjector> - certainlyakey
1
@certainlyakey 这实际上是有意为之的,想象一下如果您已经在顶级组件上访问了className,为什么还需要将其包装在另一个组件中呢?您可以直接将其传递给子组件。当然,也可能存在一种特殊情况,您可能会根据Wrapper的本地状态应用className。但在这种情况下,我认为这会误导其他开发人员,他们会期望这些classNames被应用。 - Kerem atam

12

几周前,我需要在React中实现一个轮播的场景,没有什么华丽的效果,只是简单地切换活动类。我采取了这种方法,它非常好用,但请注意这是否是一个好方法。

{
 this.props.children.map((slide, index) => {
   return React.cloneElement(slide, {
      key: index,
      className: (this.state.currentActiveIndex === index) ?'active' : ''
   });
 })
}

2
在我看来,使用cloneElement是最好的方法。你实际上正在使用API的用途。 - denyzprahy
1
你的active className方法不是我在这里寻找的,但比我实现的更好。感谢你的帮助! - Christopher Marshall

1

您可以将一个prop传递给子组件,然后它可以选择应用哪个类(或者只传递类名,但这更语义化):

<Parent contentVisible={true}>
  <Child />
</Parent>

在你的子渲染器中:
const classes = [];

if (props.contentVisible)
  classes.push('visible');

<div className={classes.join(' ')} />

0
这是一个现代化的解决方案,它是类型安全的(Typescript 5.0,React 18.2):
import * as React from 'react';
import type { ReactNode, ReactElement, ReactPortal, PromiseLikeOfReactNode, ReactFragment } from 'react';

type StylizeChildrenProps = {
  children?: ReactNode
  className: React.HTMLAttributes<any>['className']
}

export function StylizeChildren({ children, className = '' }: StylizeChildrenProps) {
  if (children == null) return null;

  className = className.trim();
  if (!className) return <>{children}</>

  return <>
    {React.Children.map(children, child => addClassToNode(child, className))}
  </>
}

// Separate out into its own function because Promise-like nodes require this to be recursive
export function addClassToNode(node: ReactNode, className: string): ReactNode {
  if (node == null) {
    node satisfies null | undefined

    return node
  }

  if (typeof node !== 'object') {
    node satisfies string | number | boolean

    // wrap in a span, somewhat arbitrary decision
    return <span className={className}>{node}</span>
  }

  if ('props' in node) {
    node satisfies ReactElement | ReactPortal

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    const existing: unknown = node?.props?.className;
    if (existing && typeof existing === 'string') {
      className = `${existing} ${className}`
    }
    return React.cloneElement(node, { className })
  }

  if ('then' in node) {
    node satisfies PromiseLikeOfReactNode

    return node.then(n => addClassToNode(n, className))
  }

  node satisfies ReactFragment

  // wrap in div, somewhat arbitrary decision
  return <div className={className}>{node}</div>
}

修改此代码以在遇到非对象类型或React片段时执行其他操作将变得简单。例如,React片段是可迭代的,因此您可以通过迭代其所有子项并为其设置类来替换它。

存在satisfies语句是因为我认为它们使情况更清晰,并且如果底层类型或React API发生更改,则会揭示一些令人讨厌的错误。如果您运行的Typescript版本低于4.9,请删除它们,一切都应该正常工作。


-4

在你的组件的渲染方法内:

var childClasses = [],
    childClassNames; 
if (condition) {
    childClasses.push('my-class-name'); 
} 
childClassNames = childClasses.join(' ');

return (<div className={childClassNames}>
     .... (your component here)
</div> );

2
我不想在每个组件中都这样做,我希望包含它们的组件进行测试并在必要时添加类。 - Golan Kiviti
1
不确定你具体想要实现什么。您的父组件可以计算出所有必要的类,并将它们作为属性传递给子组件。 - Maggie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接