首页 > 解决方案 > 使用它来存储 JSX 组件以在其他地方显示是否是滥用上下文?

问题描述

我有一个应用程序,用户将在其中单击应用程序的各个部分,这将在右侧的抽屉中显示某种配置选项。

我为此得到的解决方案是将要显示的任何内容存储在上下文中。这样抽屉只需要从上下文中检索其内容,并且需要设置内容的任何部分都可以通过上下文直接设置。

这是一个演示这个的 CodeSandbox

关键代码片段:

const MainContent = () => {
  const items = ["foo", "bar", "biz"];

  const { setContent } = useContext(DrawerContentContext);
  /**
   * Note that in the real world, these components could exist at any level of nesting 
   */
  return (
    <Fragment>
      {items.map((v, i) => (
        <Button
          key={i}
          onClick={() => {
            setContent(<div>{v}</div>);
          }}
        >
          {v}
        </Button>
      ))}
    </Fragment>
  );
};

const MyDrawer = () => {
  const classes = useStyles();

  const { content } = useContext(DrawerContentContext);

  return (
    <Drawer
      anchor="right"
      open={true}
      variant="persistent"
      classes={{ paper: classes.drawer }}
    >
      draw content
      <hr />
      {content ? content : "empty"}
    </Drawer>
  );
};

export default function SimplePopover() {
  const [drawContent, setDrawerContent] = React.useState(null);
  return (
    <div>
      <DrawerContentContext.Provider
        value={{
          content: drawContent,
          setContent: setDrawerContent
        }}
      >
        <MainContent />
        <MyDrawer />
      </DrawerContentContext.Provider>
    </div>
  );
}

我的问题是 - 这是对上下文的适当使用,还是这种解决方案可能会遇到渲染/虚拟 dom 等问题?

有没有更整洁的方法来做到这一点?(即自定义钩子?虽然 - 请记住,一些想要进行设置的组件可能不是功能组件)。

标签: reactjsreact-context

解决方案


请注意,纯粹从技术角度将组件存储在 Context 中是可以的,因为 JSX 结构只不过是最终使用React.createElement.

但是,您尝试实现的目标可以通过门户轻松完成,并且可以让您更好地控制在其他地方渲染的组件,然后通过上下文渲染它,因为如果您直接渲染它们而不是存储,您可以更好地控制状态和处理程序它们作为上下文中的值

将组件存储在上下文中并渲染它们的另一个缺点是它使组件的调试变得非常困难。如果组件变得复杂,您通常会发现很难确定道具的供应商是谁

import React, {
  Fragment,
  useContext,
  useState,
  useRef,
  useEffect
} from "react";
import { makeStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import { Drawer } from "@material-ui/core";
import ReactDOM from "react-dom";

const useStyles = makeStyles(theme => ({
  typography: {
    padding: theme.spacing(2)
  },
  drawer: {
    width: 200
  }
}));

const DrawerContentContext = React.createContext({
  ref: null,
  setRef: () => {}
});

const MainContent = () => {
  const items = ["foo", "bar", "biz"];
  const [renderValue, setRenderValue] = useState("");
  const { ref } = useContext(DrawerContentContext);

  return (
    <Fragment>
      {items.map((v, i) => (
        <Button
          key={i}
          onClick={() => {
            setRenderValue(v);
          }}
        >
          {v}
        </Button>
      ))}
      {renderValue
        ? ReactDOM.createPortal(<div>{renderValue}</div>, ref.current)
        : null}
    </Fragment>
  );
};

const MyDrawer = () => {
  const classes = useStyles();
  const contentRef = useRef(null);
  const { setRef } = useContext(DrawerContentContext);
  useEffect(() => {
    setRef(contentRef);
  }, []);
  return (
    <Drawer
      anchor="right"
      open={true}
      variant="persistent"
      classes={{ paper: classes.drawer }}
    >
      draw content
      <hr />
      <div ref={contentRef} />
    </Drawer>
  );
};

export default function SimplePopover() {
  const [ref, setRef] = React.useState(null);
  return (
    <div>
      <DrawerContentContext.Provider
        value={{
          ref,
          setRef
        }}
      >
        <MainContent />
        <MyDrawer />
      </DrawerContentContext.Provider>
    </div>
  );
}

CodeSandbox 演示


推荐阅读