使用它来存储 JSX 组件以便在其他地方显示是滥用上下文吗?

dwj*_*ton 7 reactjs react-context

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

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

这是一个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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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

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

Shu*_*tri 7

请注意,纯粹从技术角度来看,将组件存储在 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>
  );
}
Run Code Online (Sandbox Code Playgroud)

代码沙盒演示