Мемоизация — это общий принцип/идеология разработки программного обеспечения, который можно применять к коду на любом языке. Все мои примеры и библиотеки будут на JavaScript.

Так что же такое мемоизация?

Мемоизация — это принцип кэширования результата вызова функции. Если вы вызываете функцию несколько раз с одними и теми же аргументами, вы каждый раз будете получать кешированный результат. Логика в вашей функции не будет запускаться повторно, если есть кешированный результат.

Зачем/когда мне это понадобится?

Мемоизация хороша, когда вы обнаруживаете, что функции вызываются снова и снова (например, при вызове рендеринга в React). Ваша функция может иметь сложную логику, от которой выиграет ваша производительность, если вы не будете вызывать одну и ту же логику снова и снова.

Высокая производительность для функций, вызываемых несколько раз с одними и теми же аргументами.

Мемоизация в React

Концепция мемоизации в React точно такая же. Мы хотим кэшировать результат вызова функции. За исключением этого сценария, наша функция возвращает JSX, а наши аргументы являются реквизитами.

Если у вас есть повторный рендеринг родителя, ваша дочерняя функция будет вызываться при каждом рендеринге, даже если свойства не меняются. React предоставляет нам утилиту React.memo и хук useMemo, которые мы можем использовать в наших функциональных компонентах для предотвращения ненужного повторного рендеринга.

Мы также можем использовать обычную мемоизацию в методах класса и другие функции JS в наших реагирующих компонентах. Традиционный шаблон в компонентах класса React заключался в том, чтобы реагировать на изменения свойства через componentWillReceiveProps, применять некоторую логику к свойству и устанавливать его в состояние. Теперь, когда componentWillReceiveProps находится на пути к прекращению поддержки, Memoization предоставляет нам отличный альтернативный метод для достижения того же результата. См. раздел примеров ниже.

https://reactjs.org/docs/react-api.html#reactmemo

Некоторые библиотеки мемоизации vanilla JS

Для общего JavaScript я бы порекомендовал две проверенные библиотеки вместо того, чтобы пытаться реализовать их самостоятельно, о чем я рассказал ниже.

Lodash.memoize

Создает карту результатов запоминания, что означает, что она будет эффективно хранить историю всех результатов для использования в будущем.

Сериализует только первый аргумент в строку. Будьте осторожны с проходящими объектами. Несколько аргументов не сравниваются.

Полезно, если вы вызываете функцию из нескольких мест с разными аргументами.

https://lodash.com/docs/4.17.15#memoize

Запомнить один

Сохраняет последний результат вызова функции. Всегда будет сравнивать аргументы только с последними, с которыми была вызвана функция.

Использует все аргументы для сравнения вызовов функций. Нет сериализации объектов, поэтому вы можете передать что угодно.

Полезно, если вы вызываете функцию memoized только из одного места.

https://github.com/alexreardon/memoize-one

Различия между двумя

  • Lodash memoize сериализует аргументы для использования в качестве ключа карты.
  • Lodash memoize будет использовать только аргумент first
  • Memoize One запомнит только набор аргументов/результат предыдущего вызова функции. Lodash memoize будет поддерживать карту результатов.

Как насчет некоторых примеров?

Нормальная функция

import _memoize from 'lodash.memoize';
import memoizeOne from 'memoize-one';
const myFunc = users => users.filter(user => user.gender === 'female');
const myMemoizedFunc = _memoize(user => users.filter(user => user.gender === 'female'));
const myMemoizedOnceFunc = memoizeOne(user => users.filter(user => user.gender === 'female'));

React.памятка

import React, { memo } from 'react';
function MyFunctionalComponent {
  return <div />;
}
export default memo(MyFunctionalComponent);

До/после, реальный сценарий компонента класса React

До

import React, { Component } from 'react';
function filterUsers(users) {
  return users.filter(({ gender }) => gender === 'female');
}
export default class FemaleUserList extends Component {
  constructor(props) {
    super(props);
    const { allUsers } = props;
    this.state = {
      femaleUsers: filterUsers(allUsers)
    }
  }
  componentWillReceiveProps(nextProps) {
    const { allUsers } = nextProps;
    if (allUsers !== this.props.allUsers) {
      this.setState({
        femaleUsers: filterUsers(allUsers)
      });
    }
  }
  render() {
    const { femaleUsers } = this.state;
    return femaleUsers.map(User);
  }  
}

После

import React, { Component } from 'react';
import memoizeOne from 'memoize-one';
export default class FemaleUserList extends Component {
  // We bind this function to the class now because the cached results are scoped to this class instance
  filterUsers = memoizeOne(users => users.filter(({ gender }) => gender === 'female'));
  render() {
    const { allUsers  } = this.props;
    const femaleUsers = this.filterUsers(allUsers);
    return femaleUsers.map(User);
  }
}

Реагирующая форма

import React, { Component } from 'react';
import _memoize from 'lodash.memoize';
export default class FemaleUserList extends Component {
  // Yes, we can even return cached functions! This means we don't have to
  // keep creating new anonymous functions
  handleFieldChange = _memoize((fieldName) => ({ target: { value } }) => {
    this.setState({ [fieldName]: value });
  }); 

  render() {
    const { email, password } = this.state;
    return (
      <div>
        <input onChange={this.handleFieldChange('email')} value={email} />
        <input
          onChange={this.handleFieldChange('password')}
          value={password}
          type="password"
        />
      </div>
    );
  }
}

Заключительные слова

Мемоизация — отличный инструмент в арсенале разработчика. При правильном использовании и в нужных местах он может значительно повысить производительность.

Просто помните о подводных камнях, особенно при использовании React.memo и ожидании повторного рендеринга.