65.9K
CodeProject 正在变化。 阅读更多。
Home

React 中的计算属性

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年8月14日

CPOL

4分钟阅读

viewsIcon

10964

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 上发布。

© . All rights reserved.