React.Children 将(函数)字符串返回为带有打字稿的自定义组件的“类型”

问题描述 投票:0回答:2

这听起来可能很奇怪,也许我一开始就完全错了。但是,当我阅读一些与获取子级相关的文章和反应文档并通过

React.Component.map()
识别特定子级时,但是当我使用自定义组件尝试此操作时,子级将返回 stringify 函数作为
type
。 (我知道 React 会进行字符串化以防止脚本注入)。但我基本上需要识别进入组件的特定子组件,并将它们放置在另一个自定义组件中的正确位置。 (material_ui 风格)。

<Card>
 <CardTitle>
 </CardTitle>
 <CardContent>
 </CardContent>
</Card>

问题是我无法映射传递的子项,因为

type
有一个字符串。 我的环境使用

"react": "^17.0.2",
"@types/react": "^17.0.0",
"react-dom": "^17.0.2",
"@types/react-dom": "^17.0.0",
"typescript": "^4.1.2"

这就是我到目前为止所拥有的

type ExpandableCardProps = {
 children: ReactElement<any> | ReactElement<any>[],
}
const ExpandableCard = ({children}: ExpandableCardProps) => {

 React.Children.map(children, (child) => {
  concole.log(child); // just can't map the child elements as described in some articales
 })
 

 // note that I need to identify the correct child to be render in correct position
 render (
  <div>
   <div className="title-container">
    // I want to render <ExpandableTitle> here
   </div>
   <div className="content-container">
    // I want to render <ExpandableContent> here
   </div>
   <div className="content-other">
    // may be some other elements here
   </div>
  </div>
 );
}

export default ExpandableCardProps;

type CommonType = {
 children: ReactNode;
}

export const ExpandableTitle ({children}:CommonType) => {
 <div>
 {children}
 </div>
}

export const ExpandableContent ({children}:CommonType) => {
 <div>
 {children}
 </div>
}
// usage
<ExpandableCard>
 <ExpandableTitle>
  /*{ some jsx here }*/
 </ExpandableTitle>
 <ExpandableContent>
  /*{ some jsx here }*/
 </ExpandableContent>
</ExpandableCard>

这是控制台中的样子

这是我提到的一篇文章,它最接近地解释了我需要的东西,但是自从

type
stringify 事情以来,不能使用它解释的模式,想知道它是在 React 版本中,或者也许正如我之前提到的,它完全是被我自己误解了。我需要对此有一些了解。我怎样才能实现这样的目标?

javascript reactjs typescript jsx children
2个回答
1
投票

这似乎对我有用:

const ExpandableCard = ({children}) => {

    const childArray = React.Children.toArray(children);

    const expandableTitleIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableTitle');
    const expandableContentIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableContent');

    const additionalChildren = childArray.filter((_, index) => (index !== expandableTitleIndex && index !== expandableContentIndex));

    return [childArray[expandableTitleIndex], childArray[expandableContentIndex], ...additionalChildren];
};

const App = () => {
    return (
    <ExpandableCard>
      <div>Child 0 (div)</div>
      <ExpandableContent>Child 1 (ExpandableContent)</ExpandableContent>
      <ExpandableTitle>Child 2 (ExpandableTitle)</ExpandableTitle>
      <div>Child 3 (div)</div>
    </ExpandableCard>
  );

};

const ExpandableTitle = ({children}) => (
 <div>
 {children}
 </div>
);

ExpandableTitle.defaultProps = {
  __TYPE: 'ExpandableTitle',
};

const ExpandableContent = ({children}) => (
 <div>
 {children}
 </div>
);

ExpandableContent.defaultProps = {
  __TYPE: 'ExpandableContent',
};

ReactDOM.render(<App />, document.querySelector("#app"));

在 jsFiddle 上直播


0
投票

在对 Neal Burns 答案进行一些解决方法之后,我得出了

typescript
兼容的解决方案。

我会将其发布在这里,因为对于某些人来说,有一天它可能会派上用场。

import React, { Children, ReactElement, ReactNode, useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './expandableCard.scss';
import { v4 as uuidv4 } from 'uuid'

const types = {
  EXPANDABLE_CARD_HEADER: 'expandableCardTitle',
  EXPANDABLE_CARD_CONTENT: 'expandableCardContent',
  EXPANDABLE_CARD_FOOTER: 'expandableCardFooter',
  EXPANDABLE_ITEMS: 'expandableItems',
}

type ExpandableCardProps = {
  id?: string;
  select?: boolean;
  onSelect?: (id: string) => void;
  children: ReactElement<ExpandableCardContentProps> | ReactElement<ExpandableCardContentProps>[];
}

const ExpandableCard = ({ id = uuidv4(), select = false, children, onSelect = () => { } }: ExpandableCardProps) => {
  const transitionRef = useRef(null);
  const [selected, setSelected] = useState(select);
  const [expand, setExpand] = useState(false);
  const headerElement = useRef<any>(null);
  const contentElement = useRef<any>(null);
  const expandableFooter = useRef<any>(null);
  const expandableItems = useRef<any>(null);

  const handleSelected = () => {
    setSelected(!selected);
  }

  useEffect(() => {
    if (selected) {
      onSelect(id);
    }
  }, [id, onSelect, selected])

  const handleExpand = () => {
    setExpand(!expand);
  }

  Children.forEach(children, (child) => {
    switch (child.props.__TYPE) {
      case types.EXPANDABLE_CARD_HEADER:
        headerElement.current = child;
        break;
      case types.EXPANDABLE_CARD_CONTENT:
        contentElement.current = child;
        break;
      case types.EXPANDABLE_ITEMS:
        expandableItems.current = child;
        break;
      case types.EXPANDABLE_CARD_FOOTER:
        expandableFooter.current = child;
        break;
      default:
        return <div></div>;
    }
  });

  return (
    <div className={`expandable-card ${selected ? 'expandable-card-selected' : ''}`}>
      <div className={`expandable-card--content ${expand ? 'expandable-card--content-active' : ''}`}>
        <div className="expandable-card--expand-button">
          <button type="button" onClick={handleExpand}>expand</button>
        </div>
        {headerElement.current &&
          <div className="expandable-card--header">
            {headerElement.current}
          </div>
        }
        {contentElement.current}
        <div className="d-flex align-items-center mt-3">
          <button
            type="button"
            className={`btn expandable-card--button ${selected ? 'expandable-card--button-active' : ''}`}
            onClick={handleSelected}>
            {selected && !}
          </button>
          {expandableFooter.current}
        </div>
      </div>

      <CSSTransition
        nodeRef={transitionRef}
        in={expand}
        timeout={500}
        classNames={`expandable-card--drawer`}
        mountOnEnter
        unmountOnExit>
        <div ref={transitionRef} className="expandable-card--drawer">
          {expandableItems.current}
        </div>
      </CSSTransition>
    </div >
  );
}

type ExpandableCardContentProps = {
  children: ReactNode,
  __TYPE: string;
}

export const ExpandableCardHeader = ({ children }: ExpandableCardContentProps) => {
  return (
    <>
      {children}
    </>
  );
}

ExpandableCardHeader.defaultProps = {
  __TYPE: types.EXPANDABLE_CARD_HEADER,
}

export const ExpandableCardContent = ({ children }: ExpandableCardContentProps) => (
  <>
    {children}
  </>
);

ExpandableCardContent.defaultProps = {
  __TYPE: types.EXPANDABLE_CARD_CONTENT,
}

export const ExpandableCardFooter = ({ children }: ExpandableCardContentProps) => (
  <>
    {children}
  </>
);

ExpandableCardFooter.defaultProps = {
  __TYPE: types.EXPANDABLE_CARD_FOOTER,
}

export const ExpandableItems = ({ children }: ExpandableCardContentProps) => (
  <>
    {children}
  </>
);

ExpandableItems.defaultProps = {
  __TYPE: types.EXPANDABLE_ITEMS,
}

export default ExpandableCard;

请注意,这是带有动画的完整可扩展组件 我也会把 SCSS 代码贴出来,这样就完成了

.expandable-card {
  display: flex;
  flex-direction: column;
  box-shadow: 0 0px 25px 0px rgba(0, 0, 0, 0.2);
  width: 100%;
  background-color: #fff;
  border-radius: 14px;
  position: relative;

  &--expand-button {
    position: absolute;
    top: 10px;
    right: 15px;
  }

  &-selected {
    border-bottom: 15px solid yellow;
    border-radius: 14px;
  }

  &--content {
    padding: 18px 15px;
    border-radius: 14px 14px 0 0;
    transition: all 500ms ease-out;

    &-active {
      z-index: 1;
      box-shadow: 0 7px 7px 0 rgba(0, 0, 0, 0.2);
    }
  }

  &--drawer {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-height: 0;
    background-color: #fff;
    padding: 18px 20px;
    border-radius: 0 0 14px 14px;
    overflow-x: hidden;
    overflow-y: auto;
    transition: all 500ms ease-out;

    /* .classes for help dropdown animations */
    &-enter-active {
      max-height: 320px;
      padding: 18px 20px;
    }
    &-enter-done {
      max-height: 320px;
      padding: 18px 20px;
    }
    &-exit-active {
      max-height: 0;
      padding: 0 20px;
    }
    &-exit-done {
      max-height: 0;
      padding: 0 20px;
    }
  }

  &--header {
    display: flex;
    align-items: center;
  }

  &--button {
    min-width: 43px;
    height: 43px;
    background: transparent;
    border: 2px solid aqua;
    box-sizing: border-box;
    border-radius: 10px;

    &:focus {
      box-shadow: none;
    }

    &-active {
      background-color: blue;
      border: none;
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.