为 JSX 使用 Memo 安全吗?

Is it safe to useMemo for JSX?

我正在尝试做一个备忘录 Modal 但我在这里遇到了问题。

当我更改输入时,我不需要重新渲染模态组件。

例如:

Modal.tsx 看起来像这样:

import React from "react";
import { StyledModalContent, StyledModalWrapper, AbsoluteCenter } from "../../css";

interface ModalProps {
  open: boolean;
  onClose: () => void;
  children: React.ReactNode
};

const ModalView: React.FC<ModalProps> = ({ open, onClose, children }) => {
  console.log("modal rendered");

  return (
    <StyledModalWrapper style={{ textAlign: "center", display: open ? "block" : "none" }}>
      <AbsoluteCenter>
        <StyledModalContent>
          <button
            style={{
              position: "absolute",
              cursor: "pointer",
              top: -10,
              right: -10,
              width: 40,
              height: 40,
              border: 'none',
              boxShadow: '0 10px 10px 0 rgba(0, 0, 0, 0.07)',
              backgroundColor: '#ffffff',
              borderRadius: 20,
              color: '#ba3c4d',
              fontSize: 18
            }}
            onClick={onClose}
          >
            X
          </button>
          {open && children}
        </StyledModalContent>
      </AbsoluteCenter>
    </StyledModalWrapper>
  );
};

export default React.memo(ModalView);

这是我如何包装它的示例。

import React from 'react'
import Modal from './modal;

const App: React.FC<any> = (props: any) => {
  const [test, setTest] = React.useState("");
  const [openCreateChannelDialog, setOpenCreateChannelDialog] = React.useState(false);

  const hideCreateModalDialog = React.useCallback(() => {
    setOpenCreateChannelDialog(false);
  }, []);

  return (
    <>
      <input type="text" value={test} onChange={(e) => setTest(e.target.value)} />
      <button onClick={() => setOpenCreateChannelDialog(true)}>Create channel</button>

      <Modal
        open={openCreateChannelDialog}
        onClose={hideCreateModalDialog}
        children={<CreateChannel onClose={hideCreateModalDialog} />}
      />
    </>
};

我知道,Modal 重新渲染是因为 children 引用在每次 App 组件重新渲染时创建(当我更改输入文本时)。

知道我很感兴趣,如果我将 <CreateChannel onClose={hideCreateModalDialog} /> 包裹在 React.useMemo() 钩子中

例如:

  const MemoizedCreateChannel = React.useMemo(() => {
    return <CreateChannel onClose={hideCreateModalDialog} />
  }, [hideCreateModalDialog]);

并更改里面的儿童道具Modal

来自:

children={<CreateChannel onClose={hideCreateModalDialog} />}

children={MemoizedCreateChannel}

它工作正常,但我安全吗?这是我试图记忆模态的唯一解决方案?

安全吗?是的。在一天结束时,JSX 只是被转换成一个 JSON 对象,完全可以记忆。

也就是说,我认为这样做在风格上有点奇怪,我可以预见如果事情需要改变而你没有完全考虑它,它会在未来导致意想不到的错误。

记忆 JSX 表达式是 official useMemo API:

的一部分
const Parent = ({ a }) => useMemo(() => <Child1 a={a} />, [a]); 
// This is perfectly fine; Child re-renders only, if `a` changes

useMemo 在给定任何依赖项的情况下记忆单个子项和计算值。您可以将 memo 视为整个组件的 useMemo 的快捷方式,它比较 all props.

但是 memo 有一个缺陷 - 它不适用于儿童:

const Modal = React.memo(ModalView);

// React.memo won't prevent any re-renders here
<Modal>
  <CreateChannel />
</Modal>

children都是道具的一部分。并且 React.createElement 总是创建一个新的不可变对象引用 (REPL)。所以每次 memo 比较 props 时,它会确定 children 引用发生了变化,如果不是原语的话。

为了防止这种情况,您可以在父级 App 中使用 useMemo 来记忆 children(您已经这样做了)。或者定义一个 custom comparison function for memo, so Modal component now becomes responsible for performance optimization itself. react-fast-compare 是一个方便的库,以避免 areEqual.

的样板