访问React元素的子元素

7

想象一下拥有一个React组件

function List() {
   return (<ul>
             <li>1</li>
             <li>2</li>
           </ul>
    );
}

我想创建一个高阶组件,用于修改所有li节点的样式。

function makeRed(component) {
    return function(props) {
         const element = React.createElement(component, props);

         return React.cloneElement(
            element,
            element.props,
            React.Children.map(
                element.props.children,
                ch => React.cloneElement(ch, { 
                        ...ch.props, 
                        style: {
                           backgroundColor: "red"
                        }
                    },                    
                    ch.props.children
                )
            )
        );
    }
}

但是,这并不起作用。子组件为空。
有趣的是,如果我直接创建组件,它就可以正常工作,例如:
...    
const element = <ul><li>1</li><li>2</li></ul>;
...

问题:如何访问任何React元素的子孙级元素?


9
这看起来非常像 React 中的反模式。为什么不创建一个 List 组件,可以接受样式作为属性,并使用一个高阶组件将该属性传递给它们呢? - Hamms
2
你能给一个更实际的例子吗?你所想要做的事情可以用普通的CSS轻松实现。 - azium
我需要使用HOC为具有子元素的任何元素添加“可排序(可拖动)”行为。我知道这不是普通组件的纯React方式,但这只是最简单的例子来说明问题。 - STO
@STO 这可以像 Hamms 已经说的那样完成。 - Jordan Enev
1个回答

4
这是一个反模式,正如@hamms所指出的那样。有更好的方法在React中实现主题,可以使用纯CSS。
话虽如此,这里有一个针对您用例的工作示例的hack - https://codesandbox.io/s/ymqwyww22z
基本上,我所做的就是:
  1. Make List a class based component. It's not too much trouble to wrap a functional component into one.

    import React, { Component } from "react";
    
    export default class List extends Component {
      render() {
        return (
          <ul>
            <li>1</li>
            <li>2</li>
          </ul>
        );
      }
    }
    
  2. Implement render in the dynamic class Red<Component> to first fetch the element-tree returned from the base Component's render and then edit it.

    import React from "react";
    
    export default function makeRed(Component) {
      return class RedComponent extends Component {
        constructor(props) {
          super(props);
    
          RedComponent.displayName = `Red${Component.name}`;
        }
    
        render() {
          let componentElement = super.render();
          let { children, ...rest } = componentElement.props;
          children = React.Children.map(children, child => {
            return React.cloneElement(child, {
              ...child.props,
              style: {
                backgroundColor: "red"
              }
            });
          });
          return React.cloneElement(componentElement, rest, ...children);
        }
      };
    }
    

这种方式与使用createElementmakeRed版本有什么不同?

由于makeRed返回一个HOC,当您在App组件中使用它时,您不会将props分配给它。像这样...

function App() {
  return <RedList />; // no props!
}

在动态组件函数内部,您使用createElement创建新实例时,component.props不包含任何子元素。由于创建自己的子元素,因此您需要获取并修改它们,而不是从props中读取子元素。

1
感谢您的回答,并将其标记为已接受。因此,子组件仅适用于继承组件?如果我直接调用render()或调用一个函数式组件,它们是否相同? - STO
使用函数式组件,您可以直接调用它以获取元素树,而不是使用 super.render。如果组件是一个函数,您需要对 hoc 进行更改,以避免扩展类。 - hazardous

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