在JSX中使用useMemo是否安全?

20

我正在尝试使用记忆化 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} />}
      />
    </>
};

我知道,每次当 App 组件重新渲染时(比如我改变一个输入框的值),Modal 会重新渲染,因为 children 的引用也会被重新创建。

现在我很感兴趣,如果我把 <CreateChannel onClose={hideCreateModalDialog} /> 包裹在 React.useMemo() 钩子中,会发生什么呢?

例如:

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

Modal 内更改子级组件的属性

来自:

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

它运行良好,但是安全吗?而且它只是尝试缓存模态框的一种解决方案吗?

2个回答

38

记忆化JSX表达式是官方useMemo API的一部分:

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

useMemo会记忆每个子组件和计算出的值,根据给定的依赖项。你可以将memo视为整个组件的useMemo快捷方式,它会比较所有props

但是memo有一个缺陷——它无法处理子组件

const Modal = React.memo(ModalView);

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

children是props的一部分。而React.createElement总是创建一个新的不可变对象引用(REPL)。因此,每次memo比较props时,如果不是原始类型,它都会确定children引用已更改。

为了防止这种情况,您可以在父级App中使用useMemo来记忆化children(您已经这样做了)。或者为memo定义一个自定义比较函数,因此Modal组件现在负责自己的性能优化。react-fast-compare是一个方便的库,可避免为areEqual编写样板代码。


好的解释!非常感谢 :) - Tazo Leladze

-2

安全吗?是的。归根结底,JSX只是转换为JSON对象,这完全可以进行记忆化处理。

话虽如此,我认为这样做在风格上有点奇怪,并且如果需要更改但没有充分考虑可能会导致意想不到的错误。


最终,JSX只是转换成一个JSON对象。对我来说是个好消息,谢谢!我在哪里可以阅读更多细节?我同意你的观点,这很奇怪,但我该怎么办呢?我不认为将来会有特定情况下的错误,这只是一个模态框,当打开等于true时我想要展示它... - Tazo Leladze
说实话,我觉得你在试图防止组件重新渲染方面有点过于激进了。话虽如此,你尝试过对CreateChannel组件进行React.memo处理吗? - Bill Metcalf
@TazoLeladze 我知道我有点晚了,但你可以看看这个例子 console.log(React.createElement('div', { className: "world" })); - leodriesch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接