Pourquoi utiliser TypeScript ?
C'est souvent une question que l'on entend dans le monde du JS. J'ai personnellement adopté cette solution depuis un certain temps. En React, on parle d'une application comme un arbre de composants :
- Chaque composant représente un élément : un bouton, une barre de navigation...
- Cet élément est ensuite regroupé : formulaire de connexion
- Les composants peuvent transmettre une information du parent à l'enfant et vice versa
C'est en connaissant tous ces éléments que l'on peut dire que TypeScript est vraiment utile :
- Nous pouvons créer des interfaces pour nos composants : on est alors sûr que nos composants reçoivent les bonnes entrées.
Il existe "prop-types" qui nous permet en JS de définir un type :
import PropTypes from 'prop-types';
const Greeting = ({
name
}) => {
return ( <
h1 > Hello, {
name
} < /h1>
);
}
Greeting.propTypes = {
name: PropTypes.string
};
Mais alors pourquoi ne pas utiliser ce mot clé ?
Ce mot clé ne sera pas détecté par votre éditeur. Autrement dit, vous vous rendrez compte que la variable passée n'est pas du bon type seulement lorsque vous lancerez l'application. On peut utiliser ce mot clé uniquement avec des composants : dans votre application il existera aussi des fonctions et des classes qui n'utilisent pas React.
TypeScript est plus puissant : il donne la possibilité de définir des types par nous-mêmes.
Notre projet pour ce TP ?
Nous allons reproduire le site Trello (version simplifiée) : Trello. Trello permet de nous donner un kanban board où :
- Nous pouvons créer des tâches
- Organiser ces tâches en liste
- L'on peut faire un drag and drop de ces tâches
- Ajouter des commentaires et ajouter des fichiers à une tâche
Ici nous allons couvrir les fonctionnalités : 1, 2 et 3. La 4 sera ignorée.
Résultat :

Ce que nous voulons :
- Afficher des colonnes que l'on peut drag and drop : chaque colonne est une liste de tâches
- Chaque tâche doit être draggable et aller de colonne en colonne
- La possibilité de créer de nouvelles colonnes en cliquant sur un bouton
- Chaque colonne doit permettre de créer une tâche
Création du projet :
Nous allons créer notre projet à travers le CLI de React, mais pourquoi utiliser ce CLI ?
- Il permet d'amener l'ensemble des dépendances avec lui
- Il facilite les outils pour notre programme : Webpack ou Parcel
- Il nous met à disposition les runners : CSS, lint, hot-reload, ...
npx create-react-app --template typescript trello-clone
Création du projet :
Nous allons créer notre projet à travers le CLI de React, mais pourquoi utiliser ce CLI ?
- Il permet d'amener l'ensemble des dépendances avec lui
- Il facilite les outils pour notre programme : Webpack ou Parcel
- Il nous met à disposition les runners : CSS, lint, hot-reload, ...
Structure
Lors de la génération du projet, vous devriez voir la structure suivante :

Nous allons expliquer brièvement ces dossiers/fichiers :
- readme : fichier qui contient la description de votre application pour Git
- package.json : contient les metadata du projet : nom, version, description. Mais aussi les dépendances des librairies externes.
- public : dossier qui contient les fichiers statiques de notre application. Ils ne sont pas inclus dans le processus de compilation et sont intouchables durant le build.
- index.html : fichier d'entrée qui monte l'application React
- manifest.json : fournit les metadata pour les PWA. Par exemple : ce fichier permet l'installation de l'application sur un téléphone mobile, similaire à une application native. Il contient : le nom de l'application, l'icône, les couleurs du thème et d'autres données pour l'application mobile.
- src : dans ce dossier, vont être utilisés par Webpack et ajoutés dans le bundle.
- index.tsx : point d'entrée de notre application, Webpack l'utilisera pour builder votre application. Il contient la logique de React pour rendre notre application.
- reportWebVitals : permet de mesurer les performances de notre application.
- React.StrictMode : est une bonne pratique et permet de s'assurer que nous n'utilisons pas des méthodes dépréciées.
- app.tsx : représente le root de votre application, c'est la première instance montée dans le DOM qui importe React par défaut.
- react-app-env.d.ts : elle contient les définitions des types en TypeScript. On peut y référencer les fichiers images et autres, sans cela TypeScript ne les comprendrait pas (il n'est intéressé que par les fichiers TS).
Le JSX et TSX, c'est quoi ?
C’est une extension syntaxique de JavaScript utilisée par React. Ce langage permet de ne plus séparer le rendu de la logique. En d'autres termes, vous n'aurez plus de fichiers HTML séparés de la logique JS, mais un seul et même fichier unissant les deux.
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Kylian',
lastName: 'Mbappé'
};
const element = (
<h1>
Bonjour, {formatName(user)} !
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
JSX n'est rien d'autre qu'une extension. Après la compilation, les expressions JSX deviennent de simples appels de fonctions JavaScript, dont l’évaluation renvoie des objets JavaScript.
Suppression des fichiers
Avant d'aller plus loin, nous pouvons supprimer les fichiers dont nous n'avons pas besoin :
- logo.svg
- App.css
- App.test.tsx
Le style :
Nous allons maintenant créer un dossier "styles" : ce dossier regroupera l'ensemble de nos fichiers CSS. Habituellement, j'aime bien utiliser SASS, cela simplifie énormément de complexité qu'amène le langage CSS et utilise une approche un peu plus moderne.
Créons un dossier "styles" dans le dossier "src". À l'intérieur de ce dossier, nous pouvons y ajouter un dossier "style".
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body, #root {height: 100%;}
Pour lancer ce fichier nous devons installer une dépendance :
npm i sass
Grâce à cela, nous sommes en mesure d'installer et d'interpréter les fichiers SASS. Maintenant, nous pouvons importer ce fichier directement dans App.tsx :
import React from 'react';
import "./styles/index.scss";
function App() {
C'est une bonne chose de fait ! Mais il existe une librairie vraiment utile qui fonctionne très bien avec TypeScript : styled-components ! En d'autres termes, cette librairie nous permet de créer des composants réutilisables attachés à un style. Pour l'installer :
npm i styled-components
npm i @types/styled-components
Nous allons créer les fichiers de styles pour les composants suivants :
- AppContainer : nous permettra d'arranger les colonnes horizontalement
- ColumnContainer : met la couleur gris d'arrière-plan et les contours arrondis
- ColumnTitle : met le titre en gras et ajoute l'espacement
- CardContainer
Créons un fichier "container.ts" dans le dossier "styled-components". Ce fichier englobera l'ensemble des conteneurs que nous avons définis plus haut :
import styled from "styled-components";
export const AppContainer = styled.div`
align-items: flex-start;
background-color: #3179ba;
display: flex;
flex-direction: row;
height: 100%;
padding: 20px;
width: 100%;`
export const ColumnContainer = styled.div`
background-color: #ebecf0;
width: 300px;
min-height: 40px;
margin-right: 20px;
border-radius: 3px;
padding: 8px 8px;
flex-grow: 0;~
`
export const CardContainer = styled.div`
background-color: #fff;
cursor: pointer;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
max-width: 300px;
border-radius: 3px;
box-shadow: #091e4240 0px 1px 0px 0px;`
Nous avons donc défini nos 3 composants. L'ensemble de ces 3 composants seront utilisés pour générer un div qui comportera le CSS qui lui est appliqué. Ainsi, dans notre fichier App, nous pouvons ajouter :
import React from 'react';
import "./styles/index.scss";
import { AppContainer } from "./styled-components/container"
function App() {
return (
<div className="App">
<AppContainer>
Columns will go there
</AppContainer>
</div>
);
}
export default App;
Cela nous donnera un conteneur bleu, permettant l'affichage d'un texte où nos colonnes devront être placées :

Nous appliquerons les autres styled-components plus tard, mais d'abord, nous pouvons créer un nouveau fichier nommé "title.ts" :
import styled from "styled-components";
export const ColumnTitle = styled.div`
padding: 6px 16px 12px;
font-weight: bold;`
Parfait ! Nos styles sont définis nous pouvons passer aux choses sérieuses : la création de nos colonnes !
Les composants :
Nous allons utiliser des composants, cela nous permettra d'ajouter de la logique dans notre code et d'ajouter un comportement pour chacun de ces composants. Nous n'allons pas détailler le fonctionnement des composants, mais si cela vous intéresse d'aller plus en profondeur, vous pouvez consulter la documentation officielle de React.
Lorsque nous définissons des composants en React, nous devons donner des types aux props (données acheminées du parent à l'enfant) et aux states (les données du composant). Dans un premier temps, nous allons créer un dossier "components" à la racine du dossier "src". Cela nous permettra d'avoir un dossier réunissant l'ensemble de nos composants. Ensuite, nous pouvons créer notre premier composant : Column.tsx.
import React from "react"
import {
ColumnContainer
} from "../styled-components/container";
import {
ColumnTitle
} from "../styled-components/title"
export const Column = () => {
return ( <
ColumnContainer >
<
ColumnTitle > Column Title < /ColumnTitle> <
/ColumnContainer>
)
}
Ici, nous avons réutilisé nos différents styles précédemment créés. Bon, ce code n'est pas complet, nous voulons permettre au parent de transmettre les valeurs des colonnes à l'enfant ! Mais en TypeScript, nous devons typer ces props, sinon cela serait trop facile :
interface ColumnProps {
text ? : string
}
export const Column = ({
text
}: ColumnProps) => {
return ( <
ColumnContainer >
<
ColumnTitle > Column Title < /ColumnTitle> <
/ColumnContainer>
)
}
Attendez une minute ?! Pourquoi "text?: string" comporte un point d'interrogation ? Simplement parce que ce petit trick permet de dire à TypeScript qu'une valeur n'est pas obligatoire. Autrement dit : on aura un "undefined" si cela n'est pas passé.
Créons ensuite notre carte en ajoutant, et je suis sûr que vous l'aurez deviné, un fichier "card.tsx" dans notre dossier "components" :
import React from "react"
import {
CardContainer
} from "../styled-components/container"
interface CardProps {
text: string
}
export const Card = ({
text
}: CardProps) => {
return <CardContainer > {
text
} < /CardContainer>
}
Rien de très compliqué par rapport à ce que nous avons fait précédemment, sauf qu'ici nous définissons la props "text" comme obligatoire !
Maintenant nous pouvons ajouter cette carte dans notre composant "Column" :
import React from "react";
import { ColumnContainer } from "../styled-components/container";
import { ColumnTitle } from "../styled-components/title";
interface ColumnProps {
text?: string;
}
export const Column = ({
text,
children,
}: React.PropsWithChildren<ColumnProps>) => {
return (
<ColumnContainer>
<ColumnTitle>{text}</ColumnTitle>
{children}
</ColumnContainer>
);
};
React.PropsWithChildren : permets d'améliorer notre interface props en y ajoutant une définition pour children. L'on aurait pu aussi l'ajouter directement dans notre interface ; children?: React.ReactNode. Mais notre approche est plus propre et nous permet d'utiliser les fonctions fournies par React.
Maintenant, nous allons tout assembler dans notre App.tsx :
import React from "react";
import "./styles/index.scss";
import { AppContainer } from "./styled-components/container";
import { Card } from "./components/card";
import { Column } from "./components/column";
const App = () => {
return (
<AppContainer>
<Column text="To Do">
<Card text="Generate app scaffold" />
</Column>
<Column text="In Progress">
<Card text="Learn Typescript" />
</Column>
<Column text="Done">
<Card text="Begin to use static typing" />
</Column>
</AppContainer>
);
};
export default App;
On a fait du bon travail je trouve :

Interactions :
Jusqu'ici nous ne possédons pas de logique, nous allons donc ajouter la logique suivante :

Nous avons deux states :
- Un bouton qui ajoute une autre tache
- Un bouton qui ajoute une autre liste
Quand nous cliquons sur le bouton, les composants nous mettent à disposition un champ input et un autre bouton "Créer". Le clic sur ce bouton va appeler la fonction de retour que nous lui passerons via une prop.
import styled from "styled-components";
interface AddItemButtonProps {
dark?: boolean;
}
export const AddItemButton = styled.button<AddItemButtonProps>`
background-color: #ffffff3d;
border-radius: 3px;
border: none;
color: ${props => (props.dark ? "#000" : "#fff")};
cursor: pointer;
max-width: 300px;
padding: 10px 12px;
text-align: left;
transition: background 85ms ease-in;
width: 100%;
&:hover {
background-color: #ffffff52;
}
`
Ici, rien de bien compliqué, la seule chose qui diffère de tout ce que nous avons fait précédemment :
- Nous utilisons une prop pour définir la couleur du texte du bouton.
Ce bouton sera utilisé à travers notre application pour définir les différents boutons énumérés précédemment. Maintenant, nous pouvons créer notre formulaire d'ajout. Pour cela, rien de magique une nouvelle fois, nous allons commencer par créer le container de notre formulaire dans le fichier styled-components/container.ts :
export const NewItemFormContainer = styled.div`
max-width: 300px;
display: flex;
flex-direction: column;
width: 100%;
align-items: flex-start;
`
Ensuite, nous pouvons créer notre bouton en ajoutant le style suivant dans notre fichier styled-components/button.ts :
export const NewItemButton = styled.button`
background-color: #5aac44;
border-radius: 3px;
border: none;
box-shadow: none;
color: #fff;
padding: 6px 12px;
text-align: center;
`
Ce composant nous donne un magnifique bouton vert avec de très beaux contours ! Bien maintenant je vous propose de créer un nouveau fichier dans notre styled-components : form.ts. Dans ce dossier, nous allons y ajouter tous les éléments propres aux formulaires. Commençons par notre input :
import styled from "styled-components";
export const NewItemInput = styled.input`
border-radius: 3px;
border: none;
box-shadow: #091e4240 0px 1px 0px 0px;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
width: 100%;
`
Nous avons tout pour créer notre composant maintenant, alors créons le fichier add-new-item.tsx dans le dossier component:
import React, { useState} from "react"
import { AddItemButton } from "../styled-components/button"
interface AddNewItemProps {
onAdd(text: string): void
toggleButtonText: string;
dark?: boolean;
}
Jusqu'ici rien de compliqué, l'idée est que les props possibles sont :
- onAdd: C'est une fonction callback que l'on appelle quand on clique sur le bouton.
- toggleButtonText: C'est le texte que nous voulons afficher dans le composant button.
- dark: Représente la propriété que nous passons au composant.
Nous pouvons ajouter la logique de notre add :
export const AddNewItem = (props: AddNewItemProps) => {
const [showForm, setShowForm] = useState(false);
const { onAdd, toggleButtonText, dark } = props;
if (showForm) {
// We show item creation form here
}
return (
<AddItemButton dark={dark} onClick={() => setShowForm(true)}>
{toggleButtonText}
</AddItemButton>
)
}
Nous avons défini nos states :
- showForm : permettant de montrer ou non le bouton "Create"
- setShowForm: permettant de modifier la propriété showForm (setter)
Mais aussi nos props expliquées plus haut. Nous retournons ensuite le composant, qui possède une fonction callback qui permettra de changer le state. Bref, notre composant est presque fini, mais il nous manque le form que nous allons placer dans notre IF. Créons le composant new-item-form.tsx dans notre dossier components :
import React, { useState } from "react";
import { NewItemButton } from "../styled-components/button";
import { NewItemFormContainer } from "../styled-components/container";
import { NewItemInput } from "../styled-components/form";
Nous avons importé nos styled-components définis précédemment. Maintenant ajoutons les props, nous devons ajouter notre fonction onAdd :
interface NewItemFormProps {
onAdd(text: string): void
}
Nous pouvons ajouter le corps de notre composant qui assemblera l'ensemble de nos styled-components :
import React, { useState } from "react";
import { NewItemButton } from "../styled-components/button";
import { NewItemFormContainer } from "../styled-components/container";
import { NewItemInput } from "../styled-components/form";
interface NewItemFormProps {
onAdd(text: string): void;
}
export const NewItemForm = ({ onAdd }: NewItemFormProps) => {
const [text, setText] = useState("");
return (
<NewItemFormContainer>
<NewItemInput value={text} onChange={(e) => setText(e.target.value)} />
<NewItemButton onClick={() => onAdd(text)}>Create</NewItemButton>
</NewItemFormContainer>
);
};
Ce composant est utilisé pour enregistrer la valeur du champ texte. Une chose notable est l'omission du typage pour l'événement au niveau de notre fonction de retour onChange. Cela est rendu possible grâce à React qui fournit le type à Typescript.
Maintenant, nous devons importer notre composant dans AddNewItem afin d'afficher le bouton au moment d'un clic sur le bouton d'ajout :
import React, { useState } from "react";
import { AddItemButton } from "../styled-components/button";
import { NewItemForm } from "./new-item-form";
interface AddNewItemProps {
onAdd(text: string): void;
toggleButtonText: string;
dark?: boolean;
}
export const AddNewItem = (props: AddNewItemProps) => {
const [showForm, setShowForm] = useState(false);
const { onAdd, toggleButtonText, dark } = props;
if (showForm) {
return (
<NewItemForm
onAdd={(text) => {
onAdd(text);
setShowForm(false);
}}
/>
);
}
return (
<AddItemButton dark={dark} onClick={() => setShowForm(true)}>
{toggleButtonText}
</AddItemButton>
);
};
Nous avons importé notre composant. Nous avons ajouté ce composant dans notre condition showForm. Le composant utilisera la fonction setShowForm pour fermer le composant.
Fin de la partie 1
Bien, nous avons créé l'ensemble des composants et des actions que nous voulons. Dans le chapitre suivant, nous allons :
- Assembler les composants dans le layout de l'application
- Ajouter la notion de state
- Utiliser et créer un Reducer pour manipuler le state
- Créer des actions
La suite de ce tutoriel se trouve ici : React - Trello - Part 2





