React useState не выполняет повторный рендеринг, пока он ссылается на клонированный массив путем распространения

Я пытаюсь отсортировать массив и сразу отразить результат сортировки с помощью хука useState. Я уже новичок, что реакция обнаруживает изменение своего состояния с помощью Object.is(), поэтому пытаюсь распространить массив перед использованием useState, как показано ниже;

const [reviews, setReviews] = useState([...book.reviews]);

    useEffect(() => {
        const sortedReviews = [...reviews]
            .sort((review1: IReview, review2: IReview) =>
                sortBy(review1, review2, sortRule));

        setReviews([...sortedReviews])
        }, [sortRule])

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

Я уже искал решения, и казалось, что многие могут решить проблему, распространив массив до вызова useState, как это объясняет переполнение стека: Почему useState не запускает повторный рендеринг?.
Однако, с моей стороны, это не работает .. Любая помощь будет очень признательна. Спасибо!

[Добавлен] И моя часть рендеринга, как показано ниже;

    <>
        {
            sortedReviews
                .map((review: IReview) => (
                    <ReviewBlock id = {review.id}
                                 review = {review}
                                 targetBook = {book}
                                 setTargetBook = {setBook}/>
                ))
        }
    </>

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

miraj 05.04.2021 02:54

Привет, miraj, я думал, что это не было повторно отрисовано, потому что порядок не был отсортирован как массив sortedReviews . Например, когда я отсортировал sortedReviews на основе CreatedDate, он не был упорядочен таким образом на странице, в то время как значение переменной было. Я понимаю?

Takuya.N 05.04.2021 03:12

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

miraj 05.04.2021 03:16

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

miraj 05.04.2021 03:19

но я подтвердил, что sortedReviews был отсортирован, как ожидалось, поэтому setReviews должен получить идеально упорядоченные массивы, поэтому сортировка должна выполняться в useEffect, как и ожидалось.

Takuya.N 05.04.2021 03:30

Японял твою точку зрения. проблема не в sortReviews. setReviews получает reviews, когда вы инициализировали sortReviews с его помощью.

miraj 05.04.2021 03:50

Спасибо за дальнейшую проверку. Я пробовал, как const sortedReviews =[...reviews].sort....., сделать sortedReviews из клонированного массива, а не из reviews. Однако я все еще испытываю ту же проблему ...

Takuya.N 05.04.2021 04:02

ой! пропустил .

miraj 05.04.2021 04:05
reviews отсутствует в вашем списке зависимостей.
DedaDev 05.04.2021 04:08

Я не думаю, что нам нужно помещать reviews в список зависимостей useEffect, так как он должен обновляться только тогда, когда будет обновлен sortRule. Я проверил, что useEffect работает должным образом при сортировке (т.е. reviews имеет правильно отсортированные элементы). Моя проблема в том, что эта сортировка на reviews не отображается на странице, поскольку useState не запускает повторный рендеринг.

Takuya.N 05.04.2021 04:25
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
10
43
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Когда компоненты не перерисовываются, это почти всегда происходит из-за мутаций состояния. Здесь вы вызываете операцию мутации .sort на reviews. Вам нужно будет распространить массив перед, вы его измените.

useEffect(() => {
  const sortedReviews = [...reviews].sort((review1: IReview, review2: IReview) =>
    sortBy(review1, review2, sortRule)
  );

  setReviews(sortedReviews);
}, [sortRule]);

Но здесь есть и другие проблемы. reviews не является зависимостью от useEffect, поэтому мы хотели бы использовать обратный вызов setReviews, например setReviews(current => [...current].sort(....

В целом отсортированные данные имеют больше смысла для useMemo, чем для useState. sortRule - это состояние, а sortedReviews - производные от него.

То, как вы вызываете sortBy в качестве функции сравнения, кажется немного странным. Может это просто сбивающее с толку имя?

Также вам не нужно включать тип IReview в обратный вызов, если book правильно набран как {reviews: IReview[]}.

Если вы включите свою функцию sortBy и переменную sortRule, я могу вам больше помочь. Но вот что я придумал.

import React, { useState, useMemo } from "react";

type IReview = {
    rating: number;
}

type Book = {
    reviews: IReview[]
}

type SortRule = string; // just a placeholder - what is this really?

declare function sortBy(a: IReview, b: IReview, rule: SortRule): number;

const MyComponent = ({book}: {book: Book}) => {
    const [sortRule, setSortRule] = useState("");

    const reviews = book.reviews;

    const sortedReviews = useMemo( () => {
        return [...reviews].sort((review1, review2) =>
            sortBy(review1, review2, sortRule)
        );
    }, [sortRule, reviews]);
...
}

Ссылка на игровую площадку с машинописным текстом

Привет, Линда, большое спасибо за проверку.

Takuya.N 05.04.2021 04:39

Привет, Линда, большое спасибо за проверку. SortRule на самом деле является перечислением, а значение по умолчанию похоже на (0); enum SortRule { like, new }

Takuya.N 05.04.2021 04:45

Я пробовал, как вы любезно показали, и он почти работает, но часть рендеринга все еще не работает. Я подтвердил, что sortedReviews обрабатывается должным образом при переключении селектора, но после переключения SortRule на новый с подобного, пользовательский интерфейс по-прежнему показывает порядок по умолчанию; на основе Like. Я обновил приведенный выше вопрос по описанию, чтобы добавить часть рендеринга, относящуюся к sortedReviews. Могу я узнать ваше мнение, может ли эта часть быть причиной проблемы?

Takuya.N 05.04.2021 04:46

Вы, вероятно, захотите добавить key = {review.id} в <ReviewBlock />, но я не думаю, что это ваша проблема, и я не вижу ничего плохого в рендере.

Linda Paiste 05.04.2021 05:03
Ответ принят как подходящий

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

Первое решение:

const [reviews, setReviews] = useState([...book.reviews]);

    useEffect(() => {
        const sortedReviews = [...reviews]
            .sort((review1: IReview, review2: IReview) =>
                sortBy(review1, review2, sortRule));

        setReviews(()=> [...sortedReviews]) // <-- Here
        }, [sortRule])

второе решение: Вы можете использовать useRef для получения данных в реальном времени, см. Ниже:

    const [reviews, setReviews] = useState([...book.reviews]);
    const reviewsRef = useRef();
     
            useEffect(() => {
                const sortedReviews = [...reviews]
                    .sort((review1: IReview, review2: IReview) =>
                        sortBy(review1, review2, sortRule));
        
                setReviewRef([...sortedReviews]) 
                }, [sortRule])

function setReviewRef(data){
       setReview(data);
       reviewsRef.current = data;
    }  

Итак, вместо этого используйте состояние отзывы, используйте reviewsRef.current как массив u

Надеюсь, ты сможешь решить эту проблему!

Вы сделали мой день!!!! теперь он отображается по мере сортировки массива. Я понятия не имел об этой уловке. Большое спасибо!

Takuya.N 05.04.2021 05:28

Я ценю его! Спасибо! P.S: Я добавил другое решение, вы можете попробовать использовать в будущем xD

Hermanyo H 05.04.2021 06:23

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