为 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
.
的样板
我正在尝试做一个备忘录 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
.