【问题标题】:only allow children of a specific type in a react component在反应组件中只允许特定类型的子级
【发布时间】:2015-02-06 14:03:21
【问题描述】:

我有一个Card 组件和一个CardGroup 组件,当CardGroup 的子代不是Card 组件时,我想抛出一个错误。这可能吗,还是我试图解决错误的问题?

【问题讨论】:

    标签: validation reactjs


    【解决方案1】:

    对于 React 0.14+ 并使用 ES6 类,解决方案将如下所示:

    class CardGroup extends Component {
      render() {
        return (
          <div>{this.props.children}</div>
        )
      }
    }
    CardGroup.propTypes = {
      children: function (props, propName, componentName) {
        const prop = props[propName]
    
        let error = null
        React.Children.forEach(prop, function (child) {
          if (child.type !== Card) {
            error = new Error('`' + componentName + '` children should be of type `Card`.');
          }
        })
        return error
      }
    }
    

    【讨论】:

    • 我不知道为什么,但child.type === Card 在我的设置中不起作用。但是我通过使用child.type.prototype instanceof Card 让它工作了。我的 React 版本是 15.5.4
    • 为什么不直接扔掉它,而不是返回一个错误值。
    • @user2167582 因为这是 prop 验证函数的预期 API。 Official docs 在示例代码中包含以下注释:您还可以指定自定义验证器。如果验证失败,它应该返回一个错误对象。不要console.warn 或扔,因为这在oneOfType 内不起作用
    • child.type 的文档在哪里?有人可以发链接吗?
    • 这对我有用 React.Children.forEach(props[propName], function (child) { if (child.type && child.type.name !== 'Card') { error = new Error('Blabla ´' + componentName + '一些错误'); } })
    【解决方案2】:

    您可以为每个孩子使用 displayName,通过 type 访问:

    for (child in this.props.children){
      if (this.props.children[child].type.displayName != 'Card'){
        console.log("Warning CardGroup has children that aren't Card components");
      }  
    }
    

    【讨论】:

    • 确保检查当前环境是开发环境还是生产环境。 propTypes 验证不会在生产环境中触发以提高性能。在 propTypes 中使用 customProp 会很有帮助。
    • 您不应该这样做,因为props.childrenopaque data type。更好地使用React.Children 实用程序,如here 所示。
    • 请记住,使用 Uglify 之类的东西会破坏这一点
    • 那个“帮助了我”,但我用name而不是displayName(最后一个对我不起作用)
    • 永远不要使用 displayName,因为在生产中它可能会被删除!
    【解决方案3】:

    您可以使用自定义 propType 函数来验证孩子,因为孩子只是道具。如果您想了解更多详细信息,我还为此写了article

    var CardGroup = React.createClass({
      propTypes: {
        children: function (props, propName, componentName) {
          var error;
          var prop = props[propName];
    
          React.Children.forEach(prop, function (child) {
            if (child.type.displayName !== 'Card') {
              error = new Error(
                '`' + componentName + '` only accepts children of type `Card`.'
              );
            }
          });
    
          return error;
        }
      },
    
      render: function () {
        return (
          <div>{this.props.children}</div>
        );
      }
    });
    

    【讨论】:

    • 这种语法在 ES2015 和 React 0.14.x+ 中仍然有效吗?
    • 谢谢@DiegoV。我想你也可以在类定义中声明static propTypes = {}
    • 我也喜欢这种语法,但类属性仍然只是 ES7 的提议。就我个人而言,我会等着看它是否标准化:)
    • child.type.displayName 在混淆后不起作用
    【解决方案4】:

    使用React.Children.forEach 方法遍历子元素并使用name 属性检查类型:

    React.Children.forEach(this.props.children, (child) => {
        if (child.type.name !== Card.name) {
            console.error("Only card components allowed as children.");
        }
    }
    

    我建议使用Card.name 而不是'Card' 字符串,以便更好地维护uglify

    见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name

    【讨论】:

    • 仅当我将名称更改为 displayName if (child.type.displayName !== Card.name)
    【解决方案5】:

    对于那些使用 TypeScript 版本的人。 您可以像这样过滤/修改组件:

    this.modifiedChildren = React.Children.map(children, child => {
                if (React.isValidElement(child) && (child as React.ReactElement<any>).type === Card) {
                    let modifiedChild = child as React.ReactElement<any>;
                    // Modifying here
                    return modifiedChild;
                }
                // Returning other components / string.
                // Delete next line in case you dont need them.
                return child;
            });
    

    【讨论】:

      【解决方案6】:

      如果使用 Typescript,则必须使用“React.isValidElement(child)”和“child.type”以避免类型不匹配错误。

      React.Children.forEach(props.children, (child, index) => {
        if (React.isValidElement(child) && child.type !== Card) {
          error = new Error(
            '`' + componentName + '` only accepts children of type `Card`.'
          );
        }
      });
      

      【讨论】:

      • 我的代码有一个错误:[ts] 这个条件总是返回 'false',因为类型 'string |组件类 | StatelessComponent' 和 'typeof MyChildClass' 没有重叠。 [2367]
      【解决方案7】:

      我为此创建了一个自定义 PropType,我称之为 equalTo。你可以这样使用它...

      class MyChildComponent extends React.Component { ... }
      
      class MyParentComponent extends React.Component {
        static propTypes = {
          children: PropTypes.arrayOf(PropTypes.equalTo(MyChildComponent))
        }
      }
      

      现在,MyParentComponent 仅接受 MyChildComponent 的子级。您可以检查这样的 html 元素...

      PropTypes.equalTo('h1')
      PropTypes.equalTo('div')
      PropTypes.equalTo('img')
      ...
      

      这里是实现...

      React.PropTypes.equalTo = function (component) {
        return function validate(propValue, key, componentName, location, propFullName) {
          const prop = propValue[key]
          if (prop.type !== component) {
            return new Error(
              'Invalid prop `' + propFullName + '` supplied to' +
              ' `' + componentName + '`. Validation failed.'
            );
          }
        };
      }
      

      您可以轻松地扩展它以接受许多可能的类型之一。也许像......

      React.PropTypes.equalToOneOf = function (arrayOfAcceptedComponents) {
      ...
      }
      

      【讨论】:

        【解决方案8】:
        static propTypes = {
        
          children : (props, propName, componentName) => {
                      const prop = props[propName];
                      return React.Children
                               .toArray(prop)
                               .find(child => child.type !== Card) && new Error(`${componentName} only accepts "<Card />" elements`);
          },
        
        }
        

        【讨论】:

          【解决方案9】:

          您可以向您的Card 组件添加一个道具,然后在您的CardGroup 组件中检查此道具。这是在 React 中实现这一目标的最安全方法。

          这个 prop 可以作为 defaultProp 添加,所以它总是存在的。

          class Card extends Component {
          
            static defaultProps = {
              isCard: true,
            }
          
            render() {
              return (
                <div>A Card</div>
              )
            }
          }
          
          class CardGroup extends Component {
          
            render() {
              for (child in this.props.children) {
                if (!this.props.children[child].props.isCard){
                  console.error("Warning CardGroup has a child which isn't a Card component");
                }
              }
          
              return (
                <div>{this.props.children}</div>
              )
            }
          }
          

          使用 typedisplayName 检查 Card 组件是否确实是 Card 组件并不安全,因为它在生产使用期间可能无法正常工作,如下所示:https://github.com/facebook/react/issues/6167#issuecomment-191243709

          【讨论】:

          • 这似乎是我可以在生产构建中使用它的唯一方法。我有一个组件可以克隆它的子元素,同时只向特定类型的元素添加额外的道具。
          【解决方案10】:

          我发布了允许验证 React 元素类型的包https://www.npmjs.com/package/react-element-proptypes

          const ElementPropTypes = require('react-element-proptypes');
          
          const Modal = ({ header, items }) => (
              <div>
                  <div>{header}</div>
                  <div>{items}</div>
              </div>
          );
          
          Modal.propTypes = {
              header: ElementPropTypes.elementOfType(Header).isRequired,
              items: React.PropTypes.arrayOf(ElementPropTypes.elementOfType(Item))
          };
          
          // render Modal 
          React.render(
              <Modal
                 header={<Header title="This is modal" />}
                 items={[
                     <Item/>,
                     <Item/>,
                     <Item/>
                 ]}
              />,
              rootElement
          );
          

          【讨论】:

            【解决方案11】:

            为了验证正确的子组件,我结合了react children foreachCustom validation proptypes 的使用,所以最后你可以得到以下内容:

            HouseComponent.propTypes = {
            children: PropTypes.oneOfType([(props, propName, componentName) => {
                let error = null;
                const validInputs = [
                'Mother',
                'Girlfried',
                'Friends',
                'Dogs'
                ];
                // Validate the valid inputs components allowed.
                React.Children.forEach(props[propName], (child) => {
                        if (!validInputs.includes(child.type.name)) {
                            error = new Error(componentName.concat(
                            ' children should be one of the type:'
                                .concat(validInputs.toString())
                        ));
                    }
                });
                return error;
                }]).isRequired
            };
            

            如你所见,数组的名称是正确的类型。

            另一方面,airbnb/prop-types 库中还有一个名为 componentWithName 的函数,它有助于获得相同的结果。 Here you can see more details

            HouseComponent.propTypes = {
                children: PropTypes.oneOfType([
                    componentWithName('SegmentedControl'),
                    componentWithName('FormText'),
                    componentWithName('FormTextarea'),
                    componentWithName('FormSelect')
                ]).isRequired
            };
            

            希望这对某人有所帮助:)

            【讨论】:

              【解决方案12】:

              对我来说,实现这一目标的最简单方法是使用以下代码。

              示例 1:

              import React, {Children} from 'react';
              
              function myComponent({children}) {
              
                return (
                  <div>{children && Children.map(children, child => {
                    if (child.type === 'div') return child
                  })}</div>
                )
              }
              
              export default myComponent;
              

              示例 2 - 使用组件

              import React, {Children} from 'react';
              
              function myComponent({children}) {
              
                return (
                  <div>{children && Children.map(children, child => {
                    if (child.type.displayName === 'Card') return child
                  })}</div>
                )
              }
              
              export default myComponent;
              

              【讨论】:

                【解决方案13】:

                考虑了多种提议的方法,但结果证明它们要么不可靠,要么过于复杂,无法用作样板。确定了以下实现。

                class Card extends Component {
                  // ...
                }
                
                class CardGroup extends Component {
                  static propTypes = {
                    children: PropTypes.arrayOf(
                      (propValue, key, componentName) => (propValue[key].type !== Card)
                        ? new Error(`${componentName} only accepts children of type ${Card.name}.`)
                        : null
                    )
                  }
                  // ...
                }
                

                以下是关键想法:

                1. 利用内置的PropTypes.arrayOf() 而不是循环遍历子节点
                2. 在自定义验证器中通过propValue[key].type !== Card 检查子类型
                3. 使用变量替换 ${Card.name} 不硬编码类型名称

                react-element-proptypesElementPropTypes.elementOfType() 中实现了这一点:

                import ElementPropTypes from "react-element-proptypes";
                
                class CardGroup extends Component {
                  static propTypes = {
                    children: PropTypes.arrayOf(ElementPropTypes.elementOfType(Card))
                  }
                  // ...
                }
                

                【讨论】:

                  【解决方案14】:

                  断言类型:

                  props.children.forEach(child =>
                    console.assert(
                      child.type.name == "CanvasItem",
                      "CanvasScroll can only have CanvasItem component as children."
                    )
                  )
                  

                  【讨论】:

                  • 假设它是一个数组,迭代 children 道具是不可靠的。它可以很好地为空或单个节点。 React 提供了方便的实用程序来处理孩子,例如 React.Children.forEach()。见官方文档reactjs.org/docs/react-api.html#reactchildren
                  【解决方案15】:

                  简单、生产友好的检查。在 CardGroup 组件的顶部:

                  const cardType = (<Card />).type;
                  

                  然后,当迭代孩子时:

                  React.children.map(child => child.type === cardType ? child : null);
                  

                  这项检查的好处在于,它还可以与库组件/子组件一起使用,这些库组件/子组件没有公开必要的类以使 instanceof 检查工作。

                  【讨论】:

                    猜你喜欢
                    • 2020-04-10
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-01-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多