Ready for an exciting adventure in the land of React and TypeScript? Picture this: You are a star mechanic, and your task is to supercharge a fancy race car (our web app). Let's dive right in!
1. Optimize Large Components
Large components can be slow. Let's break them down!
Imagine we have a list of video game characters. Each character has a unique ability that we want to display.
type Character = {
id: number;
name: string;
ability: string;
};
const characters: Character[] = [
//... a large list of characters
];
const CharacterList = () => {
return (
<div>
{characters.map((character) => (
<div key={character.id}>
<h2>{character.name}</h2>
<p>{character.ability}</p>
</div>
))}
</div>
);
};
This component could be more efficient if we break it down:
const CharacterCard = ({ character }: { character: Character }) => (
<div key={character.id}>
<h2>{character.name}</h2>
<p>{character.ability}</p>
</div>
);
const CharacterList = () => {
return (
<div>
{characters.map((character) => (
<CharacterCard character={character} />
))}
</div>
);
};
2. Use React.memo
We can avoid unnecessary re-renders using React.memo
. Let's apply this to our CharacterCard
:
const CharacterCard = React.memo(({ character }: { character: Character }) => (
<div key={character.id}>
<h2>{character.name}</h2>
<p>{character.ability}</p>
</div>
));
Now, CharacterCard
will only re-render if any property in character
prop changes, not just if the object reference changes. To learn more, read about shallow equality and React.memo.
3. Use useCallback and useMemo
Imagine we have an action to select a character, which filters the character list:
const CharacterList = () => {
const [selectedId, setSelectedId] = React.useState<number | null>(null);
const handleSelect = (id: number) => {
setSelectedId(id);
};
return (
<div>
{characters.map((character) => (
<CharacterCard
character={character}
onSelect={handleSelect}
/>
))}
</div>
);
};
Every render creates a new handleSelect
function. We can optimize this with useCallback
:
const handleSelect = React.useCallback((id: number) => {
setSelectedId(id);
}, []);
Now, what if we only want to display selected characters? We could filter them every render, but useMemo
makes this more efficient:
const selectedCharacters = React.useMemo(
() => characters.filter((character) => character.id === selectedId),
[characters, selectedId]
);
return (
<div>
{selectedCharacters.map((character) => (
<CharacterCard
character={character}
onSelect={handleSelect}
/>
))}
</div>
);
Now selectedCharacters will only update if the characters
or selectedId
changes, instead of on every re-render.
4. Use Selectors for State Management
Selectors efficiently grab state parts. Using the Redux toolkit as an example:
const getSelectedCharacters = (state: RootState) =>
state.characters.filter((character) => character.id === state.selectedId);
// Then in your component
const selectedCharacters = useSelector(getSelectedCharacters);
Now you can reuse this data in multiple components and keep the state logic in one place. There's so much we can do with the Redux toolkit.
5. Lazy Load Components
React allows you to only load components when they're needed using React.lazy
:
const CharacterCard = React.lazy(() => import('./CharacterCard'));
// Don't forget to wrap your component with `React.Suspense` in your parent component
return (
<React.Suspense fallback={<div>Loading...</div>}>
<CharacterCard character={character} />
</React.Suspense>
);
And there you have it, friends! With these tips, your React + TypeScript application will zoom faster than ever. Keep practising, keep learning, and most importantly, have fun! See you at the finish line!