React - как заставить функциональный компонент отрисовываться?

99

У меня есть функциональный компонент, и я хочу заставить его повторно отрисоваться.

Как я могу это сделать?
Поскольку экземпляра нет this, я не могу позвонить this.forceUpdate().

Горакх Натх
источник
1
нет, компонент без состояния не имеет состояния. Используйте вместо этого класс
TryingToImprove
3
Вы действительно имеете в виду «компонент без состояния », а не «функциональный компонент» ?
Крис
3
Чтобы обновить компонент без состояния, необходимо изменить переданные реквизиты.
Fungusanthrax
1
помимо реквизита вы можете использовать хук useState, и компоненты будут повторно рендериться при его изменении
Хамид Шоджа,

Ответы:

152

🎉 Теперь вы можете, используя хуки React

Используя перехватчики реакции, теперь вы можете вызывать useState()свой функциональный компонент.

useState() вернет массив из двух вещей:

  1. Значение, представляющее текущее состояние.
  2. Его сеттер. Используйте его, чтобы обновить значение.

Обновление значения его установщиком заставит ваш функциональный компонент повторно отрисоваться ,
как это forceUpdateделает:

import React, { useState } from 'react';

//create your forceUpdate hook
function useForceUpdate(){
    const [value, setValue] = useState(0); // integer state
    return () => setValue(value => value + 1); // update the state to force render
}

function MyComponent() {
    // call your hook here
    const forceUpdate = useForceUpdate();
    
    return (
        <div>
            {/*Clicking on the button will force to re-render like force update does */}
            <button onClick={forceUpdate}>
                Click to re-render
            </button>
        </div>
    );
}

Вы можете найти демо здесь .

Компонент выше использует настраиваемую функцию перехвата ( useForceUpdate), которая использует перехватчик состояния реакции useState. Он увеличивает значение состояния компонента и, таким образом, сообщает React о необходимости повторного рендеринга компонента.

РЕДАКТИРОВАТЬ

В старой версии этого ответа фрагмент использовал логическое значение и переключал его forceUpdate(). Теперь, когда я отредактировал свой ответ, во фрагменте используется число, а не логическое значение.

Почему ? (вы бы спросили меня)

Потому что однажды со мной случилось, что my forceUpdate()был вызван дважды из двух разных событий, и, таким образом, он сбрасывал логическое значение в исходное состояние, а компонент никогда не отображался.

Это связано с тем, что в useStateсеттере ( setValueздесь) Reactсравнивайте предыдущее состояние с новым и визуализируйте только в том случае, если состояние другое.

Яиропро
источник
5
На этой странице нет информации об использовании хуков для звонка forceUpdate.
jdelman
1
На данный момент вы правы, это лучше, потому что даже сложные хуки еще не выпущены, вы все равно можете использовать его в бета-версии. Но как только они будут выпущены, нет причин, по которым компонент класса будет лучше. Использование хуков делает код чище, чем компонент класса, как показано на видео ниже в reactconf. Во всяком случае, вопрос в том, возможно ли это. Теперь ответ меняется с «Нет» на «Да» из-за крючков. youtube.com/watch?v=wXLf18DsV-I
Yairopro
1
Привет, @DanteTheSmith. Под «верхним уровнем» это означает, что, как вы сказали, хуки не должны вызываться изнутри условия или цикла. Но я могу сказать вам, что вы можете вызывать их из другой функции. А это означает создание собственного хука. Дэн Абрамов, представляя хуки React на React conf, ясно демонстрирует, что это самый чистый и лучший способ обмена логикой между функциональными компонентами: youtu.be/dpw9EHDh2bM?t=2753
Yairopro
1
Нет необходимости переключать какое-либо состояние «для принудительного рендеринга». Вы создаете ложное впечатление, что React «сравнивает» значения предыдущего и следующего состояния, чтобы решить, нужно ли его повторно отображать. Хотя это точно не так.
меандре
1
@meandre Да, это однозначно сравнивает. Мы говорим о useStateловушке, а не о классе, setStateкоторый действительно не выполняет сравнение (если вы не реализуете метод shouldUpdate). См. Ту же демонстрацию, которую я опубликовал, но со статическим значением, используемым для setState, она больше не отображается: codeandbox.io/s/determined-rubin-8598l
Yairopro
49

Обновление response v16.8 (релиз 16 февраля 2019 г.)

Поскольку response 16.8 выпущен с хуками , функциональные компоненты теперь могут сохранять постоянные state. С этой способностью вы можете теперь имитируют A forceUpdate:

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


Прежде чем реагировать 16.8.0

Нет, вы не можете, функциональные компоненты без состояния - это нормально, functionsчто возвращается jsx, у вас нет доступа к методам жизненного цикла React, поскольку вы не расширяетесь из React.Component.

Думайте о функциональном компоненте как о renderметодической части компонентов класса.

Сагив Б.Г.
источник
конечно вы можете. измените реквизит, который к этому приходит
Мариан Зеке Шедай
6
это не требует повторного рендеринга, это просто нормальный рендер. когда вы хотите принудительно выполнить рендеринг, это обычно тот случай, когда вы хотите запустить метод рендеринга, когда он не предназначен для запуска, например, когда нет новых propsили stateизменений. вы не можете принудительно выполнить функцию рендеринга, поскольку для renderкомпонентов без состояния функции нет . Компоненты без состояния, не так extends React.Componentли? Это просто возвращаемые функции jsx.
Sagiv bg
Реквизит для «когда вам нужно принудительно обновить, вы, вероятно, делаете что-то не так» - я знал, что это так, но это просто побудило меня еще раз внимательно взглянуть на мой хук useEffect.
Methodician
13

Официальный FAQ ( https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate ) теперь рекомендует этот способ, если вам действительно это нужно:

  const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }
Грамотей
источник
Ну, я никогда этого не знала
Чарит Джаясанка
11
и вы можете сделать свой код на 7 байт короче и не создавать неиспользуемую переменную:const [, forceUpdate] = useReducer(x => x + 1, 0);
Константин Смолянин
8

Я использовал стороннюю библиотеку под названием use-force-update для принудительной визуализации моих функциональных компонентов реакции. Работал как шарм. Просто используйте импорт пакета в своем проекте и используйте его так.

import useForceUpdate from 'use-force-update';

const MyButton = () => {

  const forceUpdate = useForceUpdate();

  const handleClick = () => {
    alert('I will re-render now.');
    forceUpdate();
  };

  return <button onClick={handleClick} />;
};
Чарит Джаясанка
источник
3
Чтобы сэкономить вам щелчок - useForceUpdateиспользуйте, useCallbackкак указано в других ответах. Эта библиотека - всего лишь служебная библиотека, чтобы сэкономить несколько нажатий клавиш.
asyncwait
1
о, спасибо. Я этого не знал. :)
Чарит Джаясанка
6

Заявление об ограничении ответственности: НЕ ОТВЕТ НА ПРОБЛЕМУ.

Оставив здесь важное примечание:

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

Рассмотрим следующие случаи:

  1. Передайте установщик (setState) дочернему компоненту, который может изменить состояние и вызвать повторный рендеринг родительского компонента.
  2. Рассмотрите возможность подъема состояния
  3. Подумайте о том, чтобы поместить это состояние в свое хранилище Redux, которое может автоматически принудительно выполнить повторную визуализацию подключенных компонентов.
Анкан-Зероб
источник
1
У меня есть случай, когда мне нужно было отключить обновления компонентов и заставить их, когда я хочу (это движущаяся линейка, которая обновляет ее значение при каждом движении, и обновления вызвали мерцающее поведение). Поэтому я решил использовать классы в этом компоненте и рекомендую, чтобы все компоненты, которым требуется глубокий контроль над поведением рендеринга, выполнялись в классах, потому что в настоящее время хуки не обеспечивают точного контроля над этим поведением. (текущая версия React - 16.12)
Майкл Уоллес
Я думал то же самое, но мне нужно было быстрое исправление, поэтому я использовал принудительное обновление.
Чарит Джаясанка,
В реальном мире ему есть применение. Ничто не бывает идеальным, и если вы когда-нибудь захотите выпустить программное обеспечение, вам лучше знать, как заставить вещи происходить, когда кто-то другой их облажает.
Brain2000 02 авг.2020,
1

Это можно сделать без явного использования хуков при условии, что вы добавляете опору в свой компонент и состояние родительскому компоненту компонента без состояния:

const ParentComponent = props => {
  const [updateNow, setUpdateNow] = useState(true)

  const updateFunc = () => {
    setUpdateNow(!updateNow)
  }

  const MyComponent = props => {
    return (<div> .... </div>)
  }

  const MyButtonComponent = props => {
    return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
  }

  return (
    <div> 
      <MyComponent updateMe={updateNow} />
      <MyButtonComponent updateFunc={updateFunc}/>
    </div>
  )
}
Чарльз Гудвин
источник
1

Принятый ответ хорош. Просто чтобы было легче понять.

Пример компонента:

export default function MyComponent(props) {

    const [updateView, setUpdateView] = useState(0);

    return (
        <>
            <span style={{ display: "none" }}>{updateView}</span>
        </>
    );
}

Для принудительного повторного рендеринга вызовите приведенный ниже код:

setUpdateView((updateView) => ++updateView);
Манохар Редди Поредди
источник
-1

Ни один из них не дал мне удовлетворительного ответа, поэтому в конце концов я получил то, что хотел, с помощью keyprop, useRef и некоторого генератора случайных идентификаторов, например shortid.

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

Пример кода:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// ... your JSX functional component, import shortid somewhere

const [render, rerender] = useState(shortid.generate())

const messageList = useRef([
    new Message({id: 1, message: "Hi, let's get started!"})
])

useEffect(()=>{
    await sleep(500)
    messageList.current.push(new Message({id: 1, message: "What's your name?"}))
    // ... more stuff
    // now trigger the update
    rerender(shortid.generate())
}, [])

// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div> 

Фактически, это также позволило мне прокрутить что-то вроде сообщения чата с прокруткой.

const waitChat = async (ms) => {
    let text = "."
    for (let i = 0; i < ms; i += 200) {
        if (messageList.current[messageList.current.length - 1].id === 100) {
            messageList.current = messageList.current.filter(({id}) => id !== 100)
        }
        messageList.current.push(new Message({
            id: 100,
            message: text
        }))
        if (text.length === 3) {
            text = "."
        } else {
            text += "."
        }
        rerender(shortid.generate())
        await sleep(200)
    }
    if (messageList.current[messageList.current.length - 1].id === 100) {
        messageList.current = messageList.current.filter(({id}) => id !== 100)
    }
}
danieltan95
источник