带你彻底搞清React.Children.map 和 cloneElement、createElement用法

697 阅读4分钟

刚到公司,组长铁蛋屁颠屁颠过来了:光哥,今天搞个需求。给项目中所有新建和编辑页的“提交”和“暂存”按钮边上补个“关闭”按钮。同时对之前的这些提交按钮,加个逻辑,在成功执行完之前的提交请求后,在控制台打印一个‘我爱996’。 需求不多,就涉及几十个页面,下班之前能搞定吧? 我:好的领导(你妹的)。

琢磨了一下最好能封个公共组件,直接把之前的按钮包进去,然后巴巴拉拉全套整齐了,多好。

思路:

  • 要接受并展示原页面按钮
  • 新增一个按钮
  • 保持原理按钮的点击事件,并且要接受到原请求成功后的一个通知

实现:

  • 传入页面按钮
<HOCBtn>
    <Button type="primary" onClick={() => onFinish(0)}>
      暂存
    </Button>
    <Button type="primary" onClick={() => onFinish(1)}>
      提交
    </Button>
</HOCBtn>
  • 通过props.children接受到,对其进行遍历,获取到每个子元素然后筛选clone出“Button”,增加点击事件,当新事件点击的时候,调用他原来的点击事件,并拿到返回值作为参数,根据这个值在恰当的时间输出‘我爱996’
import React, { useEffect } from "react";
import { Button } from "antd";

const HOCBtn = ({ children }) => { 
  // 监听子元素点击,当有一个返回true,并执行方法
  const handleOnClickChild = async (peddlingFlag) => {
    let flag = await peddlingFlag;
    if (flag) {
      console.log('我爱996')
    }
  };

  // 渲染子元素,插入自定义方法
  const renderChildren = (child) => {
    if (child.type === Button) {
      return React.cloneElement(child, {
        onClick: () => {
          handleOnClickChild(child.props.onClick());
        },
      });
    } else {
      return child;
    }
  };

  const onCancel = () => {
  };

  return (
    <div className="foot-btn">
      <Button onClick={onCancel}>关闭</Button>
      {React.Children.map(children, renderChildren)}
    </div>
  );
};

export default HOCBtn;

铁蛋:搞的不错嘛。 看了下时间还有半小时下班,接着说:光哥,我这还有个小需求。各个列表页的Table操作项里面的操作内容太多了,操能不能把操作项分个类,除原'查看'和'编辑'保留单独操作项,其余的都丢到DropDown里去。大概几十个页面。

思路:

  • 对原来所有的按钮根据名称,进行分类
  • 查看 和 编辑拎出来不变,其余项进行整合

实现:

  • 使用
<DropdownMenu>
  <a onClick={}>{translateTitle("取消询价")}</a>
  <a onClick={}>{translateTitle("分配询价员")}</a>
  <a onClick={}>{translateTitle("查看")}</a>
</DropdownMenu>

  • 处理
const WHITE_LIST = [translateTitle("查看"), translateTitle("编辑")]; // 白名单
const DropdownMenu = (props: Props) => {
  const { children } = props;
  // 获取所有的 a 标签
  const aTags = React.Children.toArray(children);
  // 过滤出不等于查看的 a 标签
  const filteredTags = aTags.filter((tag: any) => !WHITE_LIST.includes(tag.props.children));
  //固定的a标签
  const fixedTags = aTags.filter((tag: any) => WHITE_LIST.includes(tag.props.children));

  let dropDownList = React.createElement(
    "div",
    {
      className: "drop-down ant-dropdown-menu",
    },
    filteredTags
  );

  // 把过滤后的 a 标签放进 dropdown 组件中,让用户做出选择
  return (
    <div className="action-self">
      {fixedTags}
      {filteredTags?.length ? (
        <Dropdown overlay={dropDownList}>
          <span className="more-action">{translateTitle("操作")}</span>
        </Dropdown>
      ) : null}
    </div>
  );
};

知识点解析:

  1. React.Children.map: 用于遍历 React 组件的子元素。它有两个参数:第一个参数是组件的子元素 prop,第二个参数是一个函数,它将被调用,并以每个子元素作为参数。该函数应该返回一个修改后的子元素版本,它将用于新数组中由 map 方法返回的结果。
  2. React.cloneElement: 用于克隆并返回一个新的 React 元素,同时可以为该元素指定新的 props。它接收两个参数:第一个参数是要克隆的 React 元素,第二个参数是一个可选的对象,用于指定新的 props。克隆后的元素将保留原始元素的 key 和 ref,同时也继承了新的 props。该方法通常用于在不改变原始元素的情况下,修改元素的 props 或增加新的 props。
  3. React.createElement: 用于创建并返回一个新的虚拟 DOM 元素。它接受三个参数:元素的类型(可以是表示 HTML 标签的字符串或表示 React 组件的函数)、包含要分配给元素的任何属性的对象,以及元素的任何子元素。通常在 JSX 中使用此函数来创建和渲染 React 组件。
  4. React.Children.toArray: 用于将 props.children 转换为一个扁平的数组。它接收一个 props.children 作为参数,并返回一个数组,其中包含所有的子元素(包括字符串、数字、元素或者数组)。
      有时候直接对 props.children 进行操作,可能会出现一些问题:
      首先,当 props.children 只有一个子元素时,它的类型不是数组,而是一个单独的 React 元素。如果直接对它进行数组操作,会导致类型错误。此外,当 props.children 没有子元素时,它的值为 null,而不是一个空数组,这也可能导致类型错误或者其他问题。
      另外,当 props.children 包含嵌套的子元素时,如果不对它进行递归处理,可能会无法访问到所有的子元素,或者无法正确处理子元素的嵌套结构。 因此,为了避免这些问题,建议在对 props.children 进行操作之前,先使用 React.Children.toArray 将其转换为一个扁平的数组,这样可以方便地遍历和处理所有的子元素,无论它们的类型和数量。