Co je funkce Memoization a proč by vás to mělo zajímat?

Memoizace je obecný princip/idealogie softwarového inženýrství, kterou lze aplikovat na kód v jakémkoli jazyce. Všechny mé příklady a knihovny budou JavaScript.

Co je tedy zapamatování?

Memoizace je principem ukládání výsledku volání funkce do mezipaměti. Pokud zavoláte funkci vícekrát se stejnými argumenty, pokaždé dostanete výsledek uložený v mezipaměti. Logika ve vaší funkci se znovu nespustí, když je v mezipaměti výsledek.

Proč/kdy bych to někdy potřeboval?

Ukládání do paměti je skvělé, když zjistíte, že funkce jsou volány znovu a znovu (například při volání renderu v Reactu). Vaše funkce může mít nějakou složitou logiku, která by vašemu výkonu prospěla, kdyby nevolala stále stejnou logiku.

tl;dr výkon pro funkce volané vícekrát se stejnými argumenty.

Zapamatování v Reactu

Koncept Memoization v Reactu je úplně stejný. Chceme uložit výsledek volání funkce do mezipaměti. Kromě tohoto scénáře naše funkce vrací JSX a naše argumenty jsou rekvizity.

Pokud máte rodiče, který je znovu vykreslován, bude vaše podřízená funkce volána při každém vykreslení, i když se rekvizity nezmění. React nám poskytuje React.memo nástroj a useMemo háček, který můžeme využít v našich funkčních komponentách, abychom zabránili zbytečnému překreslování.

Můžeme také použít normální Memoization v metodách tříd a další funkce JS v našich komponentách reakce. Tradičním vzorem v komponentách třídy React bylo reagovat na změny podpory prostřednictvím componentWillReceiveProps , aplikujte na rekvizitu nějakou logiku a uveďte ji do stavu. Teď to componentWillReceiveProps je na cestě k ukončení podpory, Memoization nám poskytuje skvělou alternativní metodu k dosažení stejného výsledku. Viz část s příklady níže.

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

Některé knihovny pro zapamatování vanilla JS

Pro obecný JavaScript bych doporučil dvě bitvě otestované knihovny místo toho, abyste se pokoušeli implementovat sami, jak jsem popsal níže.

Lodash.memoize

Vytvoří mapu výsledků zapamatování, což znamená, že bude efektivně ukládat historii všech výsledků pro použití v budoucnu.

Serializuje pouze první argument řetězce. Dávejte pozor na procházející předměty. Více argumentů se neporovnává.

Užitečné, pokud funkci voláte z více míst s různými argumenty.

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

Memoise One

Ukládá poslední výsledek volání funkce. Vždy porovnává pouze argumenty s posledními, se kterými byla funkce volána.

Použije všechny argumenty pro porovnání mezi voláními funkcí. Žádná serializace objektů, takže můžete předat cokoli.

Užitečné, pokud funkci uloženou v paměti voláte pouze z jednoho místa.

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

Rozdíly mezi těmito dvěma

  • Lodash memoize se serializuje argumenty, které se mají použít jako klíč mapy
  • Lodash memoize použije pouze první argument
  • Memoize One si zapamatuje pouze sadu argumentů/výsledků předchozího volání funkce. Lodash memoize bude udržovat mapu výsledků.

Co takhle nějaké příklady?

Normální funkce

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.memo

import React, { memo } from 'react';

function MyFunctionalComponent {
  return <div />;
}

export default memo(MyFunctionalComponent);

Před/Po, scénář reálného světa komponenty třídy React

Před

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);
  }  
}

Po

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);
  }
}

Formulář reakce

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>
    );
  }
}

Závěrečná slova

Memoizace je skvělý nástroj ve vývojářském arzenálu. Při správném použití a na správných místech může poskytnout skvělé zlepšení výkonu.

Buďte si vědomi problémů, zejména při používání React.memo a očekáváte, že se věci znovu vykreslí.