React 中的计算属性
Vue 等框架具有“计算属性”的概念——一种基于传入的内容派生出一些新的、可能很复杂的数据的方法。本文介绍了如何在 React 中实现相同的结果。
Vue 等框架具有“计算属性”的概念——一种基于传入的内容派生出一些新的、可能很复杂的数据的方法。
本文介绍了如何在 React 中实现相同的结果。
在渲染时计算属性
在 React 中处理派生数据计算的“React 方式”是在 render
方法中(如果是无状态组件,则在函数体中)计算它。是的,就在渲染时。是的,每一次渲染。(我们稍后会讨论性能)
最简单的方法是内联执行,如下所示。请记住,在 React 中,您可以在大括号内运行任意 JavaScript 代码,React 将渲染该表达式的结果。
function UrlPath({ fullUrl }) { return ( <div>{new URL(fullUrl).pathname}</div> ); } // Used like: <UrlPath fullUrl="https://daveceddia.com/pure-react/" /> // Would render: <div>/pure-react/</div>
如果您的计算简短而精炼,只需将其放入 render
中即可。
简单操作不太可能导致性能问题,但如果您注意到速度变慢,请查看浏览器的性能工具。Chrome、Firefox、Safari 和 Edge 都内置了工具,通常在开发者工具的“性能”选项卡下,您可以记录正在运行的应用程序并查看速度减慢的原因。
将计算提取到函数中
如果您的计算很复杂,您可能想将其从组件中提取出来放入一个函数中。这样也可以在其他组件中重用。以下是一个过滤和排序产品列表以仅显示“新品”并按价格排序的示例
function newItemsCheapestFirst(items) { return items .filter(item => item.isNew) .sort((a, b) => { if(a.price < b.price) { return -1; } else if(a.price > b.price) { return 1; } else { return 0; } }); } function NewItemsList({ items }) { return ( <ul> {newItemsCheapestFirst(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )} </ul> ); }
这是 CodeSandbox 上的一个工作示例。
newItemsCheapestFirst
函数在这里完成了大部分工作。我们可以将其内联到组件中,但将其写成独立函数会更具可读性(也更易于重用)。
请注意,计算函数不处理为每个项目创建 <li>
元素。这是故意的,目的是将“项目处理部分”与“项目渲染部分”分开。让 React 组件 NewItemsList
处理渲染,而 newItemsCheapestFirst
函数处理数据。
类组件中的计算
您可以将上述示例改编为 class
组件,方法是将 newItemsCheapestFirst
函数移入类中,如下所示
(请注意,您不必须这样做——如果您希望函数被其他组件重用,将其保留为常规的独立函数更有意义)
class NewItemsList extends React.Component { newItemsCheapestFirst() { // I'm getting `items` from `props` instead of // passing it in, but either way works. return this.props.items.filter(item => item.isNew).sort((a, b) => { if (a.price < b.price) { return -1; } else if (a.price > b.price) { return 1; } else { return 0; } }); } render() { return ( <ul> {this.newItemsCheapestFirst().map(item => ( <li key={item.id}> {item.name}, ${item.price} </li> ))} </ul> ); } }
您甚至可以更进一步,将计算变成一个 getter,这样访问它就像访问属性一样
class NewItemsList extends React.Component { // Added the "get" keyword in front of the function name... get newItemsCheapestFirst() { return this.props.items.filter(item => item.isNew).sort((a, b) => { if (a.price < b.price) { return -1; } else if (a.price > b.price) { return 1; } else { return 0; } }); } render() { // Now we can use it like a property instead of a function call return ( <ul> {this.newItemsCheapestFirst.map(item => ( <li key={item.id}> {item.name}, ${item.price} </li> ))} </ul> ); } }
我个人可能会坚持使用“函数”方法,而不是使用 getter。我认为混合使用这两种方法会导致混淆——应该是 this.newItemsCheapestFirst
还是 this.newItemsCheapestFirst()
?无论您选择哪种方式,都要保持一致。
记忆化昂贵的计算
如果您的计算量很大——也许您正在过滤数百个项目的列表之类的——那么通过记忆化(而不是memoRizing)您的计算函数,您可以获得很好的性能提升。
什么是记忆化?
记忆化是一个表示缓存的花哨词。它说“记住调用此函数的返回值,下次使用相同的参数调用它时,只需返回旧结果,而不是重新计算。”该函数基本上是在记忆答案。不过,它仍然被称为“memoization”,而不是“memoRization”。
记忆化如何工作?
您可以使用现有的记忆化函数,例如 Lodash 中的函数,或者用不多的代码编写自己的函数。这是一个模仿 Lodash 中的函数的示例
function memoize(func) { // Create a new cache, just for this function let cache = new Map(); const memoized = function (...args) { // Use the first argument as the cache key. // If your function takes multiple args, you may // want to come up with a more complex scheme let key = args[0]; // Return the cached value if one exists if (cache.has(key)) { return cache.get(key); } // Otherwise, compute the result and save it // before returning it. let result = func.apply(this, args); cache.set(key, result); return result; }; return memoized; } function doSort(items) { console.log('doing the sort'); return items.sort(); } let memoizedSort = memoize(doSort); // Look at the console and notice how it only // prints 'doing the sort' once! let numbers = [1,7,4,2,4,9,28,3]; memoizedSort(numbers); memoizedSort(numbers); memoizedSort(numbers);
这是 CodeSandbox 上的工作示例。
如何在 React 中使用记忆化
现在您已经掌握了记忆化如何工作,并且拥有了一个记忆化函数(您自己的或别人的),您可以将昂贵的函数调用包装在其中。
这可能看起来像这样
// Let's assume we're passing in a ton of items and this is slow function newItemsCheapestFirst(items) { return items .filter(item => item.isNew) .sort((a, b) => { if(a.price < b.price) { return -1; } else if(a.price > b.price) { return 1; } else { return 0; } }); } // Memoize the function... memoizedCheapestItems = memoize(newItemsCheapestFirst); function NewItemsList({ items }) { // Use the memoized wrapper instead of the original return ( <ul> {memoizedCheapestItems(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )} </ul> ); }
一个巨大的警告:确保您没有在每次渲染时都重新创建记忆化函数,否则您将看不到任何好处——它将在每次渲染时都被调用。换句话说,如果您的 memoize
调用发生在每次渲染时,那是不好的。不要这样做
function NewItemsList({ items }) { // NO! // Don't memoize it every render! It'll never cache anything this way. const memoizedExpensiveFunction = memoize(expensiveFunction); return ( <ul> {memoizedExpensiveFunction(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )} </ul> ); }
回顾
我们回顾了什么
- React 没有“计算属性”,但您可以使用函数实现相同效果
- 如果计算简短快速,请在
render
中(或直接在函数组件中)执行 - 如果计算复杂难以阅读或需要重用,请将其提取到函数中。
- 如果计算运行成本很高,请考虑进行记忆化。
如果您想记住这些东西,请选择一两个练习题,并在 CodeSandbox 上将其构建出来。亲自动手编写是学习的最佳方式 :)
React 中的计算属性最初由 Dave Ceddia 于 2018 年 8 月 14 日在 Dave Ceddia 上发布。