A preface

Today we will talk about the current status and future of asynchronous components in React. Asynchronous components are likely to present a smooth technical solution from data interaction to UI in the future. Therefore, it is necessary to understand asynchronous components to understand React.

As usual, shall we start today’s thought with a question? (Self-test mastery)

  • 1 What is the React asynchronous component and what problems does it solve?
  • 2 componentDidCatch how to catch render phase errors and make up for them.
  • 3 How does react. lazy implement dynamic loading?
  • 4 react. lazy is inside Supsonse.
  • 5 What is Supsonse principle?

(Source version V16.13.1)

Second introduction: Asynchronous components

1 What are asynchronous components

Let’s think about the current React application that uses Ajax or FETCH to interact with data. Basically, this is what happens when you interact with the class component componentDidMount and the function component Effect, and then render the UI view. Can we have the component render wait for the asynchronous data request to complete and then render the data?

At first glance, the above situation is hard to believe, but what if you could make the render stop and wait until the data is requested? ‘Well, that’s what’ Susponse ‘could not do’ By Basil Basil ‘, ‘React 16.6’ has the component ‘wait’ for an asynchronous action until the asynchronous action is completed to render.

Traditional mode: Render component -> Request data -> Render component again. Asynchronous mode: Request data -> Render components.

2 Start Suspense mode

A traditional model of data interaction would look like this.

function Index(){
    const [ userInfo , setUserInfo ] = React.useState(0)
    React.useEffect(() = >{
       /* Request data interaction */
       getUserInfo().then(res= >{
           setUserInfo(res)
       })
    },[])
    return <div>
        <h1>{userInfo.name}</h1>;
    </div>
}
export default function Home(){
    return <div>
        <Index />
    </div>
}
Copy the code
  • Process: page initialization mount,useEffectIt’s requesting data. YesuseStateChange the data and update the component rendering data twice.

So if you do ‘Susponse’ asynchronously you could say:

function FutureAsyncComponent (){
    const userInfo = getUserInfo()
    return <div>
        <h1>{userInfo.name}</h1>; 
    </div>
}

/* Future asynchronous mode */
export default function Home(){
   return <div>
      <React.Suspense  fallback={ <div  > loading... </div> } >
          <FutureAsyncComponent/>
      </React.Suspense>
   </div>
}
Copy the code

In Suspense, fallbacks are shown when data isn’t fully loaded to make up for transitions in Suspense. Although this mode isn’t officially available in Suspense, React will support it in the future.

Three sources: from componentDidCatch to Suspense

How does Suspense make the impossible possible? React introduced a new lifecycle function componentDidCatch when v16 was released. If a component defines componentDidCatch, then the componentDidCatch function is called when all the children of that component throw exceptions during rendering.

ComponentDidCatch use

ComponentDidCatch catches exceptions by taking two arguments:

  • 1 error — An error thrown.
  • 2 INFO — An object with a componentStack key that contains stack information about component raising errors.

Let’s simulate a child component rendering failure:

/* Normal component, can render */
function Children(){
  return <div> hello ,let us learn React </div>
}
 /* Non-react components will not render properly */
function Children1(){
  return 
}
export default class Index extends React.Component{
  componentDidCatch(error,info){
      console.log(error,info)
  }
  render(){
    return <div>
      <Children />
      <Children1/>
    </div>}}Copy the code

Render a non-React Children1 as if it were the normal React Children1 component. The render error is captured by componentDidCatch.

As mentioned above, if there is an error in rendering sub-components, the whole component will fail to be rendered and the normal component Children will also be implicated. At this time, we need to take some remedial measures in componentDidCatch. For example, we find that componentDidCatch fails. You can add a state control to the render for Children1 and terminate the render for Children1 if it fails.

function ErroMessage(){
  return <div>Render error ~</div>
}

export default class Index extends React.Component{
  state={ errorRender:false }
  componentDidCatch(error,info){
      /* Remedies */
      this.setState({
        errorRender:true})}render(){
    return <div>
      <Children />
      { this.state.errorRender ? <ErroMessage/> : <Children1/>  }
    </div>}}Copy the code

If there is an error, re-render with setState and remove the failed component so that the component can be rendered properly, again without affecting the Children mount. ComponentDidCatch on the one hand catches errors that occur during the render phase, and on the other hand performs side effects within the life cycle to recover from render exceptions.

ComponentDidCatch principle

Try {} Catch (error){} componentDidCatch (){}

try {
  // Try rendering the child component
} catch (error) {
  // componentDidCatch is called.
}
Copy the code

Can componentDidCatch ideas be migrated to Suspense

Then return to the asynchronous components of us, if let asynchronous code in synchronous execution, is certainly not normal rendering, we will request data first, wait until the data back, then use the returned data rendering, so the emphasis is on the word, how to make the synchronization of rendering to stop it, asynchronous data request to wait? Is throwing an exception ok? Exceptions can stop code execution and, of course, render execution.

Suspense is where a render is aborted by throwing an exception. Suspense needs a createFetcher function to encapsulate asynchronous operations. When trying to read from the result of createFetcher, there are two possibilities: one is to return the result when the data is already in place; Another possibility is that the asynchronous operation hasn’t finished yet and the data isn’t ready, at which point createFetcher will throw an “exception.”

Is this “exception” a normal code error? Suspense is a Promise object that encapsulates the request data. It contains the real data request methods. Suspense can throw exceptions, so you can get them with a try{}catch{} for componentDidCatch.

So what do I do after I get this exception? We know that this exception is a Promise, so of course the next step is to implement the Promise, after the successful state, fetch the data, and then render the component again. At this point, the render has read the normal data, so it can render normally. Next let’s simulate createFetcher and Suspense

Let’s simulate a simple createFetcher

/ * * * *@param {*} Fn The function for which we request data interaction, returns a Promise for the data request */
function createFetcher(fn){
    const fetcher = {
        status:'pedding'.result:null.p:null
    }
    return function (){
      const getDataPromise = fn()
      fetcher.p = getDataPromise
      getDataPromise.then(result= >{ /* Succeeded in obtaining data */
         fetcher.result = result 
         fetcher.status = 'resolve'
      })
  
      if(fetcher.status === 'pedding') {/* The first time to interrupt rendering, the second time */
         throw fetcher
      }
      /* Execute */ for the second time
      if(fetcher.status)
      return fetcher.result
    }
}
Copy the code
  • Returns a function executed during the render phase, the first time the component renders, due tostatus = peddingSo throw an exceptionfetcherSusponseRender aborted.
  • SusponseIn the internalcomponentDidCatchProcess the fetcher and executegetDataPromise.thenAt this timestatusIs alreadyresolveState, the data can also return normally.
  • The followingSusponseRender the component again, at which point the data will be retrieved normally.

Let’s model a simple Suspense

export class Suspense extends React.Component{
   state={ isRender: true  }
   componentDidCatch(e){
     /* In asynchronous request, render fallback */
     this.setState({ isRender:false })
     const { p } = e
     Promise.resolve(p).then(() = >{
       /* Render the real component after the data request */
       this.setState({ isRender:true})})}render(){
     const { isRender } = this.state
     const { children , fallback } = this.props
     return isRender ? children : fallback
   }
}
Copy the code
  • Catch asynchronous requests with componentDidCatch, render fallbacks if there is an asynchronous request, wait until the asynchronous request is executed, render the real component, and thus complete the asynchronous process. But to give you an idea of the flow, which is just a simulation of asynchrony, the actual flow is much more complicated than that.

Flow chart:

Suspense to React. Lazy

A brief introduction the React. Lazy

The asynchronous component revolution in Suspense has yet to come to fruition, with versions of Suspense not officially available, but react. lazy is the best practice for the current version of Suspense. In arreste.lazy Suspense we all know that lazy loading can be implemented in arreste.lazy Suspense, which makes it easier to split up code and not load a lot of files during initialization, reducing the first screen time.

React.lazy is basically used

const LazyComponent = React.lazy(() = >import('./text'))
Copy the code

React.lazy accepts a function that calls import() dynamically. It must return a Promise that needs to resolve a default export React component.

Let’s take a look at the basics:

const LazyComponent = React.lazy(() = > import('./test.js'))

export default function Index(){
   return <Suspense fallback={<div>loading...</div>} >
       <LazyComponent />
   </Suspense>
}
Copy the code

We mock the import() effect with Promise, changing the LazyComponent from above to something like this:

function Test(){
  return <div className="demo"  >React Advanced Practice Guide is coming soon</div>
}
const LazyComponent =  React.lazy(() = > new Promise((resolve) = >{
  setTimeout(() = >{
      resolve({
          default: () = > <Test />})},2000)}))Copy the code

Effect:

React.lazy

How does React. Lazy work with ‘Susponse’ to get the effect of dynamic loading? In fact,lazy makes a createFetcher, which gets the rendered data, and lazy makes a createFetcher asynchronous request to the component. Lazy internally simulates a promiseA specification scenario. It is understandable that React.lazy simulates a request for data with Promise, but the result of the request is not data, but a dynamic component.

Now let’s see how lazy is handled, okay

react/src/ReactLazy.js

function lazy(ctor){
    return {
         $$typeof: REACT_LAZY_TYPE,
         _payload: {_status: -1.// Initialization state
            _result: ctor,
         },
         _init:function(payload){
             if(payload._status===-1) {/* The first execution will go here */
                const ctor = payload._result;
                const thenable = ctor();
                payload._status = Pending;
                payload._result = thenable;
                thenable.then((moduleObject) = >{
                    const defaultExport = moduleObject.default;
                    resolved._status = Resolved; // 1 Succeeded
                    resolved._result = defaultExport;/* defaultExport is the component itself that we dynamically load */})}if(payload._status === Resolved){ // Success status
                return payload._result;
            }
            else {  Suspense // The first time a Promise exception is thrown to Suspense
                throwpayload._result; }}}}Copy the code
  • React.lazyThe components of the package are markedREACT_LAZY_TYPEType element, which becomes during the reconciliation phaseLazyComponentThe type offiber.ReactrightLazyComponentThere will be separate processing logic, and the first render will be executed first_initMethods. At this point the_initThe method can be interpreted ascreateFetcher.

Let’s look at the execution of the init function in lazy:

react-reconciler/src/ReactFiberBeginWork.js

function mountLazyComponent(){
    const init = lazyComponent._init;
    let Component = init(payload);
}
Copy the code
  • If the mountLazyComponent initializes, the _init method will execute the first function of lazy, which returns a Promise, and bind promise. then the callback succeeds, which returns our component defaultExport. When the second if is Resolved, it will do the else, raise the Promise, raise the exception and the render will stop.

  • ‘Susponse’ processes the promise internally, and then render the component again, and the next render will render the component directly. Achieve the purpose of dynamic loading.

The flow chart

Suspense for the future

You are not currently using Relay, so you cannot try out Suspense in your application. Relay is the only library that has implemented Suspense so far that we have tested in production and are confident it will work.

Suspense isn’t available yet, but if you want to use It, try using Relay integrated with Suspense in your production environment. Relay guide!

What does Suspense solve?

  • SuspenseLet the data capture library withReactTightly integrated. If a data request library implements aSuspenseThe support, then, inReactThe use ofSuspenseWill be the natural thing.
  • SuspenseThe ability to freely display the loading effect in the request. Allows you to have more active control over view loading.
  • SuspenseCan make the request data to render more fluid and flexible, we do not have incomponentDidMountRequest the data, trigger render again, and you’re doneSuspenseSolve it in one go.

Suspense challenged?

Suspense can be used as a mainstream asynchronous request data rendering scheme in the future. The author thinks Suspense is full of expectation in the future, so the challenges of Suspense are as follows:

  • ‘1 Susponse in concurrent mode will bring a better user experience and the react team will be able to make Suspense more flexible in the future with a clearer set of createFetcher manuals, Suspense is the key to standing out in Suspense in the future concurrent mode.

  • Whether Suspense can be widely used depends more on the ecological development of Suspense, and there is a stable data request library which fits in perfectly with Suspense.

  • 3. Developers recognize the value of Suspense. If Suspense becomes more powerful in the future, more developers will want to pay for good Suspense by encapsulating their own data request methods.

Six summarize

This article talks about the origin of React Susponse, the implementation principle, the current state and the outlook for the future, what do you think about React in the past and in this life? Feel free to post your thoughts in the comments section, or point out my mistakes.

I wrote an in-depth and systematic book on React

In order to make people systematically learn React and improve React, the author recently wrote a small volume called “React Advanced Practice Guide”. This volume discusses the use guide and principle introduction of React in detail from five aspects: basic advanced, optimization advanced, Principle advanced, ecological advanced, and practice Advanced.

  • In the basic advanced part, the modules of React, such as state, props, ref, context, etc., will be rerecognized, and their basic use and advanced gameplay will be explained in detail.

  • In advance optimizations, we’ll cover performance tuning and details to make React writing more elegant.

  • In the advanced chapter of React principles, several core modules and principles of React will be elaborated to solve the React principle problems encountered in the interview at one time.

  • In the advanced chapter of Ecology, we will review the use of React key ecology and analyze the internal operation mechanism from the perspective of principle.

  • In the practice advanced chapter, the previous several modules will be connected to strengthen the practice.

As for why the booklet is called the Advanced Practice Guide, it contains many small demos of the practice while explaining the advanced play. There are also questions and answers to help readers stand out from the crowd.

At present, 20 chapters have been completed and the volume will be released soon. If you want to advance React, please check it out and follow the latest developments in the volume.

Finally, send rose, hand left lingering fragrance, feel a harvest of friends can give the author praise, pay attention to a wave, update the front end of the super core article.

reference

React 英 文 版