У меня есть компонент Card
и компонент CardGroup
, и я бы хотел выбросить ошибку, когда CardGroup
имеет дочерние элементы, которые не являются Card
компонентами. Возможно ли это, или я пытаюсь решить неправильную проблему?
Допускают только детей определенного типа в реагирующем компоненте
Ответ 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>;