Reactjs 如果组件未包含在特定父组件中,则显示警告

Reactjs 如果组件未包含在特定父组件中,则显示警告,reactjs,Reactjs,解决方案: 我正在创建一个组件,我只想在组件的子级或非子级时显示警告 我已经做了一件事来解决这个问题,但我不满意,我认为这是丑陋的 export const CardBody = ({ children, isInCard }) => { if (!isInCard) console.warning('Not in card') return <div className="cardbody">{children}</div> }

解决方案:

我正在创建一个组件,我只想在组件的子级或非子级时显示警告

我已经做了一件事来解决这个问题,但我不满意,我认为这是丑陋的

export const CardBody = ({ children, isInCard }) => {
  if (!isInCard)
    console.warning('Not in card')
  return <div className="cardbody">{children}</div>
}
export const CardHeader = ({ children, isInCard }) => {
  if (!isInCard)
    console.warning('Not in card')
  return <div className="cardheader">{children}</div>
}
export const CardBodyContainer = ({ children, isInCard }) => {
  if (!isInCard)
    console.warning('Not in card')
  return <div className="cardbodycontainer">{children}</div>
}

export const Card = ({ children }) => {
  if (children.length > 0) {
    children = children.map((child, index) => {
      if (
        [
          CardHeader.displayName,
          CardBody.displayName,
          CardBodyContainer.displayName,
        ].includes(child.type.displayName)
      ) {
        return React.cloneElement(child, {
          ...child.props,
          key: index,
          isInCard: true,
        });
      }
      return child;
    });
  }

  return <div>{children}</div>
};
export const CardBody=({children,isInCard})=>{
如果(!isInCard)
控制台。警告('不在卡中')
返回{children}
}
export const CardHeader=({children,isInCard})=>{
如果(!isInCard)
控制台。警告('不在卡中')
返回{children}
}
export const CardBodyContainer=({children,isInCard})=>{
如果(!isInCard)
控制台。警告('不在卡中')
返回{children}
}
导出常量卡=({children})=>{
如果(children.length>0){
children=children.map((子,索引)=>{
如果(
[
CardHeader.displayName,
CardBody.displayName,
CardBodyContainer.displayName,
].includes(child.type.displayName)
) {
返回React.cloneElement(子级{
…儿童道具,
关键词:索引,,
伊辛卡德:没错,
});
}
返回儿童;
});
}
返回{children}
};
这是与以下方面合作的:

<Card>
  <CardBody/>
</Card>

但它不适用于:

// displays warning even CardBody is in Card
<Card>
  <div>
    <CardBody/>
  </div>
</Card>
//即使卡体在卡中也会显示警告
简短解释 简言之,你在孩子们的内心深处只有一个层次。您必须递归遍历子对象的子对象以指定
isInCard
属性

长篇大论 React将其子对象组合为一棵树:

(2) ["Card", Object]
    0: "Card"
    1: Object
      type: ƒ CardBodyContainer() {}
      key: null
      ref: null
      props: Object
      children: Array(2)
        0: "Body Container"
        1: Object
          type: "div"
          key: null
          ref: null
          props: Object
          children: Array(2)
            0: Object
            1: Object
            _owner: FiberNode
            _store: Object
        _owner: FiberNode
        _store: Object
其中
儿童
可能有自己的
儿童
和自己的
道具
。因此,您必须递归遍历所有
子项
,并有条件地为它们分配
道具
。不幸的是,您无法将属性分配给所有子项,因为有时这些子项可能是HtmleElement或string/boolean/number。因此,您不能/不应该将道具分配给非反应元素

演示

代码 Example.js

import * as React from "react";

export const CardBody = ({ children, isInCard }) => {
  if (!isInCard) {
    console.error("CardBody was not used within a Card component");
    return <div className="error">Invalid CardBody</div>;
  }
  return <div className="cardbody">{children}</div>;
};
export const CardHeader = ({ children, isInCard }) => {
  if (!isInCard) {
    console.error("CardHeader was not used within a Card component");
    return <div className="error">Invalid CardHeader</div>;
  }
  return <div className="cardheader">{children}</div>;
};

const CardBodyContainerComponent = ({ children }) => (
  <div className="cardbodycontainer">{children}</div>
);

/**
 * A HOC that checks if a Card Component contains an ```isInCard``` property
 *
 * @function withCardCheck
 * @param Component - a React component
 * @param type - a string of the Card component type that was used
 * @returns {ReactElement}
 * @example withCardCheck(Component, "example")
 */
const withCardCheck = (Component, type) => {
  const wrappedComponent = (props) => {
    if (!props.isInCard) {
      console.error(`${type} was not used within a Card component`);
      // throw Error(`${type} was not used within a Card component`);
      return <div className="error">Invalid {type}</div>;
    }

    return <Component {...props} />;
  };

  return wrappedComponent;
};

export const CardBodyContainer = withCardCheck(CardBodyContainerComponent, "CardBodyContainer"
);

/**
 * Checks if the child is a function (React functional component) and
 * conditionally assigns it an ```isInCard``` property
 *
 * @function childContainsCardElements
 * @param child - a React child component
 * @returns {Object} either empty object or { isInCard: true }
 * @example childContainsCardElements(element)
 */
const childContainsCardElements = (child) =>
  typeof child.type === "function" &&
  [
    CardHeader.displayName,
    CardBody.displayName,
    CardBodyContainer.displayName
  ].includes(child.type.displayName)
    ? { isInCard: true }
    : {};

/**
 * Recursively iterates through children
 *
 * @function recursiveMap
 * @returns {ReactElement}
 * @example recursiveMap(children)
 */
const recursiveMap = (children) =>
  React.Children.map(children, (child) => {
    // checks if the child is a valid React element
    if (!React.isValidElement(child)) return child;

    // checks if child have their own children
    if (child.props.children) {
      // if so, then conditionally assign it an "isInCard" prop
      child = React.cloneElement(child, {
        ...childContainsCardElements(child),
        children: recursiveMap(child.props.children)
      });
    }

    return child;
  });

export const Card = ({ children }) => <div>{recursiveMap(children)}</div>;
import { Card, CardHeader, CardBody, CardBodyContainer } from "./Example";
import "./styles.css";

const App = () => (
  <div className="App">
    <h2 className="success">Valid</h2>
    <Card>Card</Card>
    <div className="divider" />
    <h2 className="success">Valid</h2>
    <Card>
      Card
      <CardBody>CardBody</CardBody>
    </Card>
    <div className="divider" />
    <h2 className="success">Valid</h2>
    <Card>
      Card
      <CardBodyContainer>
        CardBodyContainer
        <div>
          <CardHeader>CardHeader</CardHeader>
          <div>
            <CardBody>CardBody</CardBody>
          </div>
        </div>
      </CardBodyContainer>
    </Card>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardBodyContainer>CardBodyContainer</CardBodyContainer>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardBody>Body</CardBody>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardHeader>Header</CardHeader>
    <div className="divider" />
  </div>
);

export default App;
import*as React from“React”;
export const CardBody=({children,isInCard})=>{
如果(!isInCard){
控制台。错误(“卡体未在卡组件中使用”);
返回无效的卡体;
}
返回{children};
};
export const CardHeader=({children,isInCard})=>{
如果(!isInCard){
console.错误(“卡头未在卡组件中使用”);
返回无效的卡头;
}
返回{children};
};
const CardBodyContainerComponent=({children})=>(
{儿童}
);
/**
*检查卡组件是否包含``isInCard``属性的HOC
*
*@具有CardCheck的函数
*@param组件-反应组件
*@param type-使用的卡组件类型的字符串
*@返回{ReactElement}
*@example with cardcheck(组件,“示例”)
*/
const withCardCheck=(组件,类型)=>{
常量wrappedComponent=(道具)=>{
如果(!props.isInCard){
错误(`${type}未在卡组件`)中使用);
//抛出错误(`${type}未在卡组件`)中使用);
返回无效的{type};
}
返回;
};
返回wrappedComponent;
};
export const CardBodyContainer=带CardCheck(CardBodyContainer组件,“CardBodyContainer”
);
/**
*检查子项是否为函数(反应功能组件),以及
*有条件地为其分配一个``isInCard``属性
*
*@function-childescardelements
*@param child-反应子组件
*@返回{Object}空对象或{isInCard:true}
*@示例ChildContainesCardelements(元素)
*/
const childContainsCardElements=(child)=>
typeof child.type==“函数”&&
[
CardHeader.displayName,
CardBody.displayName,
CardBodyContainer.displayName
].includes(child.type.displayName)
? {isInCard:true}
: {};
/**
*递归地遍历子对象
*
*@函数递归映射
*@返回{ReactElement}
*@example recursiveMap(子级)
*/
常量递归映射=(子项)=>
React.Children.map(Children,(child)=>{
//检查子元素是否为有效的React元素
如果(!React.isValidElement(child))返回子项;
//检查孩子是否有自己的孩子
if(child.props.children){
//如果是,则有条件地将其指定为“isInCard”属性
child=React.cloneElement(child{
…儿童(儿童),
子对象:递归映射(child.props.children)
});
}
返回儿童;
});
导出常量卡=({children})=>{recursiveMap(children)};
App.js

import * as React from "react";

export const CardBody = ({ children, isInCard }) => {
  if (!isInCard) {
    console.error("CardBody was not used within a Card component");
    return <div className="error">Invalid CardBody</div>;
  }
  return <div className="cardbody">{children}</div>;
};
export const CardHeader = ({ children, isInCard }) => {
  if (!isInCard) {
    console.error("CardHeader was not used within a Card component");
    return <div className="error">Invalid CardHeader</div>;
  }
  return <div className="cardheader">{children}</div>;
};

const CardBodyContainerComponent = ({ children }) => (
  <div className="cardbodycontainer">{children}</div>
);

/**
 * A HOC that checks if a Card Component contains an ```isInCard``` property
 *
 * @function withCardCheck
 * @param Component - a React component
 * @param type - a string of the Card component type that was used
 * @returns {ReactElement}
 * @example withCardCheck(Component, "example")
 */
const withCardCheck = (Component, type) => {
  const wrappedComponent = (props) => {
    if (!props.isInCard) {
      console.error(`${type} was not used within a Card component`);
      // throw Error(`${type} was not used within a Card component`);
      return <div className="error">Invalid {type}</div>;
    }

    return <Component {...props} />;
  };

  return wrappedComponent;
};

export const CardBodyContainer = withCardCheck(CardBodyContainerComponent, "CardBodyContainer"
);

/**
 * Checks if the child is a function (React functional component) and
 * conditionally assigns it an ```isInCard``` property
 *
 * @function childContainsCardElements
 * @param child - a React child component
 * @returns {Object} either empty object or { isInCard: true }
 * @example childContainsCardElements(element)
 */
const childContainsCardElements = (child) =>
  typeof child.type === "function" &&
  [
    CardHeader.displayName,
    CardBody.displayName,
    CardBodyContainer.displayName
  ].includes(child.type.displayName)
    ? { isInCard: true }
    : {};

/**
 * Recursively iterates through children
 *
 * @function recursiveMap
 * @returns {ReactElement}
 * @example recursiveMap(children)
 */
const recursiveMap = (children) =>
  React.Children.map(children, (child) => {
    // checks if the child is a valid React element
    if (!React.isValidElement(child)) return child;

    // checks if child have their own children
    if (child.props.children) {
      // if so, then conditionally assign it an "isInCard" prop
      child = React.cloneElement(child, {
        ...childContainsCardElements(child),
        children: recursiveMap(child.props.children)
      });
    }

    return child;
  });

export const Card = ({ children }) => <div>{recursiveMap(children)}</div>;
import { Card, CardHeader, CardBody, CardBodyContainer } from "./Example";
import "./styles.css";

const App = () => (
  <div className="App">
    <h2 className="success">Valid</h2>
    <Card>Card</Card>
    <div className="divider" />
    <h2 className="success">Valid</h2>
    <Card>
      Card
      <CardBody>CardBody</CardBody>
    </Card>
    <div className="divider" />
    <h2 className="success">Valid</h2>
    <Card>
      Card
      <CardBodyContainer>
        CardBodyContainer
        <div>
          <CardHeader>CardHeader</CardHeader>
          <div>
            <CardBody>CardBody</CardBody>
          </div>
        </div>
      </CardBodyContainer>
    </Card>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardBodyContainer>CardBodyContainer</CardBodyContainer>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardBody>Body</CardBody>
    <div className="divider" />
    <h2 className="error">Error</h2>
    <CardHeader>Header</CardHeader>
    <div className="divider" />
  </div>
);

export default App;
从“/Example”导入{Card,CardHeader,CardBody,CardBodyContainer};
导入“/styles.css”;
常量应用=()=>(
有效的
卡片
有效的
卡片
名片夹
有效的
卡片
卡式集装箱
万向节头
名片夹
错误
卡式集装箱
错误
身体
错误
标题
);
导出默认应用程序;
其他想法 使用
索引
作为
键时要小心。如果子项发生任何更改,那么这可能会导致一些UI问题

另外,我建议不要抛出警告,而是抛出一个错误来摆脱递归。这样,更明显的是,某种结构是预期/强制的,而不是可选的。如果出于任何原因,您不想退出递归,并且希望减少
If(!isInCard)
样板文件,那么您可以使用like
with cardcheck

简短的解释 简言之,你在孩子们的内心深处只有一个层次。您必须递归遍历子对象的子对象以指定
isInCard
属性

长篇大论 React将其子对象组合为一棵树:

(2) ["Card", Object]
    0: "Card"
    1: Object
      type: ƒ CardBodyContainer() {}
      key: null
      ref: null
      props: Object
      children: Array(2)
        0: "Body Container"
        1: Object
          type: "div"
          key: null
          ref: null
          props: Object
          children: Array(2)
            0: Object
            1: Object
            _owner: FiberNode
            _store: Object
        _owner: FiberNode
        _store: Object
其中
children