Как использовать ответную заметку

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

Песочница: https://codesandbox.io/s/interesting-tharp-ziwe3?file=/src/components/Tasks/Tasks.jsx

Компонент задач

import React, { useState, useEffect, useCallback } from 'react'
import TaskRow from "../TaskRow";


function Tasks(props) {

    const [taskItems, setTaskItems] = useState([])

    useEffect(() => {
        setTaskItems(JSON.parse(localStorage.getItem('tasks')) || [])
    }, [])

    useEffect(() => {
        if (!props.newTask) return
        newTask({ id: taskItems.length + 1, ...props.newTask })
    }, [props.newTask])

    
    const newTask = (task) => {
        updateItems([...taskItems, task])
    }

    const toggleDoneTask = useCallback((id) => {
        const taskItemsCopy = [...taskItems]
        taskItemsCopy.map((t)=>{
            if (t.id === id){
                t.done = !t.done
                return t
            }
            return t
        })
        console.info(taskItemsCopy)
        console.info(taskItems)
        updateItems(taskItemsCopy)
    }, [taskItems])

    const updateItems = (tasks) => {
        setTaskItems(tasks)
        localStorage.setItem('tasks', JSON.stringify(tasks))
    }


    return (
        <React.Fragment>
            <h1>learning react </h1>
            <table>
                <thead>
                    <tr>
                        <th>Title</th>
                        <th>Description</th>
                        <th>Done</th>
                    </tr>
                </thead>
                <tbody>
                    {
                        props.show ? taskItems.map((task, i) =>
                            <TaskRow
                                task = {task}
                                key = {task.id}
                                toggleDoneTask = {()=>toggleDoneTask(task.id)}>
                            </TaskRow>)
                            :
                            taskItems.filter((task) => !task.done)
                                .map((task) =>
                                    <TaskRow
                                        show = {props.show}
                                        task = {task}
                                        key = {task.id}
                                        toggleDoneTask = {()=>toggleDoneTask(task.id)}></TaskRow>
                                )
                    }
                </tbody>
            </table>
        </React.Fragment>
    )
}


export default Tasks

Задача элемента (компонент TaskRow)

import React, { memo } from 'react'

function TaskRow(props) {

    return (<React.Fragment>
        {console.info('render', props.task)}
        <Tr show = {props.show} taskDone = {props.task.done}>
            <td>
                {props.task.title}
            </td>
            <td>
                {props.task.description}
            </td>
            <td>
                <input type = "checkbox"
                    checked = {props.task.done}
                    onChange = {props.toggleDoneTask}
                />

            </td>
        </Tr>
    </React.Fragment>)

}


export default memo(TaskRow, (prev,next)=>{
    console.info('prev props', prev.task)
    console.info('next props', next.task)
})
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
1 315
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Боюсь, что с кодом, которым вы поделились, довольно много проблем, только некоторые из них из-за React.memo. Я начну с этого и проработаю те, что заметил.

Вы предоставили функцию проверки равенства для memo, но вы не используете ее для проверки чего-либо. Поведение по умолчанию, которое не требует функции тестирования, будет поверхностно сравнивать свойства между предыдущим и следующим рендерингом. Это означает, что он будет обнаруживать различия между примитивными значениями (например, строкой, числом, логическим значением) и ссылками на объекты (например, литералы, массивы, функции), но не будет автоматически глубоко сравнивать эти объекты.

Помните, что memo разрешает повторную визуализацию только в том случае, если функция проверки на равенство возвращает false. Вы не предоставили возвращаемое значение функции тестирования, что означает, что она возвращает undefined, что является ложным. Я предоставил простую функцию тестирования для литерала объекта с примитивными значениями, которая будет выполнять необходимую здесь работу. Если у вас есть более сложные объекты для передачи в будущем, я предлагаю использовать комплексную глубокую проверку равенства, такую ​​​​как та, которая предоставляется библиотекой lodash, или, что еще лучше, вообще не передавать объекты, если вы можете помочь, и вместо этого попытаться придерживаться к примитивным значениям.

export default memo(TaskRow, (prev, next) => {
  const prevTaskKeys = Object.keys(prev.task);
  const nextTaskKeys = Object.keys(next.task);

  const sameLength = prevTaskKeys.length === nextTaskKeys.length;
  const sameEntries = prevTaskKeys.every(key => {
    return nextTaskKeys.includes(key) && prev.task[key] === next.task[key];
  });

  return sameLength && sameEntries;
});

Хотя это решает первоначальную проблему с запоминанием, код по-прежнему не работает по нескольким причинам. Во-первых, несмотря на копирование вашего taskItems в toggleTaskDone, по тем же причинам, что и описанные выше, ваш массив объектов не копируется глубоко. Вы помещаете объекты в новый массив, но ссылки на эти объекты сохраняются из предыдущего массива. Любые изменения, которые вы вносите в эти объекты, будут напрямую изменять состояние React, вызывая рассинхронизацию значений с остальными вашими эффектами.

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

const taskItemsCopy = [...taskItems].map((task) => ({ ...task }));

Боковое примечание: вы ничего не делаете с результатом taskItemsCopy в исходном коде. map не является мутирующим методом — его вызов без присвоения результата переменной ничего не делает.

Следующая проблема более тонкая и демонстрирует одну из ловушек и потенциальных сложностей при запоминании компонентов. Обратный вызов toggleTaskDone имеет taskItems в своем массиве зависимостей. Однако вы передаете его как реквизит в анонимной функции TaskRow. Это свойство не рассматривается React.memo — мы специально игнорируем его, потому что мы хотим перерисовывать только изменения в самом объекте задачи. Это означает, что когда задача меняет свой статус done, все остальные задачи перестают синхронизироваться с новым значением taskItems — когда они меняют свой статус done, они будут использовать значение taskItems, как это было в последний раз. были оказаны.

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

// Tasks.jsx
toggleDoneTask = {toggleDoneTask}
// TaskRow.jsx
onChange = {() => props.toggleDoneTask(props.task.id)}

Таким образом, вы сможете проверить изменения ссылки в вашей функции равенства memo, но, поскольку обратный вызов меняется каждый раз при изменении taskItems, это сделает мемоизацию совершенно бесполезной!

Так что делать. Здесь реализация остальной части компонента Tasks начинает нас немного ограничивать. У нас не может быть taskItems в зависимости от toggleTaskDone, и мы также не можем вызывать updateItems, потому что у него такая же (неявная) зависимость. Я предоставил решение, которое технически работает, хотя я бы посчитал это хаком и не рекомендовал бы его для фактического использования. Он основан на версии обратного вызова setState, которая позволит нам получить доступ к текущему значению taskItems, не включая его в качестве зависимости.

const toggleDoneTask = useCallback((id) => {
  setTaskItems((prevItems) => {
    const prevCopy = [...prevItems].map((task) => ({ ...task }));
    const newItems = prevCopy.map((t) => {
      if (t.id === id) t.done = !t.done;
      return t;
    });
    localStorage.setItem("tasks", JSON.stringify(newItems));
    return newItems;
  });
}, []);

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

В более широком смысле, я действительно думаю, что вам следует подумать о написании кода React с помощью create-react-app, когда вы изучаете фреймворк. Я был немного удивлен, увидев, что у вас настроен собственный веб-пакет, и у вас, похоже, нет надлежащего линтинга для React (автоматически включенного в CRA), который выделил бы для вас многие из этих проблем в виде предупреждений. В частности, неправильное использование массива зависимостей в ряде мест в компоненте Task, что сделает его нестабильным и подверженным ошибкам даже с предложенными существенными исправлениями.

Здравствуйте, все, что вы мне рассказали, мне очень помогло, но я до сих пор не понимаю, почему вы должны использовать этот «хак», я пытался искать информацию в Интернете, но не нашел ничего, что отвечало бы на мой вопрос. Я знаю, что без "хака" задачи не синхронизируются, но не понимаю почему, спасибо за помощь!

Franco 17.12.2020 06:49

Когда вы вводите memo в свои компоненты, вы берете на себя прямую ответственность за то, какие значения они обновляют и когда. Недавно я ответил на аналогичный вопрос , в котором это рассматривается более подробно. Я также кратко объясню, почему делать это, чтобы обойти объявление ваших зависимостей, — плохая практика. Способ реализовать это «правильно» состоит в том, чтобы переключиться на редуктор шаблон состояния, который делает то же самое, что и мое решение, но более структурированным и удобочитаемым способом.

lawrence-witt 17.12.2020 12:46

Конечно, «хак» гораздо более удобочитаем, структурирован и удобочитаем, но почему бы вам не порекомендовать его?

Franco 17.12.2020 20:24

Я говорю, что шаблон редуктора лучше структурирован и, в конечном счете, более удобочитаем, потому что он явно отделяет обратный вызов обработчика от фактической установки состояния. Поскольку useState на самом деле является специальной реализацией useReducer под капотом, след функций, вызываемых при использовании обратного вызова useState, очень похож. В этом случае лучше просто быть «честным» в отношении того, как на самом деле работает ваше состояние, и создать свой собственный редьюсер. Может быть, «взломать» — слишком сильное слово — исходный код, который я разместил, вполне действителен, я просто не считаю его идеальным.

lawrence-witt 17.12.2020 21:25

Последний вопрос, и я больше не беспокою вас, хахаха, когда вы говорите: «У нас не может быть taskItems в зависимости от toggleTaskDone, и мы также не можем вызывать updateItems, потому что у него такая же (неявная) зависимость», почему мы не может иметь taskItems в зависимости от toggleTaskDone?, спасибо за всю вашу помощь и извините за мой плохой английский xD

Franco 18.12.2020 00:31

Без проблем! Подумайте об этих шагах: 1) taskItems находится в массиве зависимостей, 2) всякий раз, когда taskItems изменяется, обратный вызов воссоздается с новыми значениями, 3) если вы передаете обратный вызов своему запоминаемому компоненту, тогда компонент необходимо перерисовывать каждый раз. время taskItems изменяется, чтобы получить воссозданный обратный вызов, 4) если вы это сделаете, нет смысла запоминать его вообще, потому что все TaskRow компоненты будут перерисовываться всякий раз, когда один из них имеет изменение значения.

lawrence-witt 18.12.2020 02:21

Другие вопросы по теме