Reagować - jak zmusić składnik funkcji do renderowania?

99

Mam składnik funkcyjny i chcę zmusić go do ponownego renderowania.

Jak mogę to zrobić?
Ponieważ nie ma instancji this, nie mogę zadzwonić this.forceUpdate().

Gorakh Nath
źródło
1
nie, składnik bezstanowy nie ma stanu. Zamiast tego użyj zajęć
TryingToImprove
3
Czy naprawdę masz na myśli „składnik bezstanowy ”, a nie „składnik funkcjonalny” ?
Chris,
3
Aby zaktualizować składnik bezstanowy, przekazane właściwości muszą ulec zmianie.
fungusanthrax
1
oprócz rekwizytów możesz użyć hook useState, a komponenty zostaną ponownie wyrenderowane po zmianie
Hamid Shoja,

Odpowiedzi:

152

🎉 Teraz możesz, używając haków React

Używając zaczepów reagowania, możesz teraz wywołać useState()swój komponent funkcji.

useState() zwróci tablicę dwóch rzeczy:

  1. Wartość reprezentująca aktualny stan.
  2. Jego ustawiacz. Użyj go, aby zaktualizować wartość.

Zaktualizowanie wartości przez jej ustawiającego wymusi ponowne renderowanie komponentu funkcji ,
tak jak 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>
    );
}

Możesz znaleźć demo tutaj .

Powyższy komponent używa niestandardowej funkcji podpięcia ( useForceUpdate), która używa zaczepu stanu reagowania useState. Zwiększa wartość stanu komponentu, a tym samym nakazuje Reactowi ponowne renderowanie komponentu.

EDYTOWAĆ

W starej wersji tej odpowiedzi fragment kodu używał wartości logicznej i włączał ją forceUpdate(). Teraz, gdy zredagowałem odpowiedź, fragment kodu używa liczby, a nie wartości logicznej.

Czemu ? (zapytałbyś mnie)

Ponieważ kiedyś zdarzyło mi się, że mój forceUpdate()został wywołany dwukrotnie później z 2 różnych zdarzeń, a zatem resetował wartość logiczną do jej pierwotnego stanu, a komponent nigdy się nie renderował.

Dzieje się tak, ponieważ w useStateseterze ( setValuetutaj) Reactporównaj poprzedni stan z nowym i renderuj tylko wtedy, gdy stan jest inny.

Yairopro
źródło
5
Nic na tej stronie nie zawiera informacji o używaniu hooków do wywoływania forceUpdate.
jdelman
1
Na razie masz rację, jest lepiej, ponieważ nawet twarde haczyki nie zostały jeszcze wydane, nadal możesz z nich korzystać w wersji beta. Ale po ich wydaniu nie ma powodu, dla którego komponent klasy będzie lepszy. Używanie hooków sprawia, że ​​kod jest czystszy niż komponent klasy, jak pokazuje poniższy film w Reactconf. W każdym razie pytanie brzmi, czy jest to możliwe. Odpowiedź zmienia się teraz z „Nie” na „Tak” z powodu haków. youtube.com/watch?v=wXLf18DsV-I
Yairopro,
1
Cześć @DanteTheSmith. „Najwyższy poziom” oznacza, że ​​haczyki nie mogą być wywoływane z wnętrza warunku lub pętli, jak powiedziałeś. Ale mogę ci powiedzieć, że możesz je wywołać z wnętrza innej funkcji. A to oznacza stworzenie niestandardowego haka. Dan Abramov, jak prezentuje się w haki React React conf, wyraźnie pokazują, że to jest najczystszym i najlepszym sposobem na logice zakładowego między elementami funkcjonalnymi: youtu.be/dpw9EHDh2bM?t=2753
Yairopro
1
Nie ma potrzeby przełączania żadnego stanu „w celu wymuszenia renderowania”. Tworzysz fałszywe wrażenie, że React porównuje wartości poprzedniego i następnego stanu, aby zdecydować, czy musi zostać ponownie wyrenderowany. Chociaż zdecydowanie nie.
meandre
1
@meandre Tak, zdecydowanie porównuje. Mówimy o useStatehaku, a nie klasie, setStatektóra rzeczywiście nie dokonuje porównania (chyba że zaimplementujesz metodę shouldUpdate). Zobacz to samo demo, które opublikowałem, ale ze statyczną wartością używaną do setState, nie renderuje się ponownie : codeandbox.io/s/determined-rubin-8598l
Yairopro
49

Aktualizacja React v16.8 (realease 16 lutego 2019)

Ponieważ reagują 16.8 wypuszczone z haczykami , komponenty funkcyjne mają teraz zdolność utrzymywania trwałości state. Dzięki tej zdolności można teraz naśladować A forceUpdate:

Zwróć uwagę, że to podejście powinno zostać ponownie rozważone iw większości przypadków, gdy musisz wymusić aktualizację, prawdopodobnie robisz coś źle.


Przed reakcją 16.8.0

Nie, nie możesz, komponenty funkcji bez stanu są po prostu normalne, functionsktóre powracają jsx, nie masz żadnego dostępu do metod cyklu życia Reacta, ponieważ nie rozszerzasz z React.Component.

Pomyśl o komponencie funkcyjnym jako o renderczęści metodycznej komponentów klasy.

Sagiv bg
źródło
ofc możesz. zmień rekwizyty, które do tego
dojdą
6
to nie wymusza ponownego renderowania, to tylko normalny render. kiedy chcesz wymusić renderowanie, zwykle jest to przypadek, gdy chcesz uruchomić metodę renderowania, gdy nie została ona zaprojektowana do działania, na przykład gdy nie ma nowych propslub statezmian. nie możesz wymusić funkcji renderującej, ponieważ nie ma renderfunkcji na składnikach bezstanowych. komponenty bezstanowe, czyż extends React.Componentnie są to zwykłe funkcje, które zwracają jsx.
Sagiv bg
Podziękowania za „kiedy musisz wymusić aktualizację, prawdopodobnie robisz coś źle” - wiedziałem, że tak jest, ale to tylko skłoniło mnie do jeszcze jednego dokładnego przyjrzenia się mojemu hookowi useEffect.
Methodician,
14

Oficjalne FAQ ( https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate ) teraz zaleca ten sposób, jeśli naprawdę musisz to zrobić:

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

  function handleClick() {
    forceUpdate();
  }
Gramotei
źródło
1
Cóż, nigdy tego nie wiedziałem
Charith Jayasanka
11
i możesz skrócić swój kod o 7 bajtów i nie tworzyć nieużywanej zmiennej:const [, forceUpdate] = useReducer(x => x + 1, 0);
Konstantin Smolyanin
8

Użyłem biblioteki innej firmy o nazwie use-force-update, aby wymusić renderowanie komponentów funkcjonalnych reagujących. Działało jak urok. Po prostu użyj importu pakietu w swoim projekcie i używaj w ten sposób.

import useForceUpdate from 'use-force-update';

const MyButton = () => {

  const forceUpdate = useForceUpdate();

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

  return <button onClick={handleClick} />;
};
Charith Jayasanka
źródło
3
Aby zaoszczędzić Ci kliknięcia - useForceUpdatezastosowania useCallbackwymienione w innych odpowiedziach. Ta biblioteka jest po prostu biblioteką narzędziową, która pozwala zaoszczędzić kilka naciśnięć klawiszy.
asyncwait
1
Och dziękuje. Nie wiedziałem tego. :)
Charith Jayasanka
6

Zastrzeżenie: NIE ODPOWIEDŹ NA PROBLEM.

Pozostawiając tutaj ważną notatkę:

Jeśli próbujesz wymusić aktualizację składnika bezstanowego, prawdopodobnie jest coś nie tak z Twoim projektem.

Rozważ następujące przypadki:

  1. Przekaż metodę ustawiającą (setState) do komponentu podrzędnego, który może zmienić stan i spowodować ponowne renderowanie komponentu nadrzędnego.
  2. Rozważ podniesienie stanu
  3. Rozważ umieszczenie tego stanu w swoim sklepie Redux, co może automatycznie wymusić ponowne renderowanie podłączonych komponentów.
Ankan-Zerob
źródło
1
Mam przypadek, w którym musiałem wyłączyć aktualizacje komponentów i wymusić je tylko wtedy, gdy chcę (jest to ruchoma linijka, która aktualizuje wartość każdego ruchu, a aktualizacje powodowały migotanie). Postanowiłem więc użyć klas w tym komponencie i zalecam, aby wszystkie komponenty, które wymagają głębokiej kontroli zachowania renderowania, były wykonywane w klasach, ponieważ obecnie punkty zaczepienia nie zapewniają dokładnej kontroli nad tym zachowaniem. (aktualna wersja Reacta to 16.12)
Michael Wallace,
Myślałem tak samo, ale chciałem szybko naprawić, więc użyłem wymuszonej aktualizacji.
Charith Jayasanka
W prawdziwym świecie są dla niego zastosowania. Nic nigdy nie jest doskonałe, a jeśli kiedykolwiek chcesz mieć oprogramowanie od ręki, lepiej wiedz, jak sprawić, by coś się stało, gdy ktoś inny je spieprzy.
Brain2000
1

Można to zrobić bez jawnego używania hooków, pod warunkiem, że dodasz właściwość do komponentu i stan do komponentu nadrzędnego komponentu bezstanowego:

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>
  )
}
Charles Goodwin
źródło
1

Przyjęta odpowiedź jest dobra. Żeby było łatwiej zrozumieć.

Przykładowy komponent:

export default function MyComponent(props) {

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

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

Aby wymusić ponowne renderowanie, wywołaj poniższy kod:

setUpdateView((updateView) => ++updateView);
Manohar Reddy Poreddy
źródło
-1

Żadne z nich nie dało mi satysfakcjonującej odpowiedzi, więc ostatecznie otrzymałem to, co chciałem, z keyprop, useRef i jakimś generatorem losowych identyfikatorów shortid.

Zasadniczo chciałem, aby aplikacja do czatu odegrała się, gdy ktoś po raz pierwszy otworzy aplikację. Tak więc potrzebowałem pełnej kontroli nad tym, kiedy i jakie odpowiedzi są aktualizowane, z łatwością asynchronicznego oczekiwania.

Przykładowy kod:

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> 

W rzeczywistości pozwoliło mi to również rzucić coś w rodzaju wiadomości na czacie z przewijaniem.

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
źródło