如何给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也就是这么做的。但是如果使用者想要自由选择TabPanel或CustomTabPanel呢?

第二级

利用React.Children.toArray和React.Children.map功能处理传进来的children。

如果我们允许MyTabs组件接受整个TabPanel或CustomTabPanel,那我们该如何将value和index传给这个TabPanel或CustomTabPanel呢?

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

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

相关推荐
小码哥_常4 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
蜗牛攻城狮6 小时前
微前端四大方案选型对比与最佳实践:qiankun、micro-app、wujie、Module Federation
前端·微前端·qiankun·前端架构·micro-app·wujie
她说可以呀8 小时前
Vue插槽
前端·javascript·vue.js
qq_448011168 小时前
Vue + Jinja2两套模版与法冲突造成undefinedError
前端·javascript·vue.js
aixingpan8 小时前
aixingpan.cn API开发文档:前端查询夏令时和时区接口指南
前端·php
2601_958352909 小时前
23.5×19mm 换掉一整条音频链?|A-68 集成了什么:ADC / DAC / 24.576MHz 时钟 / AEC / 波束降噪,主控仅需 6 根线
前端·音视频·ai降噪·回声消除·语音模块
志尊宝9 小时前
AI原生前端开发(039):AI辅助的Nuxt 4全栈开发
前端·vue.js·ai-native
FRing0310 小时前
JavaScript入门到精通三十二(深拷贝、浅拷贝、递归函数)
开发语言·前端·javascript
excel10 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放10 小时前
Python基础语法练习题(73)
java·前端·python