React'te bileşeni kancalarla yeniden oluşturmaya nasıl zorlayabilirim?


108

Aşağıdaki kancalar örneğini ele alırsak

   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>
        );
     }

Temel olarak, aşağıdaki örnekte olduğu gibi, bileşeni React sınıfı bileşenlerde hemen yeniden oluşturmaya zorlamak için this.forceUpdate () yöntemini kullanıyoruz.

    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>
        }
 }

Ancak benim sorgum, yukarıdaki işlevsel bileşeni kancalarla hemen yeniden oluşturmaya nasıl zorlayabilirim?


1
Orijinal bileşeninizin kullanan bir sürümünü gönderebilir misiniz this.forceUpdate()? Belki de aynı şeyi onsuz başarmanın bir yolu vardır.
Jacob

SetCount'taki son satır kesildi. SetCount'un şu anki durumunda amacının ne olduğu belli değil.
Estus Flask

Bu, bundan sonraki bir eylemdir. ForceUpdate (); Bunu sadece bunu açıklamak için ekledim. ForceUpdate () soruma
Hemadri Dasari

Değeri ne olursa olsun: Bununla güreşiyordum çünkü manuel olarak yeniden oluşturmaya ihtiyacım olduğunu düşündüm ve sonunda, harici olarak tutulan bir değişkeni bir durum kancasına taşımam ve ayar işlevinden yararlanmam gerektiğini fark ettim, bu da tüm sorunlarımı olmadan çözdü. yeniden oluşturma. Asla gerekli olmadığını söylememekle birlikte, özel kullanım durumunuzda gerçekten gerekli olup olmadığını görmek için üçüncü ve dördüncü bir göz atmaya değer .
Alexander Nied

Yanıtlar:


74

Bu mümkündür useStateveya useReducerberi useStatekullanımlar useReduceriçten :

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

forceUpdatenormal koşullar altında, yalnızca testlerde veya diğer olağanüstü durumlarda kullanılması amaçlanmamıştır. Bu durum daha geleneksel bir şekilde ele alınabilir.

setCountyanlış kullanılan bir örnektir forceUpdate, setStateperformans nedenleriyle eşzamansızsa ve devlet güncelleştirmeleri doğru gerçekleştirilen getirilmediği için senkron olmak zorunda olmamalıdır. Bir durum önceden ayarlanmış duruma bağlıysa, bu güncelleme işlevi ile yapılmalıdır ,

Durumu önceki duruma göre ayarlamanız gerekiyorsa, aşağıdaki güncelleyici bağımsız değişkenini okuyun.

<...>

Güncelleyici işlevi tarafından alınan durum ve nesnelerin güncel olması garanti edilir. Güncelleyicinin çıktısı sığ bir şekilde durum ile birleştirilir.

setCount açıklayıcı bir örnek olmayabilir çünkü amacı belirsizdir, ancak güncelleme işlevi için durum budur:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

Bu, geri çağırma olarak kullanılan işlevlerin daha iyi hatırlanması gerektiği dışında, kancalara 1: 1 olarak çevrilir:

   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 }));
   }, []);

Nasıl const forceUpdate = useCallback(() => updateState({}), []);çalışır? Güncellemeyi bile zorluyor mu?
Dávid Molnár

1
@ DávidMolnár useCallbackbelleğe alır forceUpdate, böylece bileşen ömrü boyunca sabit kalır ve güvenli bir şekilde destek olarak aktarılabilir. updateState({})durumu her forceUpdateçağrıda yeni nesneyle günceller , bu da yeniden oluşturma ile sonuçlanır. Yani evet, çağrıldığında bir güncellemeye zorlar.
Estus Flask

3
Yani, bu useCallbackkısım gerçekten gerekli değil. Onsuz iyi çalışmalı.
Dávid Molnár

Ve işe yaramaz updateState(0)çünkü 0ilkel bir veri türü? Bir nesne mi olmalı yoksa updateState([])(yani bir dizi ile kullanım) da işe yarayacak mı?
Andru

1
@Andru Evet, bir durum bir kez güncellenir çünkü 0 === 0. Evet, dizi de bir nesne olduğu için çalışacaktır. Eşitlik kontrolünden geçmeyen her şey, updateState(Math.random())bir sayaç gibi kullanılabilir .
Estus Flask

26

Diğerlerinin de belirttiği gibi, useStateçalışır - işte mobx-react-lite güncellemeleri nasıl uygular - benzer bir şey yapabilirsiniz.

Yeni bir kanca tanımlayın, useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

ve bir bileşende kullanın -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

Bkz. Https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts ve https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts


2
Kancalardan anladığım kadarıyla bu, useForceUpdateişlev her yeniden işlendiğinde yeni bir işlev döndüreceği için işe yaramayabilir . Bir forceUpdateiçinde kullanıldığında çalışması useEffectiçin geri dönmelidir useCallback(update)Bkz. Kentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud

Teşekkürler @MartinRatinaud - evet, useCallback (?) Olmadan bellek sızıntısına neden olabilir - düzeltildi.
Brian Burns

24

Genel olarak, bir güncellemeyi tetiklemek istediğiniz herhangi bir durum işleme yaklaşımını kullanabilirsiniz.

TypeScript ile

kodlar ve kutu örneği

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

özel kanca olarak

Sadece tercih ettiğiniz yaklaşımı böyle sarın

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

Bu nasıl çalışır?

" Bir güncellemeyi tetiklemek " , React motoruna bazı değerlerin değiştiğini ve bileşeninizi yeniden göndermesi gerektiğini söylemek anlamına gelir.

[, setState]from useState()bir parametre gerektirir. Yeni bir nesneyi bağlayarak ondan kurtuluruz {}.
() => ({})in useReducer, her eylem gönderildiğinde yeni bir nesne döndüren sahte bir indirgeyicidir.
{} (yeni nesne) , durumdaki bir referansı değiştirerek bir güncellemeyi tetiklemesi için gereklidir.

Not: useStatesadece useReduceriçten sarılır . kaynak

NOT: useState ile .bind kullanılması, işlemeler arasında işlev başvurusunda bir değişikliğe neden olur. Burada daha önce açıklandığı gibi useCallback'in içine sarmak mümkündür, ancak o zaman bu seksi bir tek astar ™ olmaz . Reducer sürümü , işlemeler arasında referans eşitliğini zaten korur . ForceUpdate işlevini props'ta geçirmek istiyorsanız bu önemlidir.

düz JS

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]

14

@ MinhKha'nın cevabına alternatif:

Şunlarla çok daha temiz olabilir useReducer:

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

Kullanım: forceUpdate()- paramsız temizleyici


12

UseState'i şu şekilde tanımlayabilirsiniz:

const [, forceUpdate] = React.useState(0);

Ve kullanım: forceUpdate(n => !n)

Umarım bu yardımcı olur!


7
ForceUpdate işleme başına çift kez çağrılırsa başarısız olur.
Izhaki

Değeri artırmaya devam edin.
Gary

2
Bu hataya açıktır ve kaldırılmalı veya düzenlenmelidir.
slikts

10

Tercihen bileşeninizin yalnızca duruma ve props'a bağlı olmasını sağlamalısınız ve beklendiği gibi çalışacaktır, ancak bileşeni yeniden oluşturmaya zorlamak için gerçekten bir işleve ihtiyacınız varsa, useStatekancayı kullanabilir ve gerektiğinde işlevi çağırabilirsiniz.

Misal

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/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>


Tamam ama React neden this.forceUpdate (); bileşen önceki sürümlerde setState ile yeniden oluşturulduğunda ilk etapta?
Hemadri Dasari

1
@ Think-Twice Kişisel olarak hiç kullanmadım ve şu anda bunun için iyi bir kullanım durumu düşünemiyorum, ancak bu gerçekten özel kullanım durumları için bir kaçış kapağı sanırım. "Normalde her kullanımlarını önlemek için çalışmalısınız forceUpdate()ve sadece okuma this.propsve this.stateiçinde render()."
Tholle

1
Kabul. Bunu deneyimimde hiç kullanmadım ama nasıl çalıştığını biliyorum, bu yüzden aynı şeyin kancalarda nasıl yapılabileceğini anlamaya çalışıyorum
Hemadri Dasari

1
@Tholle and I can't think of a good use case for it right now Bir tane var, React tarafından devlet kontrol edilmezse nasıl olur? Redux kullanmıyorum, ancak bunun bir çeşit zorunlu güncelleme yapması gerektiğini varsayıyorum. Durumu korumak için kişisel olarak proxy'leri kullanıyorum, bileşen daha sonra özellik değişikliklerini kontrol edebilir ve ardından güncelleyebilir. Çok verimli çalışıyor gibi görünüyor. Örneğin, tüm bileşenlerim bir proxy tarafından kontrol edilir, bu proxy SessionStorage tarafından desteklenir, böylece kullanıcı web sayfasını yenilese bile, açılır menülerin durumu vb. Korunur. IOW: Durumu hiç kullanmıyorum, her şey sahne ile kontrol ediliyor.
Keith

9

React Hooks SSS resmi çözümü forceUpdate:

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

Çalışma örneği


7

Basit kod

const forceUpdate = React.useReducer(bool => !bool)[1];

Kullanım:

forceUpdate();

5

React'in JSX kodunda boole'leri yazdırmaması gerçeğinden yararlanarak (ab) bir yeniden oluşturucuyu zorlamak için normal kancaları kullanabilirsiniz

// 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>
)


1
Bu durumda, setBoolean iki kez çocuk değiştirdiğinde React.useEffect güncellemeyi yeniden kazanamayabilir.
alma

Anladığım kadarıyla React, her boole güncellemesini, boole hızla geri dönse bile sayfayı yeniden oluşturmak için bir neden olarak ele alıyor. Bununla birlikte, React elbette bir standart değildir ve bu durumda tam olarak nasıl çalıştığı tanımsızdır ve değişebilir.
Fergie

1
Bilmiyorum. Nedense bu versiyona ilgi duyuyorum. Beni gıdıklıyor :-). Bunun yanı sıra, saf hissettiriyor. JSX'imin bağlı olduğu bir şey değişti, bu yüzden yeniden oluşturuyorum. Görünmezlik, bu IMO'dan uzaklaşmaz.
Adrian Bartholomew

4

Olası seçenek, güncellemeyi yalnızca belirli bir bileşen kullanarak zorlamaktır key. Anahtarın güncellenmesi, bileşenin (daha önce güncellenemeyen) bir işlemesini tetikler

Örneğin:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>

1
Durum değeri ile yeniden oluşturma gereksinimi arasında 1: 1 ilişki varsa, bu genellikle en temiz yoldur.
jaimefps

1
bu kolay bir çözüm
Priyanka V


3

Tek satırlık çözüm:

const useForceUpdate = () => useState()[1];

useState bir çift değer döndürür: mevcut durum ve onu güncelleyen bir işlev - durum ve ayarlayıcı , burada yeniden oluşturmaya zorlamak için yalnızca ayarlayıcıyı kullanıyoruz.


2

Benim varyasyonum forceUpdatebir yoluyla counterdeğil, daha çok bir nesne yoluyla:

// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])

Çünkü {} !== {}her seferinde.


Nedir useCallback()? Bu nereden geldi? oops. Şimdi görüyorum ...
zipzit

2

Bu, bileşenleri 3 kez oluşturacaktır (eşit öğelere sahip diziler eşit değildir):

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])

1
Diziye bir parça eklemenize bile gerek olmadığına inanıyorum. Boş bir dizi, tıpkı nesneler gibi, başka bir boş diziye sadece farklı referanslarla eşit değildir.
Qwerty

evet, bunu veri
aktarmanın

1

Normal React Class tabanlı bileşenler için, bu URL'deki forceUpdateapi için React Docs'a bakın . Dokümanlar şunları belirtir:

Normalde forceUpdate () 'in tüm kullanımlarından kaçınmaya çalışmalı ve render ()' da sadece this.props ve this.state 'i okumalısınız.

Bununla birlikte, belgelerde şu hususlardan da bahsedilmektedir:

Render () yönteminiz başka verilere bağlıysa, forceUpdate () 'i çağırarak React'e bileşenin yeniden oluşturulması gerektiğini söyleyebilirsiniz.

Bu nedenle, kullanım durumları forceUpdatenadir olsa da ve hiç kullanmadım, ancak diğer geliştiriciler tarafından üzerinde çalıştığım bazı eski kurumsal projelerde kullanıldığını gördüm.

Bu nedenle, İşlevsel Bileşenler için eşdeğer işlevsellik için, bu URL'deki HOOKS için React Belgelerine bakın . Yukarıdaki URL'ye göre, forceUpdateİşlevsel Bileşenler için bir işlevsellik sağlamak için "useReducer" kancası kullanılabilir .

that does not use state or propsAşağıda, bu URL'de CodeSandbox'ta da bulunan bir çalışma kodu örneği sağlanmıştır.

import 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);

NOT: useState kancasını kullanan alternatif bir yaklaşım (useReducer yerine) bu URL'de de mevcuttur .


1

react-tidysadece bunu yapmak için özel bir kancası var useRefresh:

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>
  )
}

Bu kanca hakkında daha fazla bilgi edinin

Feragatname Bu kütüphanenin yazarıyım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.