Подтвердить что ты не робот

Допускают только детей определенного типа в реагирующем компоненте

У меня есть компонент Card и компонент CardGroup, и я бы хотел выбросить ошибку, когда CardGroup имеет дочерние элементы, которые не являются Card компонентами. Возможно ли это, или я пытаюсь решить неправильную проблему?

4b9b3361

Ответ 1

Вы можете использовать displayName для каждого дочернего элемента, доступ к которому осуществляется через тип:

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");
  }  
}

Ответ 2

Для 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
  }
}

Ответ 3

Вы можете использовать пользовательскую функцию propType для проверки дочерних элементов, поскольку дети - только реквизиты. Я также написал статью об этом, если вы хотите получить более подробную информацию.

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>
    );
  }
});

Ответ 4

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`);
  },

}

Ответ 5

Я создал специальный 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) {
...
}

Ответ 6

Вы можете добавить опору к вашему компоненту Card, а затем проверить эту опору в своем компоненте CardGroup. Это самый безопасный способ достичь этого в Реактите.

Эта поддержка может быть добавлена ​​как 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>
    )
  }
}

Проверка того, действительно ли компонент карты действительно является компонентом карты с помощью type или displayName, небезопасен, поскольку он может не работать во время производственного использования, как указано здесь: https://github.com/facebook/react/issues/6167#issuecomment-191243709

Ответ 7

Для таких, как я, с использованием версии 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;
        });

Ответ 8

Нужно использовать "React.isValidElement(child)" вместе с "child.type", если вы работаете с Typescript, чтобы избежать ошибок несоответствия типов.

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

Ответ 9

Я опубликовал пакет, который позволяет проверять типы элементов 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
);

Ответ 10

Для проверки правильности дочернего компонента я комбинирую использование подходящих дочерних элементов foreach и настраиваемых типов проверки, поэтому в конце вы можете получить следующее:

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
};

Как видите, есть и массив с именем правильного типа.

С другой стороны, есть и функция componentWithName из библиотеки airbnb/prop-types, которая помогает получить тот же результат. Здесь вы можете увидеть более подробную информацию

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

Надеюсь, это поможет кому-то :)

Ответ 11

Используйте метод React.Children.forEach для перебора 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

Ответ 12

На самом деле это можно легко сделать с помощью машинописи во время компиляции:

interface Constructor<T, A extends Array<unknown> = Array<any>> extends Function {
  new(...args: A): T;
  prototype: T;
}

interface HasChildrenOfGivenType<C extends Constructor<Component>> {
  children?: ReactElement<any, C> | Array<ReactElement<any, C>> | null | undefined;
}

abstract class AcceptChildOfGivenTypeOnly<C extends Component, P extends HasChildrenOfGivenType<Constructor<C>> = HasChildrenOfGivenType<Constructor<C>>, S extends object = {}> extends Component<P, S> {
}

class Child extends Component<{ name: string }> {
}

class Parent extends AcceptChildOfGivenTypeOnly<Child> {
  render(): ReactNode {
    return <ul>
      {this.children.map((child, i) => <li>{i} {child.props.name}</li>)}
    </ul>
  }
  protected get children(): Array<ReactElement<any, Constructor<Child>>> {
    return this.props.children ?
      Array.isArray(this.props.children) ?
        this.props.children as Array<any> : [this.props.children] : [];
  }
}

const html: ReactNode = <Fragment>
  <Parent>
    <Child name="child1" />
    <Child name="child2" />
  </Parent>
  <Parent>
    <Child name="child3" />
  </Parent>
  <Parent></Parent>
</Fragment>;