Как стать автором
Обновить

Комментарии 11

Спасибо за демонстрацию примера, но переменные в коде написаны на русских и английских символах. Если кто-то захочет скопировать и протестировать, у него ничего не выйдет.

Рабочий код без русских символов
import React, { useState, useCallback } from 'react';

function Page() {
  const listOfCities = ['Beijing', 'Tokyo', 'Kinshasa', 'Moscow', 'Jakarta'];

  const [name, setName] = useState('');
  const [list, setList] = useState(listOfCities);

  const handleClick = () => {
    setList([...list, name]);
    setName('');
  };

  const handleChange = (event) => {
    setName(event.target.value);
  };

  const handleRemoveClick = useCallback(
    (item) => {
      const filteredList = list.filter((listItem) => listItem !== item);

      setList(filteredList);
    },
    [list]
  );

  console.log('Page render');

  return (
    <div>
      <input type="text" value={name} onChange={handleChange} />
      <button onClick={handleClick}>Add</button>
      <CitiesList list={list} onRemoveClick={handleRemoveClick} />
    </div>
  );
}

export const CitiesList = React.memo(({ list, onRemoveClick }) => {
  console.log('List render');

  return list.map((item) => {
    return <City key={item} city={item} onRemoveClick={onRemoveClick} />;
  });
});

export const City = ({ city, onRemoveClick }) => {
  const handleCityClick = () => onRemoveClick(city);

  console.log('City render');

  return <div onClick={handleCityClick}>{city}</div>;
};

export default Page;

Проблема: производительность - рендер Page и всех его дочерних компонентов каждый раз при вводе в input.

Причина: вызывается setState в handleChange при onChange input элемента

Решение: убираем состояние useState() для name;

без всяких useCallback

import React, { useState } from "react";

function Page() {
  const listOfCities = ["Beijing", "Tokyo", "Kinshasa", "Moscow", "Jakarta"];

  const [list, setList] = useState(listOfCities);
  let newItem = "";
  const handleClick = () => {
    setList([...list, newItem]);
    newItem = "";
  };

  const handleChange = (event) => {
    newItem = event.target.value;
  };

  const handleRemoveClick = (item) => {
    const filteredList = list.filter((listItem) => listItem !== item);

    setList(filteredList);
  };

  console.log("Page render");

  return (
    <div>
      <input type="text" onChange={handleChange} />
      <button onClick={handleClick}>Add</button>
      <CitiesList list={list} onRemoveClick={handleRemoveClick} />
    </div>
  );
}

export const CitiesList = ({ list, onRemoveClick }) => {
  console.log("List render");

  return list.map((item) => {
    return <City key={item} city={item} onRemoveClick={onRemoveClick} />;
  });
};

export const City = ({ city, onRemoveClick }) => {
  const handleCityClick = () => onRemoveClick(city);

  console.log("City render");

  return <div onClick={handleCityClick}>{city}</div>;
};

export default Page;

Пример в статье подобран неудачно ...

А теперь вбейте инпут, удалите город из списка, а потом нажмите add.

Но согласен, что пример не самый удачный

@habrjeka Спасибо за комментарии, как написано в конце статьи, действительно, данную проблему производительности можно решить путем вынесением поля ввода вместе с его state в отдельный компонент или тем методом который был предложен выше в комментарии. Моя идея была все таки не предложить идеальное решение этой задачи, а показать как можно было бы ее решить при помощи useCallback

о господи, никогда так не делайте. при любом чихе вы будете обнулять свою переменную и контролировать это никак не получится. т.е. обновился родительский компонент -- обнулили переменную, даже если вы набирали текст в моменте.

ну и не используйте let, забудьте на годик про let и var, используйте const

Вы на 100% правы.

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

const [list, setList] = useState(listOfCities);
const handleClick = () => {
  setList([...list, newItem]);
};

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории