如何给React组件里的children设置属性?由浅入深看看封装容器组件的三个级别

直接放答案:

js 复制代码
const newElement = React.cloneElement(props.children, {
  extraProp: "Some extra prop",
});

然而在实际工作中情况会更复杂,尤其是在封装容器类组件时会用到这个功能。下面我由浅入深,分三个级别,介绍实际中如何应对此场景越来越复杂的需求。

第一级

将配置的JSON(数组)传给组件

比如children是个list,且需要将每个list项设置相对应的属性。这个常见于封装可遍历组件时,比如封装一个Tabs组件,拿MUI来举例:

MUI官方实现的代码是这样的:

jsx 复制代码
// Page.tsx
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
  <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
    <Tab label="第一项" {...a11yProps(0)} />
    <Tab label="第二项" {...a11yProps(1)} />
    <Tab label="第三项" {...a11yProps(2)} />
  </Tabs>
</Box>
<TabPanel value={value} index={0}>
  <div>第一项</div>
</TabPanel>
<TabPanel value={value} index={1}>
  <div>第二项</div>
</TabPanel>
<TabPanel value={value} index={2}>
  <div>第三项</div>
</TabPanel>

这时,我们可以把这个组件封装成这样:

jsx 复制代码
// MyTab.tsx
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
    <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
        {props.items.map((item, index) => (
            <Tab key={...} label={item.label} {...a11yProps(index)} />
        )}
    </Tabs>
    {props.items.map((item, index) => (
        <TabPanel key={...} value={value} index={index}>
          {item.content}
        </TabPanel>
    )}
</Box>

这样,对于这个封装好的组件,我们可以这样用:

jsx 复制代码
// Page.tsx
<MyTabs
  items={[
    {
      label: "第一项",
      content: <div>第一项</div>
    },
    {
      label: "第二项",
      content: <div>第二项</div>
    },
    {
      label: "第三项",
      content: <div>第三项</div>
    },
  ]}
/>

这样确实是一种方式, AntDesign也就是这么做的。但是如果使用者想要自由选择TabPanelCustomTabPanel呢?

第二级

利用React.Children.toArrayReact.Children.map功能处理传进来的children

如果我们允许MyTabs组件接受整个TabPanelCustomTabPanel,那我们该如何将valueindex传给这个TabPanelCustomTabPanel呢?

所以这里使用者需要像这样写:

jsx 复制代码
// Page.tsx
<MyTabs labels={["第一项", "第二项", "第三项"]}>
    <TabPanel>
        <div>第一项</div>
    </TabPanel>
    <CustomTabPanel>
        <div>第二项</div>
    </CustomTabPanel>
    <TabPanel>
        <div>第三项</div>
    </TabPanel>
</MyTabs>

那么相应的在MyTabs.tsx应该这样封装:

jsx 复制代码
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
    <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
        {props.items.map((item, index) => (
            <Tab key={...} label={item.label} {...a11yProps(index)} />
        )}
    </Tabs>
    {React.Children.map(props.children, (child, index) => (
        React.cloneElement(child, {     // <== 此处用文章开头的方法设置value和index
          value,
          index
        })
    ))}
</Box>

然后现实工作中,真的就到此为止了嘛?

案例三

利用fiber对象里的type.name来判断是什么组件。

如果children不是一个可遍历的重复的元素,而是各不相同的元素,该怎么办呢? 比如封装一个卡片组件,像这样子:

MUI官方给出的示例代码是这样的:

jsx 复制代码
// Page.tsx
<MyCard sx={{ maxWidth: 345 }}>
    <CardMedia
        sx={{ height: 140 }}
        image="/static/images/cards/contemplative-reptile.jpg"
        title="green iguana"
    />
    <CardContent>
        <Typography gutterBottom variant="h5" component="div">
            Lizard
        </Typography>
        <Typography variant="body2" color="text.secondary">
            Lizards are a widespread group of squamate reptiles, with over 6,000
            species, ranging across all continents except Antarctica
        </Typography>
    </CardContent>
    <CardActions>
        <Button size="small">Share</Button>
        <Button size="small">Learn More</Button>
    </CardActions>
</MyCard>

如果我们自己开发这样的Card组件,让使用者可以自由地将CardMedia, CardContent, CardAction等内容作为children传进来,该怎么实现呢?

实际上上面children通过toArray方法遍历得到的一个个child,就是一个个fiber对象,打印出来就是这样:

看到那个type里地name了吗?我们可以用这个来判断是什么组件。

所以我们可以这样封装:

jsx 复制代码
MyCard.tsx
<Box>
    <SomeHeader>...</SomeHeader>
    {React.Children.map(props.children, (child, index) => {
        if (child.type.name === "CardActions") {    //  <=== 如果条件较多,用switch
            return (
                {/* 比如可以把CardActions包起来 */}
                <SomeWrapper>
                    <Divider />  {/* <=== 比如可以在CardActions上面加条分割线 */}
                    React.cloneElement(child, {
                        someProps: "someProps",   // <=== 同时用文章开头的方法加一些属性
                    })
                </SomeWrapper>
            );
        }
        return child;
    })}
</Box>

完美!这样我们可以对传进来的children,根据它是什么组件,做不同的变化。

结尾

这样对于React组件里传进来的children,我们可以自如地处理了。当然一定还有极端复杂的案例,我们需要花时间具体了解React.Children还有哪些用法,和fiber里的值代表什么,这里就不再钻下去了。

相关推荐
百思可瑞教育39 分钟前
在Vue项目中Axios发起请求时的小知识
前端·javascript·vue.js·北京百思教育
患得患失9491 小时前
【个人项目】【前端实用工具】OpenAPI to TypeScript 转换器
前端·javascript·typescript
大前端helloworld1 小时前
前端梳理体系从常问问题去完善-基础篇(html,css,js,ts)
前端·javascript·面试
trsoliu1 小时前
前端基于 TypeScript 使用 Mastra 来开发一个 AI 应用 / AI 代理(Agent)
前端·人工智能
鸡吃丸子1 小时前
前端权限控制:深入理解与实现RBAC模型
前端
Larry_zhang双栖1 小时前
低版本Chrome 内核兼容性问题的优美解决
前端·chrome
qq_12498707532 小时前
基于node.js+vue的医院陪诊系统的设计与实现(源码+论文+部署+安装)
前端·vue.js·node.js·毕业设计
袁煦丞2 小时前
9.12 Halo的“傻瓜建站魔法”:cpolar内网穿透实验室第637个成功挑战
前端·程序员·远程工作
universe_013 小时前
day27|前端框架学习
前端·笔记
沙尘暴炒饭3 小时前
前端vue使用canvas封装图片标注功能,鼠标画矩形框,标注文字 包含下载标注之后的图片
前端·vue.js·计算机外设