React中计算属性的实现

React中,经常需要根据传入的数据计算出一些派生的属性。本文将探讨如何在React中实现这一功能,包括在渲染时计算属性、将计算提取到函数中、类组件中的计算以及如何使用memoization来优化性能。

渲染时计算属性

在React中,处理派生数据的“React方式”是在渲染方法(如果是无状态组件,则在函数体中)计算它。是的,就在渲染时。是的,每一次渲染。(一会儿会讨论性能问题)最简单的方法是这样做。记住,在React中,可以在单花括号内运行任意JS代码,React会渲染出该表达式的结果。

function UrlPath({ fullUrl }) { return (
{new URL(fullUrl).pathname}
); } // 使用方式: <UrlPath fullUrl="https://daveceddia.com/pure-react/" /> // 将渲染: <div>/pure-react/</div>

如果计算很简单,就直接在渲染中进行。简单的操作不太可能导致性能问题,但如果注意到了减速,可以查看浏览器性能工具。Chrome、Firefox、Safari和Edge都有内置工具,通常在devtools的“Performance”标签下,可以让记录运行中的应用程序,并查看减速发生在哪里。

将计算提取到函数中

如果计算很复杂,可能想要将其从组件中提取出来,并将其放入一个函数中。这也会使其在其他组件中可重用。以下是一个过滤和排序产品列表以仅显示“新”产品并按价格排序的示例:

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 (
    {newItemsCheapestFirst(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )}
); }

这里的newItemsCheapestFirst函数做了大部分工作。可以将其内联到组件中,但将其写成独立函数更易于阅读(和重用)。注意,计算函数不处理为每个项目创建<li>元素。这是有意为之的,以保持“项目处理”与“项目渲染”分开。让React组件NewItemsList处理渲染,而newItemsCheapestFirst函数处理数据。

类组件中的计算

可以将上述示例适应到类组件中,将newItemsCheapestFirst函数移到类中,如下所示:

class NewItemsList extends React.Component { 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() { return ( <ul> { this.newItemsCheapestFirst().map(item => ( <li key={item.id}> {item.name}, ${item.price} </li> )) } </ul> ); } }

甚至可以更进一步,将计算变成一个getter,这样访问它就像访问一个属性一样:

class NewItemsList extends React.Component { 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() { return ( <ul> { this.newItemsCheapestFirst.map(item => ( <li key={item.id}> {item.name}, ${item.price} </li> )) } </ul> ); } }

就个人而言,可能会坚持使用“函数”方法,而不是使用getter。认为混合使用两者会导致混淆——应该是this.newItemsCheapestFirst还是this.newItemsCheapestFirst()?无论选择做什么,都要保持一致。

Memoize昂贵的计算

如果计算很昂贵——也许正在过滤数百个项目的列表或其他东西——那么通过memoization(但不是memoRizing)计算函数,可以获得很好的性能提升。

Memoization是一个花哨的词,意思是缓存。它说:“记住调用这个函数的结果,下次用相同的参数调用时,直接返回旧的结果,而不是重新计算它。”这个函数基本上是在记住答案。尽管如此,它仍然被称为“memoization”。没有“r”。

可以使用现有的memoize函数,比如Lodash中的一个,或者可以用不多的代码自己写一个。这里有一个例子,模仿Lodash中的一个:

function memoize(func) { let cache = new Map(); const memoized = function (...args) { let key = args[0]; if (cache.has(key)) { return cache.get(key); } 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); let numbers = [1, 7, 4, 2, 4, 9, 28, 3]; memoizedSort(numbers); memoizedSort(numbers); memoizedSort(numbers);

看看控制台,注意它只打印了一次'doing the sort'!

现在对memoization的工作原理有了把握,并且手头有一个memoization函数(自己的或别人的),可以用它来包装昂贵函数调用。那可能看起来像这样:

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 }) { return ( <ul> {memoizedCheapestItems(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )} </ul> ); }

一个巨大的警告:确保不是每次都重新创建memoized函数,否则不会从中看到任何好处——它将每次都被调用。换句话说,如果调用memoize每次都发生,那就不好了。不要这样做:

function NewItemsList({ items }) { const memoizedExpensiveFunction = memoize(expensiveFunction); return ( <ul> {memoizedExpensiveFunction(items).map(item => <li key={item.id}>{item.name}, ${item.price}</li> )} </ul> ); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485