Кеширование и реакт

UseMemo и UseCallback

1) Посмотрим на их особенности
 

2) Разберем работу useMemo и useCallback с точки зрения памяти

 

3) Разберем примеры как делать не нужно

 

4) Посмотрим на бенчмарк

Depth == 1

function App() {
  const [a, setA] = useState(1);

  const result = useMemo(() => {
    console.log("HERE");
    return a + "_text";
  }, [a]);

  return (
    <div className="App">
      <h1>Hello {result}</h1>
      <button onClick={() => setA(2)}>Setup 2</button>
      <button onClick={() => setA(1)}>Setup 1</button>
    </div>
  );
}

Колбеки работают через массив подписок

function isEventSupported(eventNameSuffix) {
  if (!canUseDOM) {
    return false;
  }

  const eventName = 'on' + eventNameSuffix;
  let isSupported = eventName in document;

  if (!isSupported) {
    const element = document.createElement('div');
    element.setAttribute(eventName, 'return;');
    isSupported = typeof element[eventName] === 'function';
  }

  return isSupported;
}
function handleTopLevel(bookKeeping) {
  let targetInst = bookKeeping.targetInst;
  let ancestor = targetInst;
  do {
    if (!ancestor) {
      bookKeeping.ancestors.push(ancestor);
      break;
    }
    const root = findRootContainerNode(ancestor);
    if (!root) {
      break;
    }
    bookKeeping.ancestors.push(ancestor);
    ancestor = getClosestInstanceFromNode(root);
  } while (ancestor);

  for (let i = 0; i < bookKeeping.ancestors.length; i++) {
    targetInst = bookKeeping.ancestors[i];
    runExtractedEventsInBatch(
      bookKeeping.topLevelType,
      targetInst,
      bookKeeping.nativeEvent,
      getEventTarget(bookKeeping.nativeEvent),
    );
  }
}
function MyComponent({ title }) {
  return (
    <button 
      onClick={() => {alert('hello world')}}
    >
      {title}
   </button>
  );
}

Постоянного addEventListener | removeEventListener не происходит

function MyComponent({ title }) {
  return (
    <button 
      onClick={() => {alert('hello world')}}
    >
      {title}
   </button>
  );
}

Постоянного addEventListener | removeEventListener не происходит

Добавление и удаление элемента из списка — дешевле

Memo

По-умолчанию работает как PureComponent

const A = memo(function MyComponent({ b }) {
  const [a, setA] = useState(1);
  const rendered = useRef(0);
  rendered.current++;
  return (
    <div>
      <h2>a is {a}</h2>
      <h2>b is {b}</h2>
      <h2>Component rendered {rendered.current} times</h2>
      <button
        onClick={() => {
          setA(a => a + 1);
        }}
      >
        Change A
      </button>
      <button
        onClick={() => {
          setA(a => a);
        }}
      >
        Change A to the same value
      </button>
    </div>
  );
});
 <A
  b={b.value}
  onClick={() => {
    setB(b => ({ ...b }));
    console.log("here");
  }}
/>

1) Memo проверяет state и props

 

2) Memo НЕ проверяет context

 

3) state "бойлится" (set для примитивного value с тем же значением не запускает механизм dirty компонентов)

 

4) memo без доп. параметров не имеет смысла, если есть инлайн колбек

 

5) memo принимает второй аргумент — функцию-компаратор

function MyComponent() {
    const items = useMemo(() => {
     сложные вычисления
    });
    
    return (
      <>
      	{items.map(item =>
          <MyComponent2 
            data={item} 
            onClick={() => {}} 
          />)}
      </>
    )
}

MyComponent должен:

 

1) Кешировать в useMemo и колбеки

2) Убедиться, что MyComponent2 имеет memo

3) Иначе профита от кеширования крайне мало!

MyComponent2 должен:

 

1) Иметь memo

2) Не опускать проверку на onClick в memo!

Бывает ли профит от ПРОСТО мемоизации?

 

1) Да

2) Вычисления должны быть такими, что тормоза при тротле уже заметны

Garbage collector

function A({text}) {
  const onClick = useCallback(() => {
    alert(text);
  }, [text]);
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) {
  const onClick = useCallback(() => {
    alert(text);
  }, [text]);
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) {
  const onClick = useCallback(() => { // () => {}
    alert(text);
  }, [text]);
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) {
  const onClick = useCallback(() => { // () => {}
    alert(text);
  }, [text]); // []
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) {
  const onClick = useCallback(() => { // () => {}
    alert(text);
  }, [text]); // []
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) { // () => {}, []
  const onClick = useCallback(() => { 
    alert(text);
  }, [text]); 
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) { // 1) () => {} 
  const onClick = useCallback(() => { // 2) () => {}
    alert(text);
  }, [text]); 
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) { // 1) () => {}, []
  const onClick = useCallback(() => { // 2) () => {}
    alert(text);
  }, [text]);  // 2) []
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}
function A({text}) { 
  const onClick = () => {
    alert(text);
  };
  return (
    <button onClick={onClick}>
      Click Me!
    </button>
  );
}

Benchmark Time

Made with Slides.com