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

相关推荐
GIS程序媛—椰子26 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00132 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端35 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100939 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt