Rozważając poniżej przykład haków
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Zasadniczo używamy metody this.forceUpdate (), aby wymusić na komponencie natychmiastowe ponowne renderowanie w komponentach klasy React, jak na poniższym przykładzie
class Test extends Component{
constructor(props){
super(props);
this.state = {
count:0,
count2: 100
}
this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component
}
setCount(){
let count = this.state.count;
count = count+1;
let count2 = this.state.count2;
count2 = count2+1;
this.setState({count});
this.forceUpdate();
//before below setState the component will re-render immediately when this.forceUpdate() is called
this.setState({count2: count
}
render(){
return (<div>
<span>Count: {this.state.count}></span>.
<button onClick={this.setCount}></button>
</div>
}
}
Ale moje zapytanie brzmi: Jak zmusić powyższy komponent funkcjonalny do natychmiastowego ponownego renderowania za pomocą haków?
javascript
reactjs
react-native
react-hooks
Hemadri Dasari
źródło
źródło
this.forceUpdate()
? Może jest sposób na osiągnięcie tego samego bez tego.Odpowiedzi:
Jest to możliwe z
useState
lubuseReducer
, ponieważuseState
używauseReducer
wewnętrznie :const [, updateState] = React.useState(); const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate
nie jest przeznaczony do użytku w normalnych okolicznościach, tylko w testach lub innych wyjątkowych przypadkach. Do tej sytuacji można podejść w bardziej konwencjonalny sposób.setCount
jest przykładem nieprawidłowego użyciaforceUpdate
,setState
jest asynchroniczny ze względu na wydajność i nie powinien być wymuszany na synchronizacji tylko dlatego, że aktualizacje stanu nie zostały wykonane poprawnie. Jeśli stan opiera się na wcześniej ustawionym stanie, należy to zrobić za pomocą funkcji Updater ,setCount
może nie być ilustracyjnym przykładem, ponieważ jego cel jest niejasny, ale tak jest w przypadku funkcji aktualizatora:setCount(){ this.setState(({count}) => ({ count: count + 1 })); this.setState(({count2}) => ({ count2: count + 1 })); this.setState(({count}) => ({ count2: count + 1 })); }
Jest to tłumaczone 1: 1 na podpięcia, z wyjątkiem funkcji, które są używane jako wywołania zwrotne, powinny być lepiej zapamiętywane:
const [state, setState] = useState({ count: 0, count2: 100 }); const setCount = useCallback(() => { setState(({count}) => ({ count: count + 1 })); setState(({count2}) => ({ count2: count + 1 })); setState(({count}) => ({ count2: count + 1 })); }, []);
źródło
const forceUpdate = useCallback(() => updateState({}), []);
działa? Czy to w ogóle wymusza aktualizację?useCallback
zapamiętujeforceUpdate
, więc pozostaje niezmienna przez cały okres eksploatacji elementu i może być bezpiecznie przekazywana jako rekwizyt.updateState({})
aktualizuje stan nowym obiektem przy każdymforceUpdate
wywołaniu, co powoduje ponowne renderowanie. Więc tak, wymusza aktualizację po wywołaniu.useCallback
część nie jest naprawdę konieczna. Powinno po prostu działać bez niego.updateState(0)
ponieważ0
jest to prymitywny typ danych? Czy musi to być obiekt, czy teżupdateState([])
(np. Użycie z tablicą) również zadziała?updateState(Math.random())
lub jako licznik.Jak wspomnieli inni,
useState
działa - oto jak mobx-react-lite wdraża aktualizacje - możesz zrobić coś podobnego.Zdefiniuj nowy hak,
useForceUpdate
-import { useState, useCallback } from 'react' export function useForceUpdate() { const [, setTick] = useState(0); const update = useCallback(() => { setTick(tick => tick + 1); }, []) return update; }
i użyj go w komponencie -
const forceUpdate = useForceUpdate(); if (...) { forceUpdate(); // force re-render }
Zobacz https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts i https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts
źródło
useForceUpdate
zwróci nową funkcję za każdym razem, gdy funkcja zostanie ponownie renderowana. AbyforceUpdate
działać, gdy jest używany wuseEffect
, powinien zwrócićuseCallback(update)
Zobacz kentcdodds.com/blog/usememo-and-usecallbackOgólnie rzecz biorąc, możesz użyć dowolnego podejścia do obsługi stanu, które chcesz wyzwolić aktualizację.
Dzięki TypeScript
przykład kodów i skrzynki
useState
const forceUpdate: () => void = React.useState()[1].bind(null, {}) // see NOTE below
useReducer
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
jako niestandardowy haczyk
Po prostu zapakuj dowolne podejście, które preferujesz w ten sposób
function useForceUpdate(): () => void { return React.useReducer(() => ({}), {})[1] as () => void // <- paste here }
Jak to działa?
„ Wyzwolenie aktualizacji ” oznacza powiadomienie silnika React, że jakaś wartość uległa zmianie i że powinien on ponownie zwrócić Twój komponent.
[, setState]
oduseState()
wymaga parametru. Pozbywamy się go, wiążąc świeży obiekt{}
.() => ({})
inuseReducer
jest fikcyjnym reduktorem, który zwraca nowy obiekt za każdym razem, gdy wykonywana jest akcja.{}
(świeży obiekt) jest wymagany, aby wyzwalał aktualizację poprzez zmianę odwołania w stanie.PS:
useState
po prostu zawija sięuseReducer
wewnętrznie. źródłoUWAGA: Używanie .bind z useState powoduje zmianę odwołania do funkcji między renderowaniami. Możliwe jest owinięcie go wewnątrz useCallback, jak już wyjaśniono tutaj , ale wtedy nie byłby to seksowny one-liner ™ . Wersja Reducer już utrzymuje równość odwołań między renderowaniami. Jest to ważne, jeśli chcesz przekazać funkcję forceUpdate we właściwościach.
zwykły JS
const forceUpdate = React.useState()[1].bind(null, {}) // see NOTE above const forceUpdate = React.useReducer(() => ({}))[1]
źródło
Alternatywa dla odpowiedzi @ MinhKha:
Może być znacznie czystszy dzięki
useReducer
:const [, forceUpdate] = useReducer(x => x + 1, 0);
Zastosowanie:
forceUpdate()
- odkurzacz bez parametrówźródło
Możesz po prostu zdefiniować useState w ten sposób:
const [, forceUpdate] = React.useState(0);
I zastosowanie:
forceUpdate(n => !n)
Mam nadzieję, że to pomoże !
źródło
Oficjalne rozwiązanie React Hooks FAQ dla
forceUpdate
:const [_, forceUpdate] = useReducer((x) => x + 1, 0); // usage <button onClick={forceUpdate}>Force update</button>
Przykład roboczy
Pokaż fragment kodu
const App = () => { const [_, forceUpdate] = useReducer((x) => x + 1, 0); return ( <div> <button onClick={forceUpdate}>Force update</button> <p>Forced update {_} times</p> </div> ); }; ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script> <script>var useReducer = React.useReducer</script> <div id="root"></div>
źródło
Najlepiej, aby twój komponent był zależny tylko od stanu i właściwości i będzie działał zgodnie z oczekiwaniami, ale jeśli naprawdę potrzebujesz funkcji, aby zmusić komponent do ponownego renderowania, możesz użyć
useState
haka i wywołać funkcję w razie potrzeby.Przykład
const { useState, useEffect } = React; function Foo() { const [, forceUpdate] = useState(); useEffect(() => { setTimeout(forceUpdate, 2000); }, []); return <div>{Date.now()}</div>; } ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div>
źródło
forceUpdate()
i tylko do odczytu zthis.props
ithis.state
wrender()
.”and I can't think of a good use case for it right now
Mam jeden, a co powiesz na to, że stan nie jest kontrolowany przez React. Nie używam Reduxa, ale zakładam, że musi dokonać jakiejś wymuszonej aktualizacji. Osobiście używam proxy do utrzymania stanu, komponent może następnie sprawdzić zmiany właściwości, a następnie zaktualizować. Wydaje się też działać bardzo wydajnie. Np. Wszystkie moje komponenty są następnie kontrolowane przez proxy, ten serwer proxy jest obsługiwany przez SessionStorage, więc nawet jeśli użytkownik odświeży swoją stronę internetową, stan listy rozwijanej itp. Jest utrzymywany. IOW: W ogóle nie używam stanu, wszystko jest kontrolowane za pomocą rekwizytów.Prosty kod
const forceUpdate = React.useReducer(bool => !bool)[1];
Posługiwać się:
źródło
Możesz (ab) użyć normalnych hooków, aby wymusić ponowne wydanie, wykorzystując fakt, że React nie drukuje wartości logicznych w kodzie JSX
// create a hook const [forceRerender, setForceRerender] = React.useState(true); // ...put this line where you want to force a rerender setForceRerender(!forceRerender); // ...make sure that {forceRerender} is "visible" in your js code // ({forceRerender} will not actually be visible since booleans are // not printed, but updating its value will nonetheless force a // rerender) return ( <div>{forceRerender}</div> )
źródło
Potencjalną opcją jest wymuszenie aktualizacji tylko przy użyciu określonego komponentu
key
. Aktualizacja klucza wyzwala renderowanie komponentu (którego wcześniej nie udało się zaktualizować)Na przykład:
const [tableKey, setTableKey] = useState(1); ... useEffect(() => { ... setTableKey(tableKey + 1); }, [tableData]); ... <DataTable key={tableKey} data={tableData}/>
źródło
Rozwiązanie w jednej linii:
const [,forceRender] = useReducer((s) => s+1, 0)
Możesz dowiedzieć się więcej o useReducer tutaj. https://reactjs.org/docs/hooks-reference.html#usereducer
źródło
Rozwiązanie jednoprzewodowe:
const useForceUpdate = () => useState()[1];
useState zwraca parę wartości: aktualny stan i funkcję, która go aktualizuje - stan i ustawiający , tutaj używamy tylko ustawiającego, aby wymusić ponowne renderowanie.
źródło
Moja odmiana
forceUpdate
nie polega na a,counter
ale raczej poprzez obiekt:// Emulates `forceUpdate()` const [unusedState, setUnusedState] = useState() const forceUpdate = useCallback(() => setUnusedState({}), [])
Ponieważ za
{} !== {}
każdym razem.źródło
useCallback()
? Skąd to pochodzi? ups. Widzę to teraz ...Spowoduje to trzykrotne renderowanie komponentów zależnych (tablice z równymi elementami nie są równe):
const [msg, setMsg] = useState([""]) setMsg(["test"]) setMsg(["test"]) setMsg(["test"])
źródło
react-tidy
ma niestandardowy punkt zaczepienia, który służy do tego, o nazwieuseRefresh
:import React from 'react' import {useRefresh} from 'react-tidy' function App() { const refresh = useRefresh() return ( <p> The time is {new Date()} <button onClick={refresh}>Refresh</button> </p> ) }
Dowiedz się więcej o tym haku
Zastrzeżenie Jestem autorem tej biblioteki.
źródło
W przypadku zwykłych komponentów opartych na klasie React, zapoznaj się z dokumentami React dla
forceUpdate
interfejsu API pod tym adresem URL. Dokumenty wspominają, że:Jednak w dokumentach wspomniano również, że:
Tak więc, chociaż przypadki użycia
forceUpdate
mogą być rzadkie i nigdy z niego nie korzystałem, jednak widziałem go używanego przez innych programistów w niektórych starszych projektach korporacyjnych, nad którymi pracowałem.Tak więc, aby uzyskać równoważną funkcjonalność dla komponentów funkcjonalnych, zapoznaj się z dokumentacją React dla HOOKSów pod tym adresem URL. W powyższym adresie URL można użyć haka „useReducer”, aby zapewnić plik
forceUpdate
funkcjonalności dla komponentów funkcjonalnych.Poniżej znajduje się działający przykład kodu
that does not use state or props
, który jest również dostępny w CodeSandbox pod tym adresem URLimport React, { useReducer, useRef } from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { // Use the useRef hook to store a mutable value inside a functional component for the counter let countref = useRef(0); const [, forceUpdate] = useReducer(x => x + 1, 0); function handleClick() { countref.current++; console.log("Count = ", countref.current); forceUpdate(); // If you comment this out, the date and count in the screen will not be updated } return ( <div className="App"> <h1> {new Date().toLocaleString()} </h1> <h2>You clicked {countref.current} times</h2> <button onClick={() => { handleClick(); }} > ClickToUpdateDateAndCount </button> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
UWAGA: Alternatywne podejście wykorzystujące hak useState (zamiast useReducer) jest również dostępne pod tym adresem URL.
źródło