feat: Add hooks examples and explanations for React component lifecycle
This commit is contained in:
parent
27caad99e6
commit
15cc58f07f
@ -32,7 +32,7 @@ export function QuestionIcon(props: GradientProps) {
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="m 16.39 14.617 l 1.179 -3.999 C 17.38 9.304 16.133 9.127 15.469 10.645 C 15.306 11.269 14.71 11.12 14.71 10.537 a 1.66 1.66 5 1 1 3.808 0.217 l -1.5182 5.4314 a 0.602 0.602 5 0 1 -1.1795 -0.1032 Z"
|
||||
d="M 15.613 15.7139 C 15.8705 15.1461 17.758 11.3442 17.8109 10.4986 C 17.9128 8.8632 15.5223 8.1803 14.6161 9.3819 C 14.4617 9.5862 13.8186 9.7889 13.8694 9.2081 C 14.0808 7.755 16.4352 7.3025 17.9493 8.5954 C 18.5705 9.1256 18.8798 9.9497 18.7641 10.6412 C 18.5471 11.9402 16.7786 15.9192 16.7786 15.9192 C 16.2095 17.065 15.3394 16.3173 15.613 15.7139 Z"
|
||||
class="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]"
|
||||
stroke-width={2}
|
||||
stroke-linecap="round"
|
||||
|
||||
@ -4,6 +4,9 @@ description: Découvre les hooks de React, une fonctionnalité qui te permet de
|
||||
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
|
||||
---
|
||||
|
||||
import Callout from "@/components/Callout";
|
||||
import tabs from "./tabs";
|
||||
|
||||
Ça y est, on rentre dans le vif du sujet avec les **hooks** de React !
|
||||
|
||||
On en a déjà parlé un peu dans l'article précédent _(notamment avec le hook `useState` pour déclarer un state)_, mais on va maintenant les aborder en détail.
|
||||
@ -21,75 +24,7 @@ C'était pas mal, mais ça devenait vite compliqué à gérer, notamment pour pa
|
||||
|
||||
Pour te donner un aperçu, voici à quoi ressemblait un composant de classe avec les trois étapes du cycle de vie :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { count: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log("Component mounted");
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log("Component updated");
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log("Component unmounted");
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>{this.state.count}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
|
||||
type MyComponentState = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state: MyComponentState = { count: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('Component mounted');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log('Component updated');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log('Component unmounted');
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>{this.state.count}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
<tabs.reactClassComponent />
|
||||
|
||||
Comme dirait l'un de mes chers confrères jury :
|
||||
|
||||
@ -126,39 +61,7 @@ Tu comprends pourquoi je dis "couteau suisse" ? 😏
|
||||
|
||||
Alors sur le papier c'est top, mais maintenant je te laisse t'amuser à comprendre comment ça fonctionne 😇
|
||||
|
||||
{% tabs defaultSelectedTab="1" %}
|
||||
|
||||
{% tab value="1" label="Écriture #1" %}
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
}, []);
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="2" label="Écriture #2" %}
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
}, [props.uneProp]);
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="3" label="Écriture #3" %}
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
<tabs.reactUseEffectSyntaxes />
|
||||
|
||||
Pas cool, hein ? 😂
|
||||
Et bien dans ces exemples, on a trois manières d'écrire un useEffect :
|
||||
@ -167,11 +70,9 @@ Et bien dans ces exemples, on a trois manières d'écrire un useEffect :
|
||||
2. Le hook est exécuté à chaque mise à jour du composant
|
||||
3. Le hook est exécuté à chaque mise à jour du composant, mais seulement si la propriété `uneProp` de `props` a changé
|
||||
|
||||
{% callout type="note" title="`useEffect` et les mises à jour du composant" %}
|
||||
|
||||
<Callout type="note" title="`useEffect` et les mises à jour du composant">
|
||||
Alors quand je dis "le hook est exécuté à chaque mise à jour du composant", il faut également prendre en compte qu'il est également exécuté après le premier rendu du composant.
|
||||
|
||||
{% /callout %}
|
||||
</Callout>
|
||||
|
||||
Mais alors, comment on fait pour gérer ces étapes avec des composants fonctionnels ?
|
||||
Si tu n'as pas vu la différence entre les trois écritures, tu remarqueras que c'est le deuxième argument de useEffect qui fait la différence.
|
||||
@ -183,31 +84,19 @@ Selon ce tableau, le hook sera exécuté à des moments différents du cycle de
|
||||
|
||||
### ⚙️ ComponentDidMount
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
}, []);
|
||||
```
|
||||
<tabs.reactUseEffectMount />
|
||||
|
||||
Le tableau de dépendances est vide, on sous-entend que le hook ne dépend d'aucune variable et sera exécuté une seule fois.
|
||||
On peut donc dire que c'est l'équivalent de `componentDidMount` pour les composants de classe.
|
||||
|
||||
### 🔧 ComponentDidUpdate
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
<tabs.reactUseEffectUpdate />
|
||||
|
||||
Ici, le tableau de dépendances est absent _(et tout va bien, il est optionnel !)_.
|
||||
Le hook sera exécuté à chaque mise à jour du composant, ainsi que lors du premier rendu.
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
}, [props.uneProp]);
|
||||
```
|
||||
<tabs.reactUseEffectUpdateDependency />
|
||||
|
||||
Dans ce cas, le tableau de dépendances contient la propriété `uneProp` de `props`.
|
||||
Le hook sera exécuté à chaque mise à jour du composant _(ainsi qu'au montage)_, mais seulement si la propriété `uneProp` a changé.
|
||||
@ -217,14 +106,7 @@ Le hook sera exécuté à chaque mise à jour du composant _(ainsi qu'au montage
|
||||
Et là, tu te dis : "Mais comment je fais pour gérer le démontage du composant ?".
|
||||
Hehehe, c'est là que ça devient intéressant 😏
|
||||
|
||||
```jsx
|
||||
React.useEffect(() => {
|
||||
// ...
|
||||
return () => {
|
||||
// ...
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
<tabs.reactUseEffectUnmount />
|
||||
|
||||
Tu as vu ce petit `return` ? Et bien, c'est notre équivalent de `componentWillUnmount` pour les composants de classe !
|
||||
|
||||
@ -242,65 +124,9 @@ Pour éviter que React se dise "Tiens, il y a eu un changement, je vais re-rendr
|
||||
|
||||
Allez, mettons un peu ce qu'on voit de voir en pratique !
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
Voici un exemple de code qui utilise `useEffect` pour gérer le cycle de vie d'un composant :
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
export const Counter = () => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component mounted");
|
||||
|
||||
return () => {
|
||||
console.log("Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component updated");
|
||||
});
|
||||
|
||||
const increment = () => setCount(count + 1);
|
||||
|
||||
return <button onClick={increment}>{count}</button>;
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
```tsx
|
||||
import React from "react";
|
||||
|
||||
export const Counter = () => {
|
||||
const [count, setCount] = React.useState<number>(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component mounted");
|
||||
|
||||
return () => {
|
||||
console.log("Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component updated");
|
||||
});
|
||||
|
||||
const increment = () => setCount(count + 1);
|
||||
|
||||
return <button onClick={increment}>{count}</button>;
|
||||
};
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
<tabs.reactUseEffectExample />
|
||||
|
||||
### 🔢 On revient sur le cycle de vie !
|
||||
|
||||
@ -309,39 +135,16 @@ On revient sur le cycle de vie d'un composant maintenant qu'on a vu `useEffect`
|
||||
|
||||
Je vais te donner un exemple de code supplémentaire et tu vas devoir deviner l'ordre d'apparition des messages dans la console.
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
export const MyComponent = () => {
|
||||
React.useEffect(() => {
|
||||
console.log("1");
|
||||
});
|
||||
|
||||
console.log("2");
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("3");
|
||||
}, []);
|
||||
|
||||
const logInRender = () => {
|
||||
console.log("4");
|
||||
return null;
|
||||
};
|
||||
|
||||
return <div>{logInRender()}</div>;
|
||||
};
|
||||
```
|
||||
<tabs.reactUseEffectChallenge />
|
||||
|
||||
Voici les possibilités :
|
||||
|
||||
{% callout type="question" title="Quel est l'ordre d'apparition des messages dans la console ?" %}
|
||||
|
||||
<Callout type="question" title="Quel est l'ordre d'apparition des messages dans la console ?">
|
||||
- **A** - 4, 2, 1, 3
|
||||
- **B** - 2, 4, 1, 3
|
||||
- **C** - 1, 2, 3, 4
|
||||
- **D** - La réponse D
|
||||
|
||||
{% /callout %}
|
||||
</Callout>
|
||||
|
||||
## 🧩 Les autres hooks
|
||||
|
||||
244
app/pages/docs/react/hooks/tabs.tsx
Normal file
244
app/pages/docs/react/hooks/tabs.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
import { Snippet } from "@/components/Snippet";
|
||||
|
||||
const reactClassComponentSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import React from "react";
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { count: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log("Component mounted");
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log("Component updated");
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log("Component unmounted");
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>{this.state.count}</div>;
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import React from 'react';
|
||||
|
||||
type MyComponentState = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state: MyComponentState = { count: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('Component mounted');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log('Component updated');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
console.log('Component unmounted');
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>{this.state.count}</div>;
|
||||
}
|
||||
}`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectSyntaxesSnippets = [
|
||||
{
|
||||
name: "Syntaxe 1",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter
|
||||
}, []);`,
|
||||
},
|
||||
{
|
||||
name: "Syntaxe 2",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter
|
||||
}, [props.uneProp]);`,
|
||||
},
|
||||
{
|
||||
name: "Syntaxe 3",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter
|
||||
});`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectMountSnippets = [
|
||||
{
|
||||
name: "Exemple de `useEffect` pour le montage",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter lors du montage
|
||||
// (équivalent à componentDidMount)
|
||||
}, []);`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectUpdateSnippets = [
|
||||
{
|
||||
name: "Exemple de `useEffect` pour la mise à jour",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter lors du montage et de la mise à jour
|
||||
// (équivalent à componentDidMount et componentDidUpdate)
|
||||
});`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectUpdateDependencySnippets = [
|
||||
{
|
||||
name: "Exemple de `useEffect` pour la mise à jour",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter lors du montage et de la mise à jour
|
||||
// (équivalent à componentDidMount et componentDidUpdate)
|
||||
}, [props.uneProp]);`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectUnmountSnippets = [
|
||||
{
|
||||
name: "Exemple de `useEffect` pour le démontage",
|
||||
codeLanguage: "jsx",
|
||||
code: `useEffect(() => {
|
||||
// Code à exécuter lors du montage
|
||||
|
||||
return () => {
|
||||
// Code à exécuter lors du démontage
|
||||
// (équivalent à componentWillUnmount)
|
||||
};
|
||||
}, []);`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectExampleSnippets = [
|
||||
{
|
||||
name: "JSX",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import React from "react";
|
||||
|
||||
export const Counter = () => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component mounted");
|
||||
|
||||
return () => {
|
||||
console.log("Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component updated");
|
||||
});
|
||||
|
||||
const increment = () => setCount(count + 1);
|
||||
|
||||
return <button onClick={increment}>{count}</button>;
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: "TSX",
|
||||
codeLanguage: "tsx",
|
||||
withLineNumbers: true,
|
||||
code: `import React from "react";
|
||||
|
||||
export const Counter = () => {
|
||||
const [count, setCount] = React.useState<number>(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component mounted");
|
||||
|
||||
return () => {
|
||||
console.log("Component unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("Component updated");
|
||||
});
|
||||
|
||||
const increment = () => setCount(count + 1);
|
||||
|
||||
return <button onClick={increment}>{count}</button>;
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
const reactUseEffectChallengeSnippets = [
|
||||
{
|
||||
name: "Testons le cycle de vie d'un composant",
|
||||
codeLanguage: "jsx",
|
||||
withLineNumbers: true,
|
||||
code: `import React from "react";
|
||||
|
||||
export const MyComponent = () => {
|
||||
React.useEffect(() => {
|
||||
console.log("1");
|
||||
});
|
||||
|
||||
console.log("2");
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("3");
|
||||
}, []);
|
||||
|
||||
const logInRender = () => {
|
||||
console.log("4");
|
||||
return null;
|
||||
};
|
||||
|
||||
return <div>{logInRender()}</div>;
|
||||
};`,
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
reactClassComponent: () => <Snippet snippets={reactClassComponentSnippets} />,
|
||||
reactUseEffectSyntaxes: () => (
|
||||
<Snippet snippets={reactUseEffectSyntaxesSnippets} />
|
||||
),
|
||||
reactUseEffectMount: () => <Snippet snippets={reactUseEffectMountSnippets} />,
|
||||
reactUseEffectUpdate: () => (
|
||||
<Snippet snippets={reactUseEffectUpdateSnippets} />
|
||||
),
|
||||
reactUseEffectUpdateDependency: () => (
|
||||
<Snippet snippets={reactUseEffectUpdateDependencySnippets} />
|
||||
),
|
||||
reactUseEffectUnmount: () => (
|
||||
<Snippet snippets={reactUseEffectUnmountSnippets} />
|
||||
),
|
||||
reactUseEffectExample: () => (
|
||||
<Snippet snippets={reactUseEffectExampleSnippets} />
|
||||
),
|
||||
reactUseEffectChallenge: () => (
|
||||
<Snippet snippets={reactUseEffectChallengeSnippets} />
|
||||
),
|
||||
};
|
||||
@ -90,7 +90,7 @@ Et voilà ! Pas besoin de plus pour gérer un state en React 😉
|
||||
Mais qu'est-ce qu'il se passe sous le capot ?
|
||||
C'est un peu plus complexe que ça, mais pour faire simple :
|
||||
|
||||
### ⚙️ Montage du composant
|
||||
### ⚙️ Montage du composant (Mounting)
|
||||
|
||||
On vient prévenir React que notre composant va avoir un **state** et on lui donne une valeur initiale _(ici 0)_.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user