React - Bir üst bileşenin tüm alt bileşenlerinin kullanıcı tarafından görülebildiği nasıl algılanır?


11

TL; DR

Bir üst bileşen, altındaki her alt bileşenin oluşturulmasının ne zaman tamamlandığını ve DOM'un en güncel sürümüyle kullanıcıya görünür olduğunu nasıl bilebilir?

Diyelim ki component Abir alt Gridbileşeni olan bir bileşenim var3x3 torun bileşenlerinden bileşeni var. Bu torun bileşenlerinin her biri, dinlendirici bir API uç noktasından veri alır ve veriler kullanılabilir hale geldiğinde kendisini oluşturur.

Tüm alanı Component Abirloader yer tutucu , sadece ızgaradaki bileşenlerin sonuncusu verileri başarılı bir şekilde aldığında ve zaten DOM'da ve görüntülenebileceği şekilde oluşturulduğunda açılmak istiyorum.

Kullanıcı deneyimi, "yükleyiciden" tamamen doldurulmuş bir ızgaraya titremeden süper yumuşak bir geçiş olmalıdır.

Benim sorunum yükleyicinin altındaki bileşenleri tam olarak ne zaman açığa çıkarmaktır.

Bunu kesin bir doğrulukla yapmak için güvenebileceğim herhangi bir mekanizma var mı? Yükleyici için bir zaman sınırını zor kodlamıyorum. Anladığım gibi ComponentDidMount, her çocuk için güvenmek de güvenilmez değildir çünkü aslında çağrı sırasında bileşenin kullanıcı tarafından tamamen görülebileceğini garanti etmez.

Soruyu daha da damıtmak için:

Bir tür veri işleyen bir bileşenim var. Başlatıldıktan sonra buna sahip değildir, bu yüzden onun componentDidMountiçin bir API uç noktasına vurur. Verileri aldıktan sonra, durumu yansıtmak için durumunu değiştirir. Bu anlaşılır bir şekilde, söz konusu bileşenin son durumunun yeniden oluşturulmasına neden olur. Benim sorum şudur: Bu yeniden oluşturma işleminin ne zaman gerçekleştiğini ve DOM ile karşı karşıya olan Kullanıcıya yansıtıldığını nasıl bilebilirim ? O zamandaki nokta! = Bileşenin durumunun veri içerecek şekilde değiştiği nokta.


Devleti nasıl yönetiyorsunuz? Redux kullanmak gibi mi? ya da sadece bileşenlere mi dayanıyor?
TechTurtle

Bileşenlerin içindedir, ancak haricileştirilebilir. Redux'u başka şeyler için kullanıyorum.
JasonGenX

ve tablodaki son bileşenin veri getirmeyi tamamladığını nasıl anlarsınız?
TechTurtle

Yapmıyorum. Izgara bileşenleri birbirinin farkında değildir. Veri almak için ComponentDidMountkullandığım her alt bileşende axios. veri geldiğinde, bu bileşenin durumunu değiştirerek verilerin oluşturulmasına neden olurum. Teoride, 8 alt bileşen 3 saniye içinde alınabilir ve sonuncusu 15 saniye sürer ...
JasonGenX

1
Bunu yapabilirim, ancak kullanıcıların bir bileşenin "boş" durumdan "dolu" duruma nasıl geçtiğini asla görmeyeceği şekilde yükleyici katmanını ne zaman açacağımı nasıl bilebilirim? Bu, veri getirme ile ilgili bir soru değildir. Bu sadece bir alt bileşene sahip olmama rağmen ... renderleme ile ilgili. ComponentDidMount yeterli değil. Yükleyici katmanını açığa çıkarabilmem için veri sonrası getirme işleminin ne zaman tamamlandığını ve DOM'un tamamen güncellendiğini bilmem gerekiyor.
JasonGenX

Yanıtlar:


5

React'te, bir bileşenin DOM'sini oluşturduktan sonra çağrılan iki yaşam döngüsü kancası vardır:

Kullanım durumunuz için, ana bileşen P , N alt bileşeninin her biri bir koşul X'i karşıladığında ilgilenir . X bir dizi olarak tanımlanabilir:

  • zaman uyumsuz işlem tamamlandı
  • bileşen oluşturuldu

Bileşenin durumunu birleştirip componentDidUpdate kancayı dizinin ne zaman tamamlandığını ve bileşeninizin X koşulunu karşıladığını öğrenebilirsiniz.

Bir durum değişkeni ayarlayarak eşzamansız işleminizin ne zaman tamamlandığını takip edebilirsiniz. Örneğin:

this.setState({isFetched: true})

Durumu ayarladıktan sonra, React bileşenlerinizin componentDidUpdateişlevini çağıracaktır . Bu işlev içindeki geçerli ve önceki durum nesnelerini karşılaştırarak, zaman uyumsuz işleminizin tamamlandığını ve yeni bileşeninizin durumunun oluşturulduğunu ana bileşene işaret edebilirsiniz:

componentDidUpdate(_prevProps, prevState) {
  if (this.state.isFetched === true && this.state.isFetched !== prevState.isFetched) {
    this.props.componentHasMeaningfullyUpdated()
  }
}

P bileşeninizde, kaç çocuğun anlamlı olarak güncellendiğini takip etmek için bir sayaç kullanabilirsiniz:

function onComponentHasMeaningfullyUpdated() {
  this.setState({counter: this.state.counter + 1})
}

Son olarak, N'nin uzunluğunu bilerek, tüm anlamlı güncellemelerin ne zaman gerçekleştiğini öğrenebilir ve P oluşturma yönteminizde buna göre hareket edebilirsiniz :

const childRenderingFinished = this.state.counter >= N

1

Bunu, bileşenlerinize ne zaman oluşturulacağını söylemek için bir genel durum değişkenine güvenecek şekilde ayarlardım. Redux, birçok bileşenin birbiriyle konuştuğu bu senaryo için daha iyidir ve bir yorumda bazen kullandığınızdan bahsettiniz. Bu yüzden Redux kullanarak bir cevap çizeceğim.

API çağrılarınızı üst kapsayıcıya taşımanız gerekir Component A. Torunlarınızın yalnızca API çağrıları tamamlandıktan sonra görüntülenmesini istiyorsanız, bu API çağrılarını torunlarda tutamazsınız. Henüz mevcut olmayan bir bileşenden bir API çağrısı nasıl yapılabilir?

Tüm API çağrıları yapıldıktan sonra, bir grup veri nesnesi içeren bir genel durum değişkenini güncellemek için eylemleri kullanabilirsiniz. Verilerin her alınışında (veya bir hata yakalandığında), veri nesnenizin tamamen doldurulup doldurulmadığını kontrol etmek için bir eylem gönderebilirsiniz. Tamamen dolduğunda, bir loadingdeğişkeni bileşeninize güncelleyebilir falseve koşullu olarak Gridbileşeninizi oluşturabilirsiniz .

Yani mesela:

// Component A

import { acceptData, catchError } from '../actions'

class ComponentA extends React.Component{

  componentDidMount () {

    fetch('yoururl.com/data')
      .then( response => response.json() )
      // send your data to the global state data array
      .then( data => this.props.acceptData(data, grandChildNumber) )
      .catch( error => this.props.catchError(error, grandChildNumber) )

    // make all your fetch calls here

  }

  // Conditionally render your Loading or Grid based on the global state variable 'loading'
  render() {
    return (
      { this.props.loading && <Loading /> }
      { !this.props.loading && <Grid /> }
    )
  }

}


const mapStateToProps = state => ({ loading: state.loading })

const mapDispatchToProps = dispatch => ({ 
  acceptData: data => dispatch( acceptData( data, number ) )
  catchError: error=> dispatch( catchError( error, number) )
})
// Grid - not much going on here...

render () {
  return (
    <div className="Grid">
      <GrandChild1 number={1} />
      <GrandChild2 number={2} />
      <GrandChild3 number={3} />
      ...
      // Or render the granchildren from an array with a .map, or something similar
    </div>
  )
}
// Grandchild

// Conditionally render either an error or your data, depending on what came back from fetch
render () {
  return (
    { !this.props.data[this.props.number].error && <Your Content Here /> }
    { this.props.data[this.props.number].error && <Your Error Here /> }
  )
}

const mapStateToProps = state => ({ data: state.data })

Redüktörünüz, işlerin henüz tamamlanmaya hazır olup olmadığını söyleyecek küresel devlet nesnesini koruyacaktır:

// reducers.js

const initialState = {
  data: [{},{},{},{}...], // 9 empty objects
  loading: true
}

const reducers = (state = initialState, action) {
  switch(action.type){

    case RECIEVE_SOME_DATA:
      return {
        ...state,
        data: action.data
      }

     case RECIEVE_ERROR:
       return {
         ...state,
         data: action.data
       }

     case STOP_LOADING:
       return {
         ...state,
         loading: false
       }

  }
}

Eylemlerinizde:


export const acceptData = (data, number) => {
  // First revise your data array to have the new data in the right place
  const updatedData = data
  updatedData[number] = data
  // Now check to see if all your data objects are populated
  // and update your loading state:
  dispatch( checkAllData() )
  return {
    type: RECIEVE_SOME_DATA,
    data: updatedData,
  }
}

// error checking - because you want your stuff to render even if one of your api calls 
// catches an error
export const catchError(error, number) {
  // First revise your data array to have the error in the right place
  const updatedData = data
  updatedData[number].error = error
  // Now check to see if all your data objects are populated
  // and update your loading state:
  dispatch( checkAllData() )
  return {
    type: RECIEVE_ERROR,
    data: updatedData,
  }
}

export const checkAllData() {
  // Check that every data object has something in it
  if ( // fancy footwork to check each object in the data array and see if its empty or not
    store.getState().data.every( dataSet => 
      Object.entries(dataSet).length === 0 && dataSet.constructor === Object ) ) {
        return {
          type: STOP_LOADING
        }
      }
  }

bir kenara

API çağrılarınızın her bir torun içinde canlı olduğu, ancak tüm API çağrıları tamamlanana kadar tüm torun Izgarasının oluşturulmadığı fikriyle gerçekten evliyseniz, tamamen farklı bir çözüm kullanmanız gerekir. Bu durumda, torunlarınızın çağrılarını yapmak için en baştan yapılması gerekir, ancak display: noneyalnızca global durum değişkeni loadingyanlış olarak işaretlendikten sonra değişen bir css sınıfına sahip olmak gerekir . Bu da yapılabilir, ancak Tepki noktasının yanı sıra.


1

Yapabilirsin potansiyel bu kullanarak gerilim tepki çözer.

Uyarı, işlemeyi gerçekleştiren bileşen ağacını geçip askıya almanın iyi bir fikir olmadığıdır (yani: Bileşeniniz bir oluşturma işlemini başlattıysa, bu bileşenin deneyimime göre askıya alınması iyi bir fikir değildir). muhtemelen daha iyi bir fikir bileşeninde istekleri başlaması kılmaktadır hücreleri. Bunun gibi bir şey:

export default function App() {
  const cells = React.useMemo(
    () =>
      ingredients.map((_, index) => {
        // This starts the fetch but *does not wait for it to finish*.
        return <Cell resource={fetchIngredient(index)} />;
      }),
    []
  );

  return (
    <div className="App">
      <Grid>{cells}</Grid>
    </div>
  );
}

Şimdi, Suspense'in Redux ile nasıl eşleştiğinden emin değilim. Suspense uygulamasının bu (deneysel!) Versiyonunun arkasındaki fikir, getirmeyi bir üst bileşenin oluşturma döngüsü sırasında hemen başlatmanız ve çocuklara getirmeyi temsil eden bir nesneyi geçirmenizdir. Bu, bir çeşit Bariyer nesnesine sahip olmanızı (diğer yaklaşımlarda ihtiyacınız olacak) engeller.

Ben zamana kadar beklemek sanmıyorum diyecekler herşey ekran için getirilen sahiptir şey daha sonra UI yavaş bağlantı gibi yavaş gibi olacaktır çünkü doğru yaklaşımdır veya hiç çalışmayabilir!

İşte eksik kodun geri kalanı:

const ingredients = [
  "Potato",
  "Cabbage",
  "Beef",
  "Bok Choi",
  "Prawns",
  "Red Onion",
  "Apple",
  "Raisin",
  "Spinach"
];

function randomTimeout(ms) {
  return Math.ceil(Math.random(1) * ms);
}

function fetchIngredient(id) {
  const task = new Promise(resolve => {
    setTimeout(() => resolve(ingredients[id]), randomTimeout(5000));
  });

  return new Resource(task);
}

// This is a stripped down version of the Resource class displayed in the React Suspense docs. It doesn't handle errors (and probably should).
// Calling read() will throw a Promise and, after the first event loop tick at the earliest, will return the value. This is a synchronous-ish API,
// Making it easy to use in React's render loop (which will not let you return anything other than a React element).
class Resource {
  constructor(promise) {
    this.task = promise.then(value => {
      this.value = value;
      this.status = "success";
    });
  }

  read() {
    switch (this.status) {
      case "success":
        return this.value;

      default:
        throw this.task;
    }
  }
}

function Cell({ resource }) {
  const data = resource.read();
  return <td>{data}</td>;
}

function Grid({ children }) {
  return (
    // This suspense boundary will cause a Loading sign to be displayed if any of the children suspend (throw a Promise).
    // Because we only have the one suspense boundary covering all children (and thus Cells), the fallback will be rendered
    // as long as at least one request is in progress.
    // Thanks to this approach, the Grid component need not be aware of how many Cells there are.
    <React.Suspense fallback={<h1>Loading..</h1>}>
      <table>{children}</table>
    </React.Suspense>
  );
}

Ve bir sanal alan: https://codesandbox.io/s/falling-dust-b8e7s


1
sadece yorumları okuyun .. Tüm bileşenlerin DOM'da oluşturulmasını beklemenin önemsiz olduğuna inanmıyorum - ve bu iyi bir nedendir. Gerçekten çok acayip görünüyor . Ne elde etmeye çalışıyorsunuz?
Dan Pantry

1

Her şeyden önce, yaşam döngüsünde asenkron / bekleme yöntemi olabilir.

Bilmemiz gerekenler componentDidMountve componentWillMount,

componentWillMount önce ana, sonra alt olarak adlandırılır.
componentDidMount tam tersini yapar.

Benim düşünceme göre, bunları normal yaşam döngüsünü kullanarak uygulamak yeterli olacaktır.

  • alt bileşen
async componentDidMount() {
  await this.props.yourRequest();
  // Check if satisfied, if true, add flag to redux store or something,
  // or simply check the res stored in redux in parent leading no more method needed here
  await this.checkIfResGood();
}
  • ana bileşen
// Initial state `loading: true`
componentDidUpdate() {
  this.checkIfResHaveBad(); // If all good, `loading: false`
}
...
{this.state.loading ? <CircularProgress /> : <YourComponent/>}

Material-UI CircularProgress

Çocuk yeniden oluşturma, ebeveynin aynı şeyi yapmasına neden olduğundan, onu yakalayın didUpdate.
Ayrıca, oluşturma işleminden sonra çağrıldığından, kontrol işlevini isteğiniz olarak ayarlarsanız sayfalar değiştirilmemelidir.

Bu uygulamayı, yanıt alması için 30'lara neden olan bir API'ye sahip olan ürünümüzde kullanıyoruz, hepsi gördüğüm kadar iyi çalıştı.

resim açıklamasını buraya girin


0

Eğer bir API çağrısı yapıyoruz gibi componentDidMountAPI çağrısı sen verilere sahip olur çözecektir zaman, componentDidUpdatebu yaşam döngüsü geçip gidecek şekilde prevPropsve prevState. Böylece aşağıdaki gibi bir şey yapabilirsiniz:

class Parent extends Component {
  getChildUpdateStatus = (data) => {
 // data is any data you want to send from child
}
render () {
 <Child sendChildUpdateStatus={getChildUpdateStatus}/>
}
}

class Child extends Component {
componentDidUpdate = (prevProps, prevState) => {
   //compare prevProps from this.props or prevState from current state as per your requirement
  this.props.sendChildUpdateStatus();
}

render () { 
   return <h2>{.. child rendering}</h2>
  }
}

Bileşen yeniden oluşturulmadan önce bunu bilmek istiyorsanız, getSnapshotBeforeUpdate .

https://reactjs.org/docs/react-component.html#getsnapshotbeforeupdate .


0

Uygulayabileceğiniz birden fazla yaklaşım vardır:

  1. En kolay yol, alt bileşene geri arama iletmektir. Bu alt bileşenler daha sonra, tek tek verilerini veya koymak istediğiniz diğer iş mantıklarını getirdikten sonra oluşturuldukları anda bu geri aramayı çağırabilir. İşte bunun için bir sanal alan: https://codesandbox.io/s/unruffled-shockley-yf3f3
  2. Bağımlı / alt bileşenlerden, bileşeninizin dinleyeceği genel bir uygulama durumuna getirilen veriler oluşturulduğunda bittiğinde güncelleme yapmasını isteyin
  3. Bu üst bileşen için yerelleştirilmiş durum oluşturmak üzere React.useContext'i de kullanabilirsiniz. Alt bileşenler, getirilen verileri oluştururken bu bağlamı güncelleyebilir. Yine, ana bileşen bu bağlamı dinliyor olacak ve buna göre hareket edebilecek
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.