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


Введение

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

Я буду рассматривать эту тему следующим образом: ЧТО, ПОЧЕМУ и КАК

1. Что такое мемоизация?

Когда я впервые увидел слово мемоизация, мне на ум пришло слово запоминать, и я был в замешательстве! Как JavaScript должен запоминать и помнить что-то от моего имени (я подумал, есть ли какая-то форма машинного обучения в JavaScript по умолчанию), но углубившись в концепцию мемоизации, я понял, что это все о помощи JavaScript в запоминании предыдущих вычислений.

Говоря простыми словами, мемоизация — это техника оптимизации, которая помогает ускорить повторяющиеся, дорогостоящие вычисления путем запоминания результата предыдущего вычисления.

2. Зачем нужна мемоизация?

Эта техника направлена на то, чтобы сделать вычисления эффективнее и быстрее; если выполняется дорогостоящее вычисление, то при мемоизации результат может быть сохранен в кэше и извлечен, когда это необходимо, поэтому нет необходимости в повторном вычислении.

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

3. Как реализовать мемоизацию?

В JavaScript?

Реализация мемоизации заключается в передаче функции в мемоизированный обратный вызов

const multiplyBy10 = (num: number) => num * 10;
console.log('Simple call', multiplyBy10(3));

/**
 * 
 * Explanation: a simple memoize function that takes in a function
 * 
 * @param fn a function to be memoized or used to perform computation
 * @returns a memoized function
 */

const memoize = (fn: Function) => {
  let cache = {};

  return (...args) => {
    let num = args[0];  // just taking one argument here

    if (num in cache) {
      console.log('Fetching from cache');
      return cache[num];
    } else {
      console.log('Calculating result');
      let result = fn(num);
      cache[num] = result;
      return result;
    }
  }

}

// creating a memoized function for the 'multiplyBy10' pure function

const memoizedAdd = memoize(multiplyBy10);
console.log(memoizedAdd(3));  // calculated
console.log(memoizedAdd(3));  // cached
console.log(memoizedAdd(4));  // calculated
console.log(memoizedAdd(4));  // cached

Вход в полноэкранный режим Выйти из полноэкранного режима

спасибо Codegrepper & Agreeable Armadillo за ссылку на код

В React

Существует несколько способов реализации мемоизации, и они основаны на том, что необходимо сделать

  1. для компонента используйте React.memo()
  2. Если вы хотите мемоизировать функцию, то используйте React.useCallback();
  3. Если вы хотите запомнить результат дорогой функции, то используйте React.useMemo();

Эти методы предотвращают ненужный повторный рендеринг в react, если использовать их правильно.

Понимание React.memo()

/**
 * Explanation: 
 *  this function accept a name and render a styled version of it
 * 
 * @param name
 * @returns JSX.Element (styled version of the name)
 **/
import React from 'react';

function RenderName({ name }: string) {
    return <span className="text-red-500">{name}</span>
}

export default React.memo(RenderName);

Вход в полноэкранный режим Выход из полноэкранного режима

Рассмотрим компонент выше (child-component) без React.memo() компонента высшего порядка (HOC) у нас будут проблемы с повторным рендерингом, когда имя, переданное компоненту RenderName, будет одинаковым; но React.memo() HOC помогает предотвратить этот ненужный повторный рендеринг.

NB: memo не оптимизирует функцию, передаваемую дочернему компоненту, поэтому нам нужен React.useCallback().

Понимание React.useCallback()

/**
 * Explanation:
 * a password field that handles users password 
 * and provides a toggle for the password
 *  
 * @returns JSX.Element (a styled password field)
 */
import React from 'react';
import eye from 'images/eye.svg';
import eyeClose from 'images/eye-close.svg';

function PasswordInput() {
  const [show, setShow] = React.useState<boolean>(false);
  const toggleSecret = React.useCallback(() => setShow(x => !x), []);

  return (
    <div className="h-8 flex items-center">
      <input type="password" className="..." placeholder="Enter Password" />

      <button onClick={toggleSecret}>
        <img src={show ? eyeClose : eye} alt="toggle secret" />
      </button>
    </div>
  );
}

export default PasswordInput;

Вход в полноэкранный режим Выход из полноэкранного режима

С помощью React.useCallback() функция toggleSecret() мемоизируется, и каждый раз, когда вы нажимаете на кнопку переключения, она не вызывает повторного рендеринга, что повышает оптимизацию.
Примечание: мы передали функцию стрелки в React.useCallback(). функция стрелки имеет вид:
() => setShow((x) => !x)
React.useCallback() также помогает предотвратить повторный рендеринг, когда функция передается дочернему компоненту. Это делается путем «сохранения» функции, переданной дочернему компоненту, в то время как родительский компонент повторно рендерится.

Понимание React.useMemo()

/**
 * Explanation:
 * The code demonstrates how to create a DynamicTable using React's useMemo() function.
 * The DynamicTable component is a wrapper around the Table component.
 * The DynamicTable component is responsible for creating the columns and data for the Table component.
 * 
 * @param {values: Record<string, string>[] | null}
 * @returns returns a JSX.Element(Table)
 */

import React, { useMemo } from 'react';
import Table, { ColumnsProps } from './Table';

interface DynamicTableType {
  values: Record<string, string>[] | null;
}

const DynamicTable = ({ values }: DynamicTableType): JSX.Element => {
  const columns: ColumnsProps[] = useMemo(() => {
    if (!values) return [];
    const keys = Object.keys(values?.[0]);

    const result = [];

    for (let index = 0; index < keys.length; index++) {
      const element = keys[index];
      result.push({
        Header: element?.replace('_', ' '),
        accessor: element,
      });
    }

    return result;
  }, [values]);

  const data: Record<string, string>[] = useMemo(() => {
    if (!values) return [];
    const result: Record<string, string>[] = [];

    for (let index = 0; index < values.length; index++) {
      const element = values[index];
      result.push(element);
    }
    return result;
  }, [values]);

  return <Table columns={columns} data={data} showSearch={false} />;
};

export default DynamicTable;

Вход в полноэкранный режим Выход из полноэкранного режима

Выдержка из проекта с открытым исходным кодом, над которым я сейчас работаю, проверьте его на github

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

Заключение

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

Я доступен для технических переговоров по структуре данных, алгоритмам, javascript, react и react-native; вы можете связаться со мной через twitter или электронную почту.

Я также скоро запущу канал на youtube, чтобы рассказать о своих приключениях со структурой данных и алгоритмами. На канале я буду решать некоторые вопросы по leetcode и любые другие материалы для интервью, которые попадут мне в руки. следите 😊.

Сноски

Спасибо, что ознакомились с этим учебником. (пожалуйста, ставьте лайк и добавляйте свои комментарии)
Вы также можете ознакомиться с другими моими статьями в моем блоге.

Если у вас есть какие-либо вопросы, отзывы или комментарии, пожалуйста, дайте мне знать.

Вы можете связаться со мной через twitter email github

Вы также можете связаться со мной (я занимаюсь React-Native & React Js) twitter email

Оцените статью
devanswers.ru
Добавить комментарий