React 18 introduced a new API, startTransition, and two new hooks, useTransition and useDeferredValue, which essentially rely on the concept transition.

Through this chapter, you will learn the following:

  • TransitionWhat problems have been solved?
  • startTransitionThe usage and principle of.
  • useTranstionThe usage and principle of.
  • useDeferredValueThe usage and principle of.

Transition refers to the transition of data from nothing to something in an update. Describe startTransition with a sentence from ReactWg.

StartTransition keeps the page responsive when large screen views are updated, and the API marks React updates as a special update type, Transitions, for which React maintains visual feedback and normal browser responses.

From the above description of startTransition, it’s hard to understand what the new API solves. Never mind, let me step through what this API does and how it might be used.

The second transition mission

The birth of Transition

Why Transition? React 18 describes startTransition many times. The “big screen” does not simply refer to size, but rather a scene with a large amount of data and DOM element nodes. For example, in the case of large screen of data visualization, in this scenario, changes brought by an update may be huge, so frequent updates, frequent invocation of JS transactions, and a large amount of rendering work have to be performed by the browser, so the user feels stuck.

Transition is essentially used for less-urgent updates. Before React 18, all update tasks were considered urgent, while React 18 introduced the Concurrent Mode, in which rendering can be interrupted, low-priority tasks, You can let higher-priority tasks update renderings first. React 18 favors a good user experience. From Concurrent Mode to Susponse to startTransition is definitely all about a better user experience.

StartTransition relies on concurrent Mode to render concurrent Mode. To use startTransition in React 18, you need to enable concurrent mode first, which means creating Root using createRoot. Let’s first look at creating Root differences between the two modes.

Legacy Mode

import ReactDOM from 'react-dom'
/* reactdom.render */
ReactDOM.render(
    <App />.document.getElementById('app'))Copy the code

V18 Concurrent Mode Indicates the concurrent Mode

import ReactDOM from 'react-dom'
/* createRoot */ from createRoot
const root =  ReactDOM.createRoot(document.getElementById('app'))
/* Call root's render method */
root.render(<App/>)

Copy the code

StartTransition is used in different scenarios. React 18 assigns different priorities to update tasks. In the React world, there were no priorities. When there were too many scenes, there were priorities.

If all the tasks are the same in an update, then there is no priority for the tasks, and the tasks can be processed in batches, which is not the case. Here’s a common scenario: you have an input form. And there is a large list of data, through the form input content, the list of data search, filtering. In this case, there are multiple concurrent update tasks. Respectively,

  • The first is that the input form is controlled because it captures the state in real time, so updating the content of the input triggers the update task.
  • Input changes, filtering lists, and re-rendering lists are also tasks.

The first type of update is expected to show immediate visual changes when input. If the input content is delayed during input, it will give users a very poor visual experience. The second type of update is to filter the data in the list and render the list based on the content of the data. This type of update is not as high priority as the previous one. So if the user wants the state of the input field to change more than anything else during the input search, it would be normal to bind the onChange event to the input to trigger both types of updates.

const handleChange=(e) = >{
   /* Change the search criteria */ 
   setInputValue(e.target.value)
   /* Change the state of the filtered list */
   setSearchQuery(e.target.value)
}
Copy the code

In this way, the update from setInputValue and setSearchQuery is an update of the same priority. As mentioned above, input field state changes take precedence over list updates. “And that’s where our hero comes in. Use startTransition to distinguish between the two updates.

const handleChange=() = >{
    /* High priority task -- change search criteria */
    setInputValue(e.target.value)
    /* Low priority task - changes the state of the search filtered list */
    startTransition(() = >{
        setSearchQuery(e.target.value)
    })
}
Copy the code
  • As shown above, the less urgent update task setSearchQuery is isolated by startTransition. How does this work in real life? So let’s test that out.

2 Simulation Scenario

Let’s simulate the above scenario. The process looks like this:

  • There is a search box and a list of 10,000 items, each of which has the same copy.
  • To change the content of the input in real time (the first update), then highlight the same search value in the list (the second update).
  • Control regular mode | with one buttontransitionMode.
/* Analog data */
const mockDataArray = new Array(10000).fill(1)
/* High volume display content */
function ShowText({ query }){
   const text = 'asdfghjk'
   let children
   if(text.indexOf(query) > 0) {/* Find the matching keyword */
       const arr = text.split(query)
       children = <div>{arr[0]}<span style={{ color:'pink'}} >{query}</span>{arr[1]} </div>
   }else{
      children = <div>{text}</div>
   }
   return <div>{children}</div>
}
/* List data */
function List ({ query }){
    console.log(Render 'List')
    return <div>
        {
           mockDataArray.map((item,index)=><div key={index} >
              <ShowText query={query} />
           </div>)}</div>
}
/* The memo is optimized */
const NewList = memo(List)
Copy the code
  • ListComponent rendering ten thousandShowTextComponents. Dynamic highlighting is implemented in the ShowText component through the incoming Query.
  • Because every changequeryWill have 10000 re-render updates, and will also show the highlighted contents of query, so yesConcurrent renderingThe scene.

Next comes App component writing.

export default function App(){
    const [ value ,setInputValue ] = React.useState(' ')
    const [ isTransition , setTransion ] = React.useState(false)
    const [ query ,setSearchQuery  ] = React.useState(' ')
    const handleChange = (e) = > {
        /* High priority task -- change search criteria */
        setInputValue(e.target.value)
        if(isTransition){ /* Transition mode */
            React.startTransition(() = >{
                /* Low priority task - changes the state of the search filtered list */
                setSearchQuery(e.target.value)
            })
        }else{ /* Unoptimized, traditional mode */
            setSearchQuery(e.target.value)
        }
    }
    return <div>
        <button onClick={()= >setTransion(! isTransition)} >{isTransition ? 'transition' : 'normal'}</button>
        <input onChange={handleChange}
            placeholder="Enter your search"
            value={value}
        />
       <NewList  query={query} />
    </div>
}

Copy the code

So let’s see what our App does.

  • The onchange event is first handled by the handleChange event.
  • buttonButton to toggletransition(Set priority) andnormal(Normal mode). And then there’s the magic moment.

Effects in normal mode:

  • It can be clearly seen that in the normal mode, the input content and the presentation of the content become abnormal and slow, giving a person a very poor user experience.

Effects in Transtion mode:

  • After processing a large number of concurrent tasks through startTransition, you can clearly see that the input will render normally and the update list task will be delayed, but the user experience will be greatly improved.

Overall effect:

  • Feel some of the magic of startTransition.

Conclusion: From the above, it can be seen intuitively that startTransition plays a pivotal role in handling transition tasks and optimizing user experience.

3 Why not setTimeout

The above problem can wrap the setSearchQuery update inside setTimeout, as shown below.

const handleChange=() = >{
    /* High priority task -- change search criteria */
    setInputValue(e.target.value)
    /* Wrap setSearchQuery through the delayer */
    setTimeout(() = >{
        setSearchQuery(e.target.value)
    },0)}Copy the code
  • Here through setTimeout, put the update inside setTimeout, so we all know that setTimeout is a delay timer task, it will not block the normal drawing of the browser, the browser will use setTimeout in the next idle time. So what’s the effect? Let’s take a look:

  • As can be seen above, setTimeout can indeed make the input state better, but since setTimeout itself is also a macro task, and every time onchange is triggered is also a macro task, setTimeout will also affect the interactive experience of the page.

In summary, the advantages and differences of startTransition compared with setTimeout are as follows:

  • On the one hand, there is an important difference between the startTransition processing logic and setTimeout. SetTimeout executes asynchronously, while startTransition’s callback executes synchronously. Any updates to startTransition will be marked with the transition flag. React will use this flag to determine whether the update is complete. So Transition can be understood as updating earlier than setTimeout. But at the same time, we need to ensure the normal response of the UI. On a high-performance device, the delay of the transition two updates will be small, but on a slow device, the delay will be large, but the UI response will not be affected.

  • On the other hand, it can be seen from the above example that setTimeout will still cause the page to stall in concurrent rendering scenarios. Because setTimeout tasks are performed after a timeout, their interaction with the user is also a macro task, so it still prevents the page from interacting. In conCurrent mode, startTransition interrupts rendering, so it doesn’t stall the page. React allows these tasks to be executed at idle time in the browser. StartTransition takes care of updating input values first, and rendering lists later.

4 Why not throttling and anti-shaking

So let’s think about another question, why not throttling and anti-shaking. First throttling and anti – shake can solve the problem of stuck? And the answer is yes, until we have an API like Transition, we’re going to have to deal with it with shock proofing and throttling, and then we’re going to deal with it with shock proofing.

const SetSearchQueryDebounce = useMemo(() = > debounce((value) = > setSearchQuery(value),1000)  ,[])
const handleChange = (e) = > {
    setInputValue(e.target.value)
    /* setSearchQuery () ¶ * /
    SetSearchQueryDebounce(e.target.value)
}
Copy the code
  • For example, the setSearchQuery anti-shake process. And then let’s see what happens.

From the above, it can be intuitively felt that the input input is basically not affected after the shaking treatment. But one problem is that the delay for list view changes is getting longer. So the essential difference between transition and throttling is this:

  • On the one hand, throttling is essentially setTimeout, but controls the frequency of execution, so it can be found by printing the content, the principle is to reduce the number of render times. Transitions, on the other hand, don’t reduce the number of renders.

  • On the other hand, throttling and shaking prevention require effective control of Delay Time. If the Time is too long, it will give a feeling of rendering lag; if the Time is too short, it will be similar to setTimeout(fn,0), which will also cause the previous problems. StartTransition doesn’t have that much to worry about.

5 The computer performance is affected

The transition process works better on computers that are slow. Let’s look at the Real world example

Pay attention to the slider speed

  • Processing performance is higher and faster on devices. StartTransition is not used.

  • Processing performance is higher and faster on devices. Use startTransition.

  • StartTransition is not used on devices with poor processing performance or low speed.

  • StartTransition is used to process devices with poor performance and slow speed.

3 the transition characteristics

Now that we’ve talked about why we have transition, let’s look at what it does.

What is overtasking?

Status updates are generally classified into two categories:

  • Type 1 urgent update task. For example, some user interactions, buttons, clicks, typing, etc.
  • The second type is the transitional update task. For example, UI transitions from one view to another.

2 What is startTransition

StartTransition = startTransition = startTransition = startTransition

startTransition(scope)
Copy the code
  • Scope is a callback function, in which update tasks are marked as transition update tasks, which will degrade the update priority and interrupt the update in concurrent rendering scenarios.

use

startTransition(() = >{
   /* Update task */
   setSearchQuery(value)
})
Copy the code

3 What is useTranstion

So we talked about startTransition, we talked about transition tasks, and essentially transition tasks have a transition period in which the current task is essentially interrupted, so what do you do during transition, or tell the user when the transition task is pending, When the pending state ends.

To fix this, React provides hooks with isPending status — useTransition. The useTransition execution returns an array. Arrays have two status values:

  • The first one is isPending, the flag for when in a transition state.
  • The second is a method, which can be understood as the startTransition described above. You can turn the update task inside into a transition task.
import { useTransition } from 'react' 

/ * * / use
const  [ isPending , startTransition ] = useTransition ()
Copy the code

So when the task is in the hovering state, isPending is true and can be presented as the UI for the user to wait for. Such as:

{ isPending  &&  < Spinner  / > }
Copy the code

UseTranstion practice

Let’s do a useTranstion exercise and reuse the above demo. Modify the above demo.

export default function App(){
    const [ value ,setInputValue ] = React.useState(' ')
    const [ query ,setSearchQuery  ] = React.useState(' ')
    const [ isPending , startTransition ] = React.useTransition()
    const handleChange = (e) = > {
        setInputValue(e.target.value)
        startTransition(() = >{
            setSearchQuery(e.target.value)
        })
    }
    return  <div>
    {isPending && <span>isTransiton</span>}
    <input onChange={handleChange}
        placeholder="Enter your search"
        value={value}
    />
   <NewList  query={query} />
</div>
}
Copy the code
  • As withuseTransitionisPendingRepresents the transition state. When in the transition state, is displayedisTransitonPrompt.

Here’s the result:

You can see that you can accurately capture the state during the transition.

4 What is useDeferredValue

As we saw in the above scenario, query is essentially value, but updates to Query lag behind updates to value. React 18 provides useDeferredValue to allow state lag derivation. UseDeferredValue works similarly to transtion, in that a new state is created after the urgent task is executed, and that state is called a DeferredValue.

What are the essential differences between useDeferredValue and useTransition?

Similarities:

  • UseDeferredValue is essentially marked as a transition update task, as is the internal implementation of useTransition.

Difference:

  • UseTransition is to change the update task inside startTransition to a transition task, and useDeferredValue is to change the original value to a new value through the transition task. This value is used as the delay state. One is to process a piece of logic and the other is to produce a new state.
  • Another difference with useDeferredValue is that the task is essentially executed inside useEffect, whereas useEffect internal logic is executed asynchronously, so it is somewhat behinduseTransition.useDeferredValue = useEffect + transtion

So going back to the demo, it seems that changing Query to DeferredValue is more realistic, so modify the demo.

export default function App(){
    const [ value ,setInputValue ] = React.useState(' ')
    const query = React.useDeferredValue(value)
    const handleChange = (e) = > {
        setInputValue(e.target.value)
    }
    return  <div>
     <button>useDeferredValue</button>
    <input onChange={handleChange}
        placeholder="Enter your search"
        value={value}
    />
   <NewList  query={query} />
   </div>
}
Copy the code
  • As you can see above, Query is generated by Value through useDeferredValue.

Effect:

Four principles

And then the principle, from startTransition to useTranstion to useDeferredValue is pretty simple in nature,

1 startTransition

Let’s take a look at how the basic startTransition is implemented.

react/src/ReactStartTransition.js -> startTransition

export function startTransition(scope) {
  const prevTransition = ReactCurrentBatchConfig.transition;
  /* By setting the state */
  ReactCurrentBatchConfig.transition = 1;
  try {  
      /* Perform the update */
    scope();
  } finally {
    /* Restore status */ReactCurrentBatchConfig.transition = prevTransition; }}Copy the code
  • StartTransition is very simple, similar to the batch processing logic of batchUpdate in React V17. I’m going to do it by setting a switch, and the switch is going to be transition = 1, and then I’m going to do an update, and every update task in there is going to get the Transtion flag.

  • Transtion type updates are then handled separately in Concurrent mode.

The schematic diagram is shown below.

2 useTranstion

Next, look at the internal implementation of useTranstion.

react-reconciler/src/ReactFiberHooks.new.js -> useTranstion

function mountTransition(){
    const [isPending, setPending] = mountState(false);
    const start = (callback) = >{
        setPending(true);
        const prevTransition = ReactCurrentBatchConfig.transition;
        ReactCurrentBatchConfig.transition = 1;
        try {
            setPending(false);
            callback();
        } finally{ ReactCurrentBatchConfig.transition = prevTransition; }}return [isPending, start];
}
Copy the code

This code is not the source code, I put the source code inside the content of the combination, compression.

  • From the above you can see that useTranstion is essentially useState + startTransition.
  • Change pending state with useState. This command is triggered twice during the mountTransition executionsetPending, in at a timetransition = 1Before, and once after. A normal updatesetPending(true), once will act astransitionInterim mission updatesetPending(false);, so the transition time can be accurately captured.

The schematic diagram is shown below.

3 useDeferredValue

Finally, let’s take a look at the internal implementation of useDeferredValue.

react-reconciler/src/ReactFiberHooks.new.js -> useTranstion

function updateDeferredValue(value){
  const [prevValue, setValue] = updateState(value);
  updateEffect(() = > {
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = 1;
    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}
Copy the code

The useDeferredValue process looks like this.

  • You can see from above that useDeferredValue is essentiallyuseDeferredValue = useState + useEffect + transition
  • UseDeferredValue saves the state through useState by passing in the value value of useDeferredValue.
  • It is then passed in useEffecttransitionPattern to update value. This ensures that DeferredValue lags behind state updates and satisfiestransitionTransitional renewal principle.

The schematic diagram is shown below.

Four summarizes

The knowledge points of this chapter are as follows:

  • TransitionWhat problems have been solved?
  • startTransitionThe usage and principle of.
  • useTranstionThe usage and principle of.
  • useDeferredValueThe usage and principle of.

Those of you who are interested can do the following

Reference documentation

  • New feature: startTransition

  • Real world example: adding startTransition for slow renders