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

问题

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.5k
1 个回答

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 组件,方便在组件加载期间显示。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题