React recently introduced an experimental profiler API. After discussing this API with several teams at Facebook, one common piece of feedback was that the performance information would be more useful if it could be associated with the events that caused the application to render (e.g. button click, XHR response). Tracing these events (or "interactions") would enable more powerful tooling to be built around the timing information, capable of answering questions like "What caused this really slow commit?" or "How long does it typically take for this interaction to update the DOM?".
With version 16.4.3, React added experimental support for this tracing by way of a new NPM package, scheduler. However the public API for this package is not yet finalized and will likely change with upcoming minor releases, so it should be used with caution.
This Gist provides some high-level documentation for anyone looking to experiment with the API.
React has always respected semantic versioning for its public API. However, sometimes we want to share an early preview of something that is likely to change in the future. This is one of those cases. If you care about semantic versioning guarantees, don't use "unstable_" APIs because they are subject to change in any patch release. If you use them, you agree that things will break between releases, and that you use a lockfile to avoid that.
Note that if you're using a version of
react
/react-dom
that's less than 16.6, you should refer to this earlier revision of the documentation instead.
Note that if you're using the
schedule
package v0.3.0-v0.4.0 you should refer to this earlier revision of the documentation instead.
An interaction is a descriptive string (e.g. "login button clicked") and a timestamp. When you declare an interaction, you also provide a callback in which to do some work related to that interaction. Interactions are similar to "zones" in that any work done within the "zone" is attributed to the interaction.
For example, you can trace a React render like so:
import { unstable_Profiler as Profiler } from "react";
import { render } from "react-dom";
import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () =>
render(
<Profiler id="Application" onRender={onRender}>
<Application />
</Profiler>,
domElement
)
);
In the above example, the profiler's onRender
prop will be called after the application renders and mounts. Among other things, it will receive a parameter that specifies the Set
of interactions that were part of that render (i.e. the "initial render" interaction).
The interaction will also appear in the React DevTools profiler plug-in.
The following methods are a subset of the interaction tracing API.
unstable_trace(
name: string,
timestamp: number,
callback: () => T
) => T
Traces a new interaction (by appending to the existing set of interactions). The callback function will be executed and its return value will be returned to the caller. Any code run within that callback will be attributed to that interaction. Calls to unstable_wrap()
will schedule async work within the same zone.
unstable_wrap(callback: Function) => Function
Wrap a function for later execution within the current interaction "zone". When this function is later run, interactions that were active when it was "wrapped" will be reactivated.
The wrapped function returned also defines a cancel()
property which can be used to notify any traceed interactions that the async work has been cancelled.
import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () => render(<Application />));
import { unstable_trace as trace } from "scheduler/tracing";
class MyComponent extends Component {
handleLoginButtonClick = event => {
trace("Login button click", performance.now(), () => {
this.setState({ isLoggingIn: true });
});
};
// render ...
}
import {
unstable_trace as trace,
unstable_wrap as wrap
} from "scheduler/tracing";
trace("Some event", performance.now(), () => {
setTimeout(
wrap(() => {
// Do some async work
})
);
});
actualDuration
is probably your best metric for how well or poor a component "performs" in terms of rendering time. The delta betweenactualDuration
andbaseDuration
is an indicator of how much better it performs on an update rather than a mount (aka much time is saved by memoization).Finishes rendering as in...commits what it has to the DOM? That's what the
React.Profiler
API does (and also what the DevTools Profiler shows). Each commit is a finished render. If you mean "has no more scheduled work pending" then the experimental interaction tracing API could be used to do this but for your purposes, it's probably overkill. In hindsight you could just look at the last commit and when it took place vs the current time.A "cascading update" means something in the commit phase (
componentDidMount
,componentDidUpdate
,useEffect
, oruseLayoutEffect
) scheduled another render right after React just finished the current one. So yes it does relate to the number of commits. This is something we warn about because in most of those cases, the cascading update has to be flushed synchronously (b'c of e.g. positioning tooltips, sizing modals, etc- intermediate states that shouldn't be visible to the user) so it delays paint. (You can find lots of other things written about this on the React docs and GItHub issues.)Simple answer: Yes, some differences are expected. Profiler's
actualDuration
only includes time spent in your React component. Browser profiler also includes time spent in React internals. Profiler also sums durations within the render. Browser tools might show them separately (at least in the case of concurrent rendering).