Это неправильное использование контекста, чтобы использовать его для хранения компонентов JSX для отображения в другом месте? - PullRequest
4 голосов
/ 09 апреля 2020

У меня есть приложение, в котором пользователи щелкают различные части приложения, и это отображает некоторые параметры конфигурации в ящике справа.

Решение, которое я получил для этого, состоит в том, чтобы иметь любой контент, который должен отображаться, для сохранения в контексте. Таким образом, выдвижной ящик просто должен извлечь свой контент из контекста, и любые его части могут установить его непосредственно через контекст.

Вот 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>
  );
}

Мой вопрос - это правильное использование контекста, или это решение может столкнуться с проблемами при рендеринге / virtual dom et c ?

Есть ли более аккуратный способ сделать это? (ie. Custom hooks? Хотя - помните, что некоторые компоненты, желающие выполнить настройку, могут не быть функциональными компонентами).

Ответы [ 4 ]

4 голосов
/ 12 апреля 2020

Заметьте, что хорошо хранить компоненты в 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 demo

3 голосов
/ 12 апреля 2020

Что касается производительности, компоненты, подписанные на контекст, будут перерисованы, если контекст изменится, независимо от того, хранят ли они произвольные данные, элементы jsx или компоненты React. Существует открытая RF C для селекторов контекста , которая решает эту проблему, но в то же время некоторые обходные пути: useContextSelector и Redux.

Помимо производительности, неправильное использование зависит от того, облегчает ли он работу с кодом. Просто помните, что элементы React - это просто объекты. В документах говорится:

Элементы React являются простыми объектами и дешевы в создании

А jsx - это просто синтаксис. документы :

Каждый элемент JSX является просто syntacti c сахаром для вызова React.createElement (component, props, ... children).

Итак, если хранение { children: 'foo', element: 'div' } в порядке, то во многих случаях это так же <div>foo</div>.

1 голос
/ 17 апреля 2020

Я использую следующую логику c в своих проектах для повышения производительности и большей гибкости, которую вы можете использовать при разработке макетов страниц, а также вы можете разработать ее по-своему. В этом методе context не используется, а вместо него используются ref и useImperativeHandle.

useImperativeHandle очень похож, но позволяет вам делать две вещи:

  1. Это дает вам контроль над значением, которое возвращается. Вместо того, чтобы возвращать элемент экземпляра, вы явно указываете, каким будет возвращаемое значение (см. Фрагмент ниже).
  2. Позволяет заменить встроенные функции (такие как размытие, фокус и т. Д. c) функциями ваши собственные, что позволяет побочные эффекты для нормального поведения или совсем другое поведение. Однако вы можете вызывать функцию как угодно.

useImperativeHandle настраивает значение экземпляра, которое предоставляется родительским компонентам при использовании ref

Для получения дополнительной информации:

React docs: useImperativeHandle

Stackoverflow вопрос: когда использовать useImperativeHandle ...


Демонстрация моего примера: codesandbox

Структура моего примера:

src
 |---pages
 |     |---About.js
 |     |---Home.js
 |
 |---App.js
 |---CustomPage.js
 |---DrawerSidebar.js
 |---index.js

index. js файл:

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

import { BrowserRouter as Router } from "react-router-dom";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Router>
    <App />
  </Router>,
  rootElement
);

приложение. js файл:

import React from "react";
import "./styles.css";
import { Switch, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";

export default function App() {
  return (
    <div className="App">
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
      <Switch>
        <Route path="/" exact component={Home} />
        <Route path="/about" exact component={About} />
      </Switch>
    </div>
  );
}

CustomPage. js файл:

import React, { useRef, useImperativeHandle } from "react";

import DrawerSidebar from "./DrawerSidebar";

const CustomPage = React.forwardRef((props, ref) => {
  const leftSidebarRef = useRef(null);
  const rootRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      rootRef: rootRef,
      toggleLeftSidebar: () => {
        leftSidebarRef.current.toggleSidebar();
      }
    };
  });

  return (
    <div className="page">
      <h1>custom page with drawer</h1>
      <DrawerSidebar position="left" ref={leftSidebarRef} rootRef={rootRef} />

      <div className="content">{props.children}</div>
    </div>
  );
});

export default React.memo(CustomPage);

DrawerSidebar. js файл:

import React, { useState, useImperativeHandle } from "react";
import { Drawer } from "@material-ui/core";

const DrawerSidebar = (props, ref) => {
  const [isOpen, setIsOpen] = useState(false);

  useImperativeHandle(ref, () => ({
    toggleSidebar: handleToggleDrawer
  }));

  const handleToggleDrawer = () => {
    setIsOpen(!isOpen);
  };

  return (
    <React.Fragment>
      <Drawer
        variant="temporary"
        anchor={props.position}
        open={isOpen}
        onClose={handleToggleDrawer}
        onClick={handleToggleDrawer}
      >
        <ul>
          <li>home</li>
          <li>about</li>
          <li>contact</li>
        </ul>
      </Drawer>
    </React.Fragment>
  );
};

export default React.forwardRef(DrawerSidebar);

О. js файл:

import React, { useRef } from "react";
import CustomPage from "../CustomPage";

const About = () => {
  const pageRef = useRef(null);

  return (
    <div>
      <CustomPage ref={pageRef}>
        <h1>About page</h1>
        <button onClick={() => pageRef.current.toggleLeftSidebar()}>
          open drawer in About page
        </button>
      </CustomPage>
    </div>
  );
};

export default About;

Дом. js файл:

import React, { useRef } from "react";
import CustomPage from "../CustomPage";

const Home = () => {
  const pageRef = useRef(null);

  return (
    <div>
      <CustomPage ref={pageRef}>
        <h1>Home page</h1>
        <button onClick={() => pageRef.current.toggleLeftSidebar()}>
          open drawer in Home page
        </button>
      </CustomPage>
    </div>
  );
};

export default Home;
1 голос
/ 12 апреля 2020

Да, вы можете сделать это, так как компоненты React являются просто объектами, и вы можете хранить объекты как значения контекста. Но в этом есть немного проблем, теперь вы не сможете использовать остановку ненужных повторных визуализаций, потому что объекты React всегда будут иметь разные ссылки, поэтому ваши дочерние компоненты будут перерисовываться каждый раз.

Что я буду делать Рекомендуется хранить простые значения в вашем контексте и отображать пользовательский интерфейс в зависимости от того, где вы хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...