Как предотвратить повторное рендеринг и улучшить производительность (пример Todo с хуками) - PullRequest
0 голосов
/ 03 июня 2019

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

Что я мог бы улучшить, чтобы только один или два компонента изменились, а не воссоздали все это?

function createTodos(number) {
  const todos = [];
  for (let i = 0; i < number; i++) {
    todos.push({
      id: i,
      toggled: !(i % 4)
    });
  }
  return todos;
}

function Todos() {
  const [todos, setTodos] = useState(() => createTodos(10000));

  const toggleTodo = useCallback(
    id => {
      setTodos(
        todos.map(todo => {
          return todo.id === id ? { ...todo, toggled: !todo.toggled } : todo;
        })
      );
    },
    [todos]
  );

  return useMemo(
    () =>
      todos.map(todo => (
        <Todo
          key={todo.id}
          todoId={todo.id}
          toggled={todo.toggled}
          toggleTodo={toggleTodo}
        />
      )),
    [todos, toggleTodo]
  );
}

function Todo({ todoId, toggled, toggleTodo }) {
  const toggle = useCallback(() => toggleTodo(todoId), [todoId, toggleTodo]);
  return useMemo(() => {
    const style = {
      background: toggled ? "green" : "red",
      margin: 2,
      padding: 4
    };
    return (
      <div style={style} onClick={toggle}>
        {todoId}
      </div>
    );
  }, [todoId, toggled, toggle]);
}

Пример (открывать с осторожностью, так как я рендеринг 10k компонентов): https://codesandbox.io/embed/quizzical-sun-92qnw

Чтобы сравнить производительность с решением useReducer: https://codesandbox.io/s/nifty-hodgkin-gdo2g

Решение для редуктора намного быстрее (потому что ссылка на редуктор никогда не меняется, НО я использую useCallback для всех функций I), передается в качестве обратного вызова, поэтому разве я не должен иметь тот же результат, что и в первом примере?

1 Ответ

1 голос
/ 03 июня 2019

Решил это вместо того, чтобы объявить мою toggleTodo функцию вроде:

const toggleTodo = useCallback(
    id => {
      setTodos(
        todos.map(todo => {
          return todo.id === id ? { ...todo, toggled: !todo.toggled } : todo;
        })
      );
    },
    [todos]
  );

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

Итак, решение:

const toggleTodo = useCallback(
    id => {
      setTodos(oldTodos => 
        oldTodos.map(todo => {
          return todo.id === id ? { ...todo, toggled: !todo.toggled } : todo;
        })
      );
    },
    []
  );

наш параметр useState 2nd может быть значением или обратным вызовом функции. Используя функцию обратного вызова функции, задачи больше не будут зависеть, и мы сможем избежать этих повторных визуализаций (на самом деле это не повторные визуализации, а пересчеты?)

...