我需要演示如何使用 React Hooks useMemo。我有如下工作代码,可以做我想要的:
const SpeakerCardDetail = React.memo(
({id,...
我找到了一个链接,表明我可以像这样使用语法,但我无法确切地弄清楚。
据我所知:
const SpeakerDetail = React.useMemo(() => {
({ id,
显然不是。 我确实知道 React.memo 解决了这个问题,但我确实需要展示 useMemo 的实际效果,并希望有一种我可以使用的替代语法。
React.memo
和React.useMemo
根本不等同(不要依赖命名相似性)。以下是React.memo
文档的引述:
React.memo
是高阶组件。
因此,它是一个 HOC,可以优化组件的再现,因为它呈现具有相同属性的相同输出。
另一方面,React.useMemo
更通用,并返回一个记忆值:
传递一个"create"函数和一个依赖项数组。
useMemo
意志 仅当其中一个依赖项(a
或b
)具有 时,才重新计算记忆值 改变。
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
虽然它可以被黑客入侵而不是React.memo
,这不是它的目的,它会增加混乱而不是帮助。useMemo
是一个钩子,受某些使用规则的约束。
还有这个警告:
将来,React 可能会选择"忘记"一些以前记住的 值并在下次渲染时重新计算它们,例如释放内存 屏幕外组件。编写代码,使其在没有的情况下仍然可以工作
useMemo
— 然后添加它以优化性能。
虽然memo
是一个HOC和一个钩子,useMemo
是一个钩子,但你可以使用它们来实现相同的结果。
对于上下文,HOC 是一种较旧的 React 模式,已经与基于类和功能组件一起使用了很多年。您今天仍然可以使用它(没有弃用计划)。
Hooks 是一个相对较新的概念(大约一年),它增强了功能组件,并在许多情况下大大简化了代码。这就是为什么许多开发人员正在转向使用钩子的原因。
无论如何,memo
和useMemo
都接受两个参数:函数和道具。如果后续重新渲染时没有任何道具更改,则不会再次执行该函数,而是返回之前的结果。实际上,这用纯函数方法替换了shouldComponentUpdate
回调。
使用memo
,您的代码将如下所示:
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
)
使用useMemo
,您可以编写:
const SpeakerCardDetail = (props) => useMemo(() => <div>{props.name}</div>)
请注意,useMemo
在组件函数内部使用,而memo
包装函数。
更传统地说,useMemo
可以写成:
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
)
}
现在,上面的代码每次都会重新渲染,使useMemo
函数有点无用。为了使它发挥其魔力,我们需要添加第二个参数。(即使不指定第二个参数,memo
仍然有效,但您可以添加它来自定义它)
第二个参数的格式略有不同。memo
需要一个比较以前和当前 props 的函数,就像shouldComponentUpdate
对类组件所做的那样。
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
,
// return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false
(prevProps, nextProps) => prevProps.name === nextProps.name
)
另一方面,useMemo
期望数组作为第二个参数。每当数组中的值更改时,该函数将再次执行。
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
,
[props.name]
)
}
真的没有比这更神奇的了。memo
和useMemo
都用于记忆函数的结果,唯一的区别是memo
是一个 HOC(可用于包装类和功能组件),useMemo
是一个钩子(并且只能在功能组件内部使用)。
总结React.memo
与useMemo
/TLDR:
React.memo
是一个高阶组件(简称 HOC),它将根据 props 记忆一个反应组件。
export function SomeComponent({ num }) {
return <p>{num * 10}</p>
}
export default React.memo(SomeComponent, function areEqual(
prevProps,
nextProps
) {
if (prevProps.num !== nextProps.num) {
return false
}
return true
})
useMemo
是一个 react 钩子,它将记住从您提供的函数返回的值。
export function SomeComponent({ num }) {
const res = useMemo(() => num * 10, [num])
return <p>{res}</p>
}
源
React.Memo
使用React.memo
将导致 React 跳过渲染组件,如果它的 props 没有改变。
例:
const Child = React.memo(props => {
console.log("rendered");
return <React.Fragment>{props.name}</React.Fragment>;
});
class App extends React.Component {
state = {
value: 1,
name: "Jioke"
};
handleClick = () => {
this.setState({
value: this.state.value + 1
});
};
render() {
return (
<React.Fragment>
<Child name={this.state.name} />
<div>{this.state.value}</div>
<button onClick={this.handleClick}>+</button>
</React.Fragment>
);
}
}
当我们单击按钮时,Child
组件不会重新渲染(rendered
仅显示 1)
注意:道具越多,计算越多,比较(检查是否渲染)增加的比较成本对于渲染、协调、DOM 更改和副作用成本方面的"简单"组件来说是不值得的。所以要小心决定是否使用它
使用备忘录
useMemo
将缓存一个值,以便每次重新渲染组件时都不需要重新计算该值。它保存函数的返回值,如果输入没有改变,则返回。
例:
import { useState, useMemo } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = useMemo(() => expensiveCalculation(count), [count]);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};
return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};
const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};
ReactDOM.render(<App />, document.getElementById('root'));
我们有一个昂贵的函数,可以在每个渲染上运行。 更改计数或添加待办事项时,您会注意到执行延迟。 因此,当我们使用useMemo
昂贵的函数只会在其依赖项更改时运行。 在以下示例中,昂贵的函数仅在更改计数时运行,而不会在添加待办事项时运行。
请使用下面的simple
示例来更好地理解上面的答案。该示例以最简单的方式演示了这些方案:
memo
- 如果道具没有更改,则不会重新渲染组件。
useMemo
- 如果依赖项未更改,则不会重新呈现组件。
- 如果依赖项未更改,则不会重新运行该函数(对于昂贵的函数很有用)。
// index.js
import React , { useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';
const Child1 = (props) => {
console.log("Child1");
return <p>Child 1</p>;
};
const Child2 = React.memo((props) => {
console.log("Child2");
return <p>Child 2</p>;
});
const Child3 = (props) => {
console.log("Child3");
return <p>Child 3</p>;
};
const expensiveCalculation = (label) => {
console.log(label);
return label;
};
function App() {
console.log("App");
const [count, setCount] = useState(0);
const child3 = useMemo(() => <Child3 />, []);
const calculation1 = expensiveCalculation("Expensive calculation without useMemo");
const calculation2 = useMemo(() => expensiveCalculation("Expensive calculation with useMemo"), []);
return (
<>
<button onClick={() => {setCount(c => c + 1);}}>Increase count</button>
<p>Current count: {count}</p>
<Child1 />
<Child2 />
{child3}
<p>{calculation1}</p>
<p>{calculation2}</p>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
输入/输出:
// Initial render
App
Expensive calculation without useMemo
Expensive calculation with useMemo
Child1
Child2
Child3
// Click on the "Increase count" button and observe the impact of memo and useMemo.
App
Expensive calculation without useMemo
Child1
笔记:
- 我举了一个非常简单的例子来方便解释。请对备忘录和使用备忘录采取的论点进行更多研究。
在新的 react 文档中说你可以使用useMemo
而不是memo
https://react.dev/reference/react/useMemo#memoizing-individual-jsx-nodes
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab), [todos, tab]
);
const children = useMemo(
() => <List items={visibleTodos} />, [visibleTodos]
);
return (
<div className={theme}>
{children}
</div>
);
}
但是,在文档页面上,您可以找到:
Manually wrapping JSX nodes into useMemo is not convenient. For example, you can’t do this conditionally. This is usually why you would wrap components with memo instead of wrapping JSX nodes.
最好记住,memo
还有第二个可选参数,可用于比较道具,
在某些情况下,还可以在某些非memo(Component, arePropsEqual?)
https://react.dev/reference/react/memo#memo