如果 areEqualFunction 执行 complex/a 大量比较,使用 React.memo 会更快吗?
Is it faster to use React.memo if the areEqualFunction performs complex/a large number of comparisons?
假设我有以下代码:
import React, { memo } from 'react';
const MyComponent = ({ arrayOfStuff }) => (
<div>
{arrayOfStuff.map(element => (
<p key={element.foo}>element.foo</p>
))}
</div>
);
const areEqual = (prevProps, nextProps) => {
const prevArrayOfStuff = prevProps.arrayOfStuff;
const nextArrayOfStuff = nextProps.arrayOfStuff;
if (prevArrayOfStuff.length !== nextArrayOfStuff.length)
return false;
for (let i; i < prevArrayOfStuff.length && i < nextArrayOfStuff.length; ++i) {
if (prevArrayOfStuff[i].foo !== nextArrayOfStuff[i].foo)
return false;
}
return true;
};
export default memo(MyComponent, areEqual);
假设 arrayOfStuff 非常大,可能有数百个元素。我真的节省了很多记忆组件的时间吗?我认为如果 props 相同,它会迭代所有元素而不考虑备忘录,因为 areEqual 和渲染函数都是这样做的。
对此的最佳答案是:配置文件并查看。 :-)
但是,尽管您的数组中可能有数百个条目,但您所做的检查并不复杂,而且非常简单快捷。 (我会在开头添加一个 if (prevArrayOfStuff === nextArrayOfStuff) { return true; }
。)
一些优缺点:
优点:
您的检查非常简单快速,即使是数百个元素也是如此。
如果没有发现任何变化,您保存:
- 创建一堆 objects(由组件 return 编辑的 React“元素”)。
- React 必须将先前元素的键与新元素进行比较,以查看是否需要更新 DOM。
请记住,您的组件将在其 parent 发生任何更改时被调用到 re-render,即使这些更改没有'与您的组件相关。
缺点:
如果数组中 经常变化,那么您只是在无回报地增加更多工作,因为 areEqual
将要 return false
无论如何。
areEqual
有持续的维护成本,并且它提供了出现错误的机会。
因此,这实际上归结为您的整个应用程序发生了哪些变化,尤其是组件的 parents。如果那些 parent 的状态或道具经常变化但与您的组件无关,那么您的组件进行检查可以节省很多时间。
这里展示了当 parent 中的某些内容发生更改时,即使其 props 中没有任何内容发生更改,您的组件将如何被调用到 re-render:
没有 记忆它(如果没有任何变化,React 实际上不会更新 DOM 元素,但是你的函数被调用并创建 React 比较的 React 元素渲染的):
const {useState, useEffect} = React;
// A stand-in for your component
const Example = ({items}) => {
console.log("Example rendering");
return <div>
{items.map(item => <span key={item}>{item}</span>)}
</div>;
};
// Some other component
const Other = ({counter}) => {
console.log("Other rendering");
return <div>{counter}</div>;
};
// A parent component
const App = () => {
// This changes every tick of our interval timer
const [counter, setCounter] = useState(0);
// This changes only every three ticks
const [items, setItems] = useState([1, 2, 3]);
useEffect(() => {
const timer = setInterval(() => {
setCounter(c => {
c = c + 1;
if (c % 3 === 0) {
// Third tick, change `items`
setItems(items => [...items, items.length + 1]);
}
// Stop after 6 ticks
if (c === 6) {
setTimeout(() => {
console.log("Done");
}, 0);
clearInterval(timer);
}
return c;
});
}, 500);
return () => clearInterval(timer);
}, []);
return <div>
<Example items={items} />
<Other counter={counter} />
</div>;
};
ReactDOM.render(<App/>, document.getElementById("root"));
.as-console-wrapper {
max-height: 80% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
用记忆它:
const {useState, useEffect} = React;
// A stand-in for your component
const Example = ({items}) => {
console.log("Example rendering");
return <div>
{items.map(item => <span key={item}>{item}</span>)}
</div>;
};
const examplePropsAreEqual = ({items: prevItems}, {items: nextItems}) => {
const areEqual = (
prevItems === nextItems ||
(
prevItems.length === nextItems.length &&
prevItems.every((item, index) => item === nextItems[index])
)
);
if (areEqual) {
console.log("(skipped Example)");
}
return areEqual;
}
const ExampleMemoized = React.memo(Example, examplePropsAreEqual);
// Some other component
const Other = ({counter}) => {
console.log("Other rendering");
return <div>{counter}</div>;
};
// A parent component
const App = () => {
// This changes every tick of our interval timer
const [counter, setCounter] = useState(0);
// This changes only every three ticks
const [items, setItems] = useState([1, 2, 3]);
useEffect(() => {
const timer = setInterval(() => {
setCounter(c => {
c = c + 1;
if (c % 3 === 0) {
// Third tick, change `items`
setItems(items => [...items, items.length + 1]);
}
// Stop after 6 ticks
if (c === 6) {
setTimeout(() => {
console.log("Done");
}, 0);
clearInterval(timer);
}
return c;
});
}, 500);
return () => clearInterval(timer);
}, []);
return <div>
<ExampleMemoized items={items} />
<Other counter={counter} />
</div>;
};
ReactDOM.render(<App/>, document.getElementById("root"));
.as-console-wrapper {
max-height: 80% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
假设我有以下代码:
import React, { memo } from 'react';
const MyComponent = ({ arrayOfStuff }) => (
<div>
{arrayOfStuff.map(element => (
<p key={element.foo}>element.foo</p>
))}
</div>
);
const areEqual = (prevProps, nextProps) => {
const prevArrayOfStuff = prevProps.arrayOfStuff;
const nextArrayOfStuff = nextProps.arrayOfStuff;
if (prevArrayOfStuff.length !== nextArrayOfStuff.length)
return false;
for (let i; i < prevArrayOfStuff.length && i < nextArrayOfStuff.length; ++i) {
if (prevArrayOfStuff[i].foo !== nextArrayOfStuff[i].foo)
return false;
}
return true;
};
export default memo(MyComponent, areEqual);
假设 arrayOfStuff 非常大,可能有数百个元素。我真的节省了很多记忆组件的时间吗?我认为如果 props 相同,它会迭代所有元素而不考虑备忘录,因为 areEqual 和渲染函数都是这样做的。
对此的最佳答案是:配置文件并查看。 :-)
但是,尽管您的数组中可能有数百个条目,但您所做的检查并不复杂,而且非常简单快捷。 (我会在开头添加一个 if (prevArrayOfStuff === nextArrayOfStuff) { return true; }
。)
一些优缺点:
优点:
您的检查非常简单快速,即使是数百个元素也是如此。
如果没有发现任何变化,您保存:
- 创建一堆 objects(由组件 return 编辑的 React“元素”)。
- React 必须将先前元素的键与新元素进行比较,以查看是否需要更新 DOM。
请记住,您的组件将在其 parent 发生任何更改时被调用到 re-render,即使这些更改没有'与您的组件相关。
缺点:
如果数组中 经常变化,那么您只是在无回报地增加更多工作,因为
areEqual
将要 returnfalse
无论如何。areEqual
有持续的维护成本,并且它提供了出现错误的机会。
因此,这实际上归结为您的整个应用程序发生了哪些变化,尤其是组件的 parents。如果那些 parent 的状态或道具经常变化但与您的组件无关,那么您的组件进行检查可以节省很多时间。
这里展示了当 parent 中的某些内容发生更改时,即使其 props 中没有任何内容发生更改,您的组件将如何被调用到 re-render:
没有 记忆它(如果没有任何变化,React 实际上不会更新 DOM 元素,但是你的函数被调用并创建 React 比较的 React 元素渲染的):
const {useState, useEffect} = React;
// A stand-in for your component
const Example = ({items}) => {
console.log("Example rendering");
return <div>
{items.map(item => <span key={item}>{item}</span>)}
</div>;
};
// Some other component
const Other = ({counter}) => {
console.log("Other rendering");
return <div>{counter}</div>;
};
// A parent component
const App = () => {
// This changes every tick of our interval timer
const [counter, setCounter] = useState(0);
// This changes only every three ticks
const [items, setItems] = useState([1, 2, 3]);
useEffect(() => {
const timer = setInterval(() => {
setCounter(c => {
c = c + 1;
if (c % 3 === 0) {
// Third tick, change `items`
setItems(items => [...items, items.length + 1]);
}
// Stop after 6 ticks
if (c === 6) {
setTimeout(() => {
console.log("Done");
}, 0);
clearInterval(timer);
}
return c;
});
}, 500);
return () => clearInterval(timer);
}, []);
return <div>
<Example items={items} />
<Other counter={counter} />
</div>;
};
ReactDOM.render(<App/>, document.getElementById("root"));
.as-console-wrapper {
max-height: 80% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
用记忆它:
const {useState, useEffect} = React;
// A stand-in for your component
const Example = ({items}) => {
console.log("Example rendering");
return <div>
{items.map(item => <span key={item}>{item}</span>)}
</div>;
};
const examplePropsAreEqual = ({items: prevItems}, {items: nextItems}) => {
const areEqual = (
prevItems === nextItems ||
(
prevItems.length === nextItems.length &&
prevItems.every((item, index) => item === nextItems[index])
)
);
if (areEqual) {
console.log("(skipped Example)");
}
return areEqual;
}
const ExampleMemoized = React.memo(Example, examplePropsAreEqual);
// Some other component
const Other = ({counter}) => {
console.log("Other rendering");
return <div>{counter}</div>;
};
// A parent component
const App = () => {
// This changes every tick of our interval timer
const [counter, setCounter] = useState(0);
// This changes only every three ticks
const [items, setItems] = useState([1, 2, 3]);
useEffect(() => {
const timer = setInterval(() => {
setCounter(c => {
c = c + 1;
if (c % 3 === 0) {
// Third tick, change `items`
setItems(items => [...items, items.length + 1]);
}
// Stop after 6 ticks
if (c === 6) {
setTimeout(() => {
console.log("Done");
}, 0);
clearInterval(timer);
}
return c;
});
}, 500);
return () => clearInterval(timer);
}, []);
return <div>
<ExampleMemoized items={items} />
<Other counter={counter} />
</div>;
};
ReactDOM.render(<App/>, document.getElementById("root"));
.as-console-wrapper {
max-height: 80% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>