如果React组件渲染(或者说`执行`)提前,就可以在条件判断里使用Hooks了吗?

作者站长头像
站长
· 阅读数 6

问题

muiTab组件DEMO里面, TabPanel是通过选择的TabValue来判断现在要渲染哪个组件.但是竟然可以在TabPanel child组件里使用Hooks,这和react官网的那句话不太一样呀

如果我把child类型改成函数, child组件就又不能用hooks了

改成函数前

https://codesandbox.io/s/lingering-microservice-9xxfjj?file=/...

import * as React from "react";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";

function TabPanel(index: number, value: number, children: React.ReactNode) {
  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      id={`simple-tabpanel-${index}`}
      aria-labelledby={`simple-tab-${index}`}
    >
      {value === index && (
        <Box sx={{ p: 3 }}>
          <Typography>{children}</Typography>
        </Box>
      )}
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `simple-tab-${index}`,
    "aria-controls": `simple-tabpanel-${index}`
  };
}

const Tab2Child = (value) => {
  const [res, setRes] = React.useState("res");
  return (
    <h1 id="aaa444">
      {value} +++ {res}
    </h1>
  );
};

const Tab2 = (value) => {
  return TabPanel(1, value, Tab2Child(value));
};

export default function BasicTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: "100%" }}>
      <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
        <Tabs
          value={value}
          onChange={handleChange}
          aria-label="basic tabs example"
        >
          <Tab label="Item One" {...a11yProps(0)} />
          <Tab label="Item Two" {...a11yProps(1)} />
          <Tab label="Item Three" {...a11yProps(2)} />
        </Tabs>
      </Box>
      {TabPanel(0, value, <h1>tab 1</h1>)}
      {TabPanel(2, value, <h1>tab 3</h1>)}
      {Tab2(value)}
    </Box>
  );
}

改成函数后

https://codesandbox.io/s/pedantic-edison-7zju4s?file=/demo.ts...

import * as React from "react";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";

function TabPanel(
  index: number,
  value: number,
  children: () => React.ReactNode
) {
  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      id={`simple-tabpanel-${index}`}
      aria-labelledby={`simple-tab-${index}`}
    >
      {value === index && (
        <Box sx={{ p: 3 }}>
          <Typography>{children()}</Typography>
        </Box>
      )}
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `simple-tab-${index}`,
    "aria-controls": `simple-tabpanel-${index}`
  };
}

const Tab2Child = (value) => {
  const [res, setRes] = React.useState();
  return (
    <h1 id="aaa444">
      {value} +++ {res}
    </h1>
  );
};

const Tab2 = (value) => {
  return TabPanel(1, value, () => Tab2Child(value));
};

export default function BasicTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: "100%" }}>
      <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
        <Tabs
          value={value}
          onChange={handleChange}
          aria-label="basic tabs example"
        >
          <Tab label="Item One" {...a11yProps(0)} />
          <Tab label="Item Two" {...a11yProps(1)} />
          <Tab label="Item Three" {...a11yProps(2)} />
        </Tabs>
      </Box>
      {TabPanel(0, value, () => (
        <h1>tab 1</h1>
      ))}
      {TabPanel(2, value, () => (
        <h1>tab 3</h1>
      ))}
      {Tab2(value)}
    </Box>
  );
}
回复
1个回答
avatar
test
2024-07-06

React 的规则是不能在条件语句中使用 Hooks,因为 Hooks 必须按照它们在组件中声明的顺序运行。当你使用条件语句时,可能会导致 Hooks 的顺序不一致,从而引发问题。

在你的代码里,虽然 TabPanel 组件是通过条件渲染的,但它们的子组件始终使用 Hooks。当条件满足时,组件会正常渲染,但实际上仍然违反了 React Hooks 的规则。这是一个潜在的问题,可能会导致意外的行为。

解决方案:

你可以使用 React 的懒加载特性(React.lazy)来确保组件在需要时才加载,并且始终遵循 Hooks 的规则。修改 Tab2Child 和 Tab2 组件如下:

import * as React from "react";

const Tab2Child = React.lazy(() => {
  return new Promise((resolve) => {
    resolve({
      default: (value) => {
        const [res, setRes] = React.useState("res");
        return (
          <h1 id="aaa444">
            {value} +++ {res}
          </h1>
        );
      },
    });
  });
});

const Tab2 = (value) => {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <TabPanel index={1} value={value} children={<Tab2Child />} />
    </React.Suspense>
  );
};

这样,代码依然会遵循 React Hooks 的规则,同时实现条件渲染。你要注意,在使用React.lazy 时,需要包裹在 React.Suspense 组件中,要提供一个 fallback 组件,方便在组件加载期间显示。

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容