memo参考文档
memo
允许你的组件在 props 没有改变的情况下跳过重新渲染。
参数 :memo(Component, arePropsEqual)
-
Component
:要进行记忆化的组件。memo
不会修改该组件,而是返回一个新的、记忆化的组件。它接受任何有效的 React 组件,包括函数组件和 forwardRef 组件。 -
可选参数
arePropsEqual
:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回true
。否则返回false
。通常情况下,你不需要指定此函数。默认情况下,React 将使用 Object.is 比较每个 prop。
返回值
memo
返回一个新的 React 组件。它的行为与提供给 memo
的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化。
用法 :
如下,在App组件中定义的2个state,但是只有count传递给了DemoChild,如果DemoChild不用memo包裹,那么count和count1改变时,DemoChild都会重新渲染。
import React from 'react'
import DemoChild from './page/DemoChild';
const App = () => {
const [count, setCount] = React.useState(0)
const [count1, setCount1] = React.useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>count:{count}</button>
<button onClick={() => setCount1(count1 + 2)}>count:{count1}</button>
<DemoChild count={count} />
</>
);
};
export default App
import React from 'react'
const DemoChild = ({ count }) => {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return (
<>
Child: {count}
</>
)
}
export default DemoChild
如下用memo包裹后,只有count改变时,DemoChild会重新渲染。
import React, { memo } from 'react'
const DemoChild = ({ count }) => {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return (
<>
Child: {count}
</>
)
}
export default memo(DemoChild)
useMemo参考文档
useMemo
是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
在什么情况下是有用的?
默认情况下,React 会在每次重新渲染时重新运行整个组件。如果计算速度很快,这将不会产生问题。但是,当正在过滤转换一个大型数组,或者进行一些昂贵的计算,而数据没有改变,那么使用useMemo就可以跳过这些重复计算可以大大提高性能。
用法 :
先定一个一个filterTodos方法:
export function filterTodos(todos, tab) {
console.log( todos.length + ' todos for "' + tab + '" tab.');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// 在 500 毫秒内不执行任何操作以模拟极慢的代码
// console.log("500ms还没执行, 太慢啦");
}
用useMemo包裹filterTodos,当我们切换isDark时,会跳过重复计算,背景色切换速度很快。反之,则会始终重新计算 ,这是因为 此版本没有调用 useMemo
,因此每次重新渲染都会调用人为减速的 filterTodos
。
import React,{useMemo} from 'react';
import { filterTodos } from '../utils'
const TodoList = ({ todos, tab }) => {
const [isDark, setIsDark] = React.useState(false);
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// const visibleTodos = filterTodos(todos, tab);
return (
<div style={{ background: isDark ? 'green' : 'pink' }}>
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>
{todo.completed ?
<s>{todo.text}</s> :
todo.text
}
</li>
))}
</ul>
</div >
);
}
export default TodoList
useCallback参考文档
用法:
如下:父组件定义主题state,传递给<ProductPage/>,点击checkbox时,可以切换主题。
import React from 'react'
import ProductPage from './page/ProductPage';
const App = () => {
const [isDark, setIsDark] = React.useState(false);
return (
<>
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<ProductPage
referrerId="wizard_of_oz"
productId={123}
theme={isDark ? 'dark' : 'light'}
/>
</>
);
};
export default App
如下时ProductPage组件具体函数,
import React from 'react'
import ShippingForm from './ShippingForm';
export default function ProductPage({ productId, referrer, theme }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
// const handleSubmit = React.useCallback((orderDetails) => {
// post('/product/' + productId + '/buy', {
// referrer,
// orderDetails,
// });
// }, [productId, referrer]);
return (
<div style={{ background: theme === "dark" ? 'green' : 'pink' }}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
function post(url, data) {
//想象这发送了一个请求
console.log('POST /' + url);
console.log(data);
}
如下时ShippingForm组件的详细代码:
import { memo, useState } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
const [count, setCount] = useState(1);
console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// 500 毫秒内不执行任何操作来模拟极慢的代码
}
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const orderDetails = {
...Object.fromEntries(formData),
count
};
onSubmit(orderDetails);
}
return (
<form onSubmit={handleSubmit}>
<p><b>Note: <code>ShippingForm</code> is artificially slowed down!</b></p>
<label>
Number of items:
<button type="button" onClick={() => setCount(count - 1)}>–</button>
{count}
<button type="button" onClick={() => setCount(count + 1)}>+</button>
</label>
<label>
Street:
<input name="street" />
</label>
<label>
City:
<input name="city" />
</label>
<label>
Postal code:
<input name="zipCode" />
</label>
<button type="submit">Submit</button>
</form>
);
});
export default ShippingForm;
由此例看出,尝试更改主题。将 useCallback
和memo结合使用后,由于 useCallback
依赖 productId
与 referrer
自上次渲染后始终没有发生改变,因此 handleSubmit
也没有改变。由于 handleSubmit
没有发生改变,ShippingForm
就跳过了重新渲染。
如果不是有useCallback
,切换主题会很慢!因为 handleSubmit
总是一个新的函数,并且被减速的 ShippingForm
组件不能跳过重新渲染。
为了解决频繁触发 Effect ,可以在 Effect 中将要调用的函数包裹在 useCallback
中:
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]);
useEffect(() => {
createOptions();
}, [createOptions]);
如果你正在编写一个 自定义hooks,建议将它返回的任何函数包裹在
useCallback
中!