Fermer

mai 10, 2022

Tutoriel React : créer une application de calculatrice à partir de zéro

Tutoriel React : créer une application de calculatrice à partir de zéro


Dans ce didacticiel, nous allons créer une application React Calculator. Vous apprendrez à créer une structure filaire, à concevoir une mise en page, à créer des composants, à mettre à jour les états et à formater la sortie.

Pour vous inspirer, voici un lien vers le projet déployé nous allons construire.

Aussi, voici le code sourcejuste pour référence si vous avez besoin d’aide à n’importe quelle étape du projet.

Planification

Puisque nous allons créer une application de calculatrice, choisissons une portée qui n’est pas trop compliquée pour l’apprentissage, mais aussi pas trop basique pour couvrir différents aspects de la création d’une application.

Les fonctionnalités que nous allons implémenter incluent :

  • additionner, soustraire, multiplier, diviser
  • prendre en charge les valeurs décimales
  • calculer des pourcentages
  • inverser les valeurs
  • fonctionnalité de réinitialisation
  • formater des nombres plus grands
  • redimensionner la sortie en fonction de la longueur

Pour commencer, nous allons dessiner un wireframe de base pour afficher nos idées. Pour cela, vous pouvez utiliser des outils gratuits comme Figma ou alors Diagrammes.net.

Filaire

Notez que, dans cette phase, il n’est pas si important de penser aux couleurs et au style. Le plus important est que vous puissiez structurer la mise en page et identifier les composants impliqués.

Couleurs de conception

Une fois que nous avons traité la mise en page et les composants, tout ce qu’il reste à faire pour terminer la conception est de choisir une belle palette de couleurs.

Vous trouverez ci-dessous quelques directives pour rendre l’application attrayante :

  • l’emballage doit contraster avec le fond
  • les valeurs de l’écran et des boutons doivent être faciles à lire
  • le bouton égal doit être d’une couleur différente, pour donner un peu d’accent

Sur la base des critères ci-dessus, nous utiliserons la palette de couleurs ci-dessous.

Schéma de couleur

Configuration du projet

Pour commencer, ouvrez le terminal dans votre dossier de projets et créez un modèle passe-partout à l’aide du créer-réagir-app. Pour ce faire, exécutez la commande :

npx create-react-app calculator

C’est le moyen le plus rapide et le plus simple de configurer une application React entièrement fonctionnelle sans aucune configuration. Tout ce que vous devez faire après cela est exécuté cd calculator pour passer au dossier de projet nouvellement créé et npm start pour démarrer votre application dans le navigateur.

Affichage du navigateur

Comme vous pouvez le voir, il est livré avec un passe-partout par défaut, donc nous allons ensuite faire un peu de nettoyage dans l’arborescence des dossiers du projet.

Trouvez le src dossier, où la logique de votre application vivra, et supprimez tout sauf App.js pour créer votre application, index.css pour styliser votre application, et index.js pour rendre votre application dans le DOM.

Arborescence du projet

Créer des composants

Comme nous avons déjà fait du wireframing, nous connaissons déjà les principaux éléments constitutifs de l’application. Ce sont Wrapper, Screen, ButtonBoxet Button.

Créez d’abord un components dossier à l’intérieur du src dossier. Nous allons ensuite créer un fichier séparé .js fichier et .css fichier pour chaque composant.

Si vous ne souhaitez pas créer ces dossiers et fichiers manuellement, vous pouvez utiliser la ligne suivante pour configurer rapidement les éléments :

cd src && mkdir components && cd components && touch Wrapper.js Wrapper.css Screen.js Screen.css ButtonBox.js ButtonBox.css Button.js Button.css

Emballage

Le Wrapper composant sera le cadre, tenant tous les composants enfants en place. Cela nous permettra également de centrer l’ensemble de l’application par la suite.

Wrapper.js

import "./Wrapper.css";

const Wrapper = ({ children }) => {
  return <div className="wrapper">{children}</div>;
};

export default Wrapper;

Wrapper.css

.wrapper {
  width: 340px;
  height: 540px;
  padding: 10px;
  border-radius: 10px;
  background-color: #485461;
  background-image: linear-gradient(315deg, #485461 0%, #28313b 74%);
}

Filtrer

Le Screen composant sera l’enfant de la section supérieure du Wrapper composant, et son but sera d’afficher les valeurs calculées.

Dans la liste des fonctionnalités, nous avons inclus le redimensionnement de la sortie d’affichage sur la longueur, ce qui signifie que les valeurs plus longues doivent réduire leur taille. Nous allons utiliser une petite bibliothèque (gzip de 3,4 Ko) appelée réagir-textfit pour ça.

Pour l’installer, lancez npm i react-textfit puis importez-le et utilisez-le comme indiqué ci-dessous.

Screen.js

import { Textfit } from "react-textfit";
import "./Screen.css";

const Screen = ({ value }) => {
  return (
    <Textfit className="screen" mode="single" max={70}>
      {value}
    </Textfit>
  );
};

export default Screen;

Écran.css

.screen {
  height: 100px;
  width: 100%;
  margin-bottom: 10px;
  padding: 0 10px;
  background-color: #4357692d;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  color: white;
  font-weight: bold;
  box-sizing: border-box;
}

ButtonBox

Le ButtonBox composant, à l’instar du Wrapper composant, sera le cadre pour les enfants – seulement cette fois pour le Button Composants.

ButtonBox.js

import "./ButtonBox.css";

const ButtonBox = ({ children }) => {
  return <div className="buttonBox">{children}</div>;
};

export default ButtonBox;

ButtonBox.css

.buttonBox {
  width: 100%;
  height: calc(100% - 110px);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(5, 1fr);
  grid-gap: 10px;
}

Bouton

Le Button fournira l’interactivité de l’application. Chaque composant aura le value et onClick accessoires.

Dans la feuille de style, nous inclurons également les styles pour le equal bouton. Nous utiliserons Button props pour accéder à la classe plus tard.

Bouton.js

import "./Button.css";

const Button = ({ className, value, onClick }) => {
  return (
    <button className={className} onClick={onClick}>
      {value}
    </button>
  );
};

export default Button;

Bouton.css

button {
  border: none;
  background-color: rgb(80, 60, 209);
  font-size: 24px;
  color: rgb(255, 255, 255);
  font-weight: bold;
  cursor: pointer;
  border-radius: 10px;
  outline: none;
}

button:hover {
  background-color: rgb(61, 43, 184);
}

.equals {
  grid-column: 3 / 5;
  background-color: rgb(243, 61, 29);
}

.equals:hover {
  background-color: rgb(228, 39, 15);
}

Éléments de rendu

Le fichier de base pour le rendu dans les applications React est index.js. Avant d’aller plus loin, assurez-vous que votre index.js se présente comme suit :

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
import "./index.css";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

Aussi, vérifions index.css et assurez-vous que nous réinitialisons les valeurs par défaut pour padding et marginchoisissez une bonne police (comme Montserrat dans ce cas) et définissez les règles appropriées pour centrer l’application dans la fenêtre :

@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap");

* {
  margin: 0;
  padding: 0;
  font-family: "Montserrat", sans-serif;
}

body {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #fbb034;
  background-image: linear-gradient(315deg, #fbb034 0%, #ffdd00 74%);
}

Enfin, ouvrons le fichier principal App.jset importez tous les composants que nous avons créés précédemment :

import Wrapper from "./components/Wrapper";
import Screen from "./components/Screen";
import ButtonBox from "./components/ButtonBox";
import Button from "./components/Button";

const App = () => {
  return (
    <Wrapper>
      <Screen value="0" />
      <ButtonBox>
        <Button
          className=""
          value="0"
          onClick={() => {
            console.log("Button clicked!");
          }}
        />
      </ButtonBox>
    </Wrapper>
  );
};

export default App;

Dans l’exemple ci-dessus, nous n’avons rendu qu’un seul Button composant.

Créons une représentation matricielle des données dans le wireframe, afin que nous puissions mapper et rendre tous les boutons dans le ButtonBox:

import Wrapper from "./components/Wrapper";
import Screen from "./components/Screen";
import ButtonBox from "./components/ButtonBox";
import Button from "./components/Button";

const btnValues = [
  ["C", "+-", "%", "https://www.sitepoint.com/"],
  [7, 8, 9, "X"],
  [4, 5, 6, "-"],
  [1, 2, 3, "+"],
  [0, ".", "="],
];

const App = () => {
  return (
    <Wrapper>
      <Screen value=0 />
      <ButtonBox>
        {
          btnValues.flat().map((btn, i) => {
            return (
              <Button
                key={i}
                className={btn === "=" ? "equals" : ""}
                value={btn}
                onClick={() => {
                  console.log(`${btn} clicked!`);
                }}
              />
            );
          })
        }
      </ButtonBox>
    </Wrapper>
  );
};

Vérifiez votre terminal et assurez-vous que votre application React est toujours en cours d’exécution. Sinon, lancez-vous npm start pour le recommencer.

Ouvrez votre navigateur. Si vous avez suivi, votre résultat actuel devrait ressembler à ceci :

Conception d'applications

Si vous le souhaitez, vous pouvez également ouvrir les outils de développement du navigateur et tester les valeurs du journal pour chaque bouton enfoncé.

Console.log

Définir les états

Ensuite, nous allons déclarer les variables d’état en utilisant React useState crochet.

Plus précisément, il y aura trois états : numla valeur saisie ; signle signe sélectionné : et resla valeur calculée.

Afin d’utiliser le useState crochet, nous devons d’abord l’importer dans App.js:

import React, { useState } from "react";

Dans le App fonction, nous utiliserons un objet pour définir tous les états à la fois :

import React, { useState } from "react";



const App = () => {
  let [calc, setCalc] = useState({
    sign: "",
    num: 0,
    res: 0,
  });

  return (
    
  );
};

Fonctionnalité

Notre application a l’air bien, mais il n’y a aucune fonctionnalité. Actuellement, il ne peut sortir que les valeurs des boutons dans la console du navigateur. Réparons ça !

Nous allons commencer par le Screen composant. Définissez la logique conditionnelle suivante sur value prop, il affiche donc le nombre saisi (si le nombre est saisi) ou le résultat calculé (si le bouton égal est enfoncé).

Pour cela, nous utiliserons le JS intégré opérateur ternairequi est essentiellement un raccourci pour le if déclaration, en prenant une expression et en renvoyant une valeur après ? si l’expression est vraie, ou après : si l’expression est fausse :

<Screen value={calc.num ? calc.num : calc.res} />

Modifions maintenant le Button composant afin qu’il puisse détecter différents types de boutons et exécuter la fonction attribuée une fois que le bouton spécifique est enfoncé. Utilisez le code ci-dessous :

import React, { useState } from "react";



const App = () => {
  

  return (
    <Wrapper>
      <Screen value={calc.num ? calc.num : calc.res} />
      <ButtonBox>
        {btnValues.flat().map((btn, i) => {
          return (
            <Button
              key={i}
              className={btn === "=" ? "equals" : ""}
              value={btn}
              onClick={
                btn === "C"
                  ? resetClickHandler
                  : btn === "+-"
                  ? invertClickHandler
                  : btn === "%"
                  ? percentClickHandler
                  : btn === "="
                  ? equalsClickHandler
                  : btn === "https://www.sitepoint.com/" || btn === "X" || btn === "-" || btn === "+"
                  ? signClickHandler
                  : btn === "."
                  ? commaClickHandler
                  : numClickHandler
              }
            />
          );
        })}
      </ButtonBox>
    </Wrapper>
  );
};

Nous sommes maintenant prêts à créer toutes les fonctions nécessaires.

numClickHandler

Le numClickHandler La fonction se déclenche uniquement si l’une des touches numériques (0 à 9) est enfoncée. Il obtient alors la valeur de Button et ajoute cela au courant num valeur.

Il s’assurera également que :

  • aucun nombre entier ne commence par zéro
  • il n’y a pas de multiples zéros avant la virgule
  • le format sera « 0 ». si « . » est pressé en premier
  • les nombres sont entrés jusqu’à 16 nombres entiers
import React, { useState } from "react";



const App = () => {
  

  const numClickHandler = (e) => {
    e.preventDefault();
    const value = e.target.innerHTML;

    if (calc.num.length < 16) {
      setCalc({
        ...calc,
        num:
          calc.num === 0 && value === "0"
            ? "0"
            : calc.num % 1 === 0
            ? Number(calc.num + value)
            : calc.num + value,
        res: !calc.sign ? 0 : calc.res,
      });
    }
  };

  return (
    
  );
};

virguleClicHandler

Le commaClickHandler La fonction n’est déclenchée que si le point décimal (.) est enfoncé. Il ajoute le point décimal au courant num valeur, ce qui en fait un nombre décimal.

Il s’assurera également qu’aucun point décimal multiple n’est possible.

Remarque : J’ai appelé la fonction de gestion « commaClickHandler » car, dans de nombreuses régions du monde, les entiers et les décimales sont séparés par une virgule et non par un point décimal.



const commaClickHandler = (e) => {
  e.preventDefault();
  const value = e.target.innerHTML;

  setCalc({
    ...calc,
    num: !calc.num.toString().includes(".") ? calc.num + value : calc.num,
  });
};

signClickHandler

Le signClickHandler la fonction est déclenchée lorsque l’utilisateur appuie sur l’un ou l’autre +, , * ou alors /. La valeur particulière est alors définie comme un courant sign valeur dans le calc objet.

Il s’assurera également qu’il n’y a aucun effet sur les appels répétés :



const signClickHandler = (e) => {
  e.preventDefault();
  const value = e.target.innerHTML;

  setCalc({
    ...calc,
    sign: value,
    res: !calc.res && calc.num ? calc.num : calc.res,
    num: 0,
  });
};

equalsClickHandler

Le equalsClickHandler calcule le résultat lorsque le bouton égal (=) est enfoncé. Le calcul est basé sur le courant num et res valeur, ainsi que la sign sélectionné (voir math une fonction).

La valeur renvoyée est alors définie comme la nouvelle res pour les calculs ultérieurs.

Il s’assurera également que :

  • il n’y a aucun effet sur les appels répétés
  • les utilisateurs ne peuvent pas diviser par 0


const equalsClickHandler = () => {
  if (calc.sign && calc.num) {
    const math = (a, b, sign) =>
      sign === "+"
        ? a + b
        : sign === "-"
        ? a - b
        : sign === "X"
        ? a * b
        : a / b;

    setCalc({
      ...calc,
      res:
        calc.num === "0" && calc.sign === "https://www.sitepoint.com/"
          ? "Can't divide with 0"
          : math(Number(calc.res), Number(calc.num), calc.sign),
      sign: "",
      num: 0,
    });
  }
};

invertClickHandler

Le invertClickHandler la fonction vérifie d’abord s’il y a une valeur entrée (num) ou valeur calculée (res) puis les inverse en multipliant par -1 :



const invertClickHandler = () => {
  setCalc({
    ...calc,
    num: calc.num ? calc.num * -1 : 0,
    res: calc.res ? calc.res * -1 : 0,
    sign: "",
  });
};

pourcentageClicHandler

Le percentClickHandler la fonction vérifie s’il y a une valeur entrée (num) ou valeur calculée (res), puis calcule le pourcentage à l’aide de la fonction intégrée Math.pow fonction, qui renvoie la base à la puissance exposant :



const percentClickHandler = () => {
  let num = calc.num ? parseFloat(calc.num) : 0;
  let res = calc.res ? parseFloat(calc.res) : 0;

  setCalc({
    ...calc,
    num: (num /= Math.pow(100, 1)),
    res: (res /= Math.pow(100, 1)),
    sign: "",
  });
};

resetClickHandler

Le resetClickHandler la fonction utilise par défaut toutes les valeurs initiales de calcrenvoyant le calc état tel qu’il était lors du premier rendu de l’application Calculatrice :



const resetClickHandler = () => {
  setCalc({
    ...calc,
    sign: "",
    num: 0,
    res: 0,
  });
};

Formatage d’entrée

Une dernière chose pour compléter la liste des fonctionnalités dans l’intro serait d’implémenter le formatage des valeurs. Pour cela, nous pourrions utiliser une chaîne Regex modifiée publiée par Émissaire:

const toLocaleString = (num) =>
  String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 ");

Essentiellement, ce qu’il fait est de prendre un nombre, de le formater au format chaîne et de créer les séparateurs d’espace pour le millier.

Si nous inversons le processus et voulons traiter la chaîne de nombres, nous devons d’abord supprimer les espaces, afin que nous puissions ensuite la convertir en nombre. Pour cela, vous pouvez utiliser cette fonction :

const removeSpaces = (num) => num.toString().replace(/\s/g, "");

Voici le code où vous devez inclure les deux fonctions :

import React, { useState } from "react";



const toLocaleString = (num) =>
  String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 ");

const removeSpaces = (num) => num.toString().replace(/\s/g, "");

const App = () => {
  

  return (
    
  );
};

Consultez la section suivante avec le code complet sur la façon d’ajouter toLocaleString et removeSpaces aux fonctions de gestionnaire pour le Button composant.

Mettre tous ensemble

Si vous avez suivi, l’ensemble App.js le code devrait ressembler à ceci :

import React, { useState } from "react";

import Wrapper from "./components/Wrapper";
import Screen from "./components/Screen";
import ButtonBox from "./components/ButtonBox";
import Button from "./components/Button";

const btnValues = [
  ["C", "+-", "%", "https://www.sitepoint.com/"],
  [7, 8, 9, "X"],
  [4, 5, 6, "-"],
  [1, 2, 3, "+"],
  [0, ".", "="],
];

const toLocaleString = (num) =>
  String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 ");

const removeSpaces = (num) => num.toString().replace(/\s/g, "");

const App = () => {
  let [calc, setCalc] = useState({
    sign: "",
    num: 0,
    res: 0,
  });

  const numClickHandler = (e) => {
    e.preventDefault();
    const value = e.target.innerHTML;

    if (removeSpaces(calc.num).length < 16) {
      setCalc({
        ...calc,
        num:
          calc.num === 0 && value === "0"
            ? "0"
            : removeSpaces(calc.num) % 1 === 0
            ? toLocaleString(Number(removeSpaces(calc.num + value)))
            : toLocaleString(calc.num + value),
        res: !calc.sign ? 0 : calc.res,
      });
    }
  };

  const commaClickHandler = (e) => {
    e.preventDefault();
    const value = e.target.innerHTML;

    setCalc({
      ...calc,
      num: !calc.num.toString().includes(".") ? calc.num + value : calc.num,
    });
  };

  const signClickHandler = (e) => {
    e.preventDefault();
    const value = e.target.innerHTML;

    setCalc({
      ...calc,
      sign: value,
      res: !calc.res && calc.num ? calc.num : calc.res,
      num: 0,
    });
  };

  const equalsClickHandler = () => {
    if (calc.sign && calc.num) {
      const math = (a, b, sign) =>
        sign === "+"
          ? a + b
          : sign === "-"
          ? a - b
          : sign === "X"
          ? a * b
          : a / b;

      setCalc({
        ...calc,
        res:
          calc.num === "0" && calc.sign === "https://www.sitepoint.com/"
            ? "Can't divide with 0"
            : toLocaleString(
                math(
                  Number(removeSpaces(calc.res)),
                  Number(removeSpaces(calc.num)),
                  calc.sign
                )
              ),
        sign: "",
        num: 0,
      });
    }
  };

  const invertClickHandler = () => {
    setCalc({
      ...calc,
      num: calc.num ? toLocaleString(removeSpaces(calc.num) * -1) : 0,
      res: calc.res ? toLocaleString(removeSpaces(calc.res) * -1) : 0,
      sign: "",
    });
  };

  const percentClickHandler = () => {
    let num = calc.num ? parseFloat(removeSpaces(calc.num)) : 0;
    let res = calc.res ? parseFloat(removeSpaces(calc.res)) : 0;

    setCalc({
      ...calc,
      num: (num /= Math.pow(100, 1)),
      res: (res /= Math.pow(100, 1)),
      sign: "",
    });
  };

  const resetClickHandler = () => {
    setCalc({
      ...calc,
      sign: "",
      num: 0,
      res: 0,
    });
  };

  return (
    <Wrapper>
      <Screen value={calc.num ? calc.num : calc.res} />
      <ButtonBox>
        {btnValues.flat().map((btn, i) => {
          return (
            <Button
              key={i}
              className={btn === "=" ? "equals" : ""}
              value={btn}
              onClick={
                btn === "C"
                  ? resetClickHandler
                  : btn === "+-"
                  ? invertClickHandler
                  : btn === "%"
                  ? percentClickHandler
                  : btn === "="
                  ? equalsClickHandler
                  : btn === "https://www.sitepoint.com/" || btn === "X" || btn === "-" || btn === "+"
                  ? signClickHandler
                  : btn === "."
                  ? commaClickHandler
                  : numClickHandler
              }
            />
          );
        })}
      </ButtonBox>
    </Wrapper>
  );
};

export default App;

Remarques finales

Toutes nos félicitations! Vous avez créé une application entièrement fonctionnelle et stylisée. J’espère que vous avez appris une chose ou deux au cours du processus !

Démo

D’autres idées à explorer seraient d’ajouter des fonctionnalités scientifiques ou d’implémenter la mémoire avec la liste des calculs précédents.

Si vous avez des rapports de problèmes ou des demandes de fonctionnalités, n’hésitez pas à les laisser dans le Dépôt GitHub. Si vous aimez le projet, n’hésitez pas à le mettre en vedette.




Source link