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

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom12 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试