topics:
speeding up the three steps:
react devtools functionality:
highlightling components whenever they update:
In the React devtools settings: select Highlight updates when components render.
Components that render get a colored border (color varies based on render frequency)
recording and profiling a session:
In the browser tools' "Profiler" tab:
exploring the profile data:
Each user interaction (e.g. click, button press) causes a so-called commit
Commits are shown as bars in the top right corner
Details of a commit can be seen by clicking on it
Numbers in a commit detail:
TodoApp (3ms of 109ms)
this means:
colors in a commit detail:
Color scale from green to yellow shows how much time a component took to render - compared to its siblings
Grey-striped components did not rerender
memoization = caching of previously computed results
applications in React:
example without memoization:
const [todos, setTodos] = useState([]);
const numActiveTodos = todos.filter(
(todo) => !todo.completed
).length;
with memoization:
const [todos, setTodos] = useState([]);
const numActiveTodos = useMemo(
// function to recompute value
() => todos.filter((todo) => !todo.completed).length,
// array of dependencies
[todos]
);
the computation is only rerun if a dependency listed in the array changes
Note:
If the rendering of a component is the same as before, re-rendering will generally already be fast as React will only recreate the virtual DOM (and will not touch the real DOM)
Often, no further optimization is necessary
Generally a component only needs to be rerendered when its props or state actually change
what React already does for us:
hooks (state, reducer, context) will not trigger a re-rendering if their value has not changed
what we can add:
if a parent component rerenders, but the child's props haven't changed, don't rerender the child component (memoization)
demo: component only rerenders if its state changes
function Coin() {
const [coin, setCoin] = useState('heads');
const throwCoin = () => {
setCoin(Math.random() > 0.5 ? 'heads' : 'tails');
};
return (
<div>
{coin}
<button onClick={throwCoin}>throw</button>
<div>last rendering: {new Date().toISOString()}</div>
</div>
);
}
if only those subcomponents whose props have changed should rerender:
memo
functionPureComponent
instead of Component
optimized function component:
import { memo } from 'react';
function Rating(props) {
// ...
}
export default memo(Rating);
optimized class component:
import { PureComponent } from 'react';
class Rating extends PureComponent {
// ...
}
the Rating
component will not be rerendered if its props are the same as before:
<Rating stars={prodRating} />
<Rating stars={prodRating} onChange={setProdRating} />
See also:
if Rating
is a "memoized" component, which of the following will re-render when the parent is re-rendered?
<Rating stars={prodRating} />
<Rating stars={prodRating} onChange={setProdRating} />
<Rating
stars={prodRating}
onChange={(newRating) => setProdRating(newRating)}
/>
<Rating
stars={prodRating}
onChange={(newRating) => setProdRating(newRating)}
/>
the change handler would be recreated and passed down as a different object on every rendering of the parent component
solutions:
dispatch
functionmemoizing event handlers:
function TodoApp() {
const [todos, setTodos] = useState([]);
const deleteTodoA = useMemo(
() => (id) => {
setTodos((todos) => todos.filter((t) => t.id !== id));
},
[]
);
const deleteTodoB = useCallback((id) => {
setTodos((todos) => todos.filter((t) => t.id !== id));
}, []);
}
If a React component does rerender, its results are not directly passed on to the browser.
Instead, a virtual DOM representation is created and compared to the previous virtual DOM. Only the differences are passed on to the browser to process.
Usually React is very efficient at figuring out what has changed - but it needs help when elements are repeated in an array
Rule of thumb: Any time we use .map
in our JSX templates the inner elements should have a unique key property to help React
to reduce bundle size of React apps: only import components when they are needed
common use case: import a route only when it is accessed
imports in JavaScript:
import
as a statement - synchronous import before the rest of the file is executed (in webpack: automatically included in bundle)import
as a function - asynchronous import when neededReact facilities for lazy-loading:
lazy
functionSuspense
componentwith react router:
import { Suspense, lazy } from 'react';
import { Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
source:
Route-based code splitting on reactjs.org