如何给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里的值代表什么,这里就不再钻下去了。

相关推荐
正小安26 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript