Métaprogrammation avec React

Mar 09 2023
Le but n'est pas d'entrer dans les détails de ce qu'est la métaprogrammation et de ses techniques, mais seulement de voir un exemple de la façon de l'appliquer dans React. C'est mon premier article sur ce médium fantastique, et j'aimerais écrire un peu sur une technique que nous avons déjà utilisée une fois dans notre vie (d'un programmeur), mais que nous ne connaissons peut-être pas en détail, la métaprogrammation ! Le code ci-dessous est un exemple d'utilisation de la construction Proxy et de l'API Reflect, dans un composant React.
Photo de Lautaro Andreani sur Unsplash

Le but n'est pas d'entrer dans les détails de ce qu'est la métaprogrammation et de ses techniques, mais seulement de voir un exemple de la façon de l'appliquer dans React.

C'est mon premier article sur ce médium fantastique, et j'aimerais écrire un peu sur une technique que nous avons déjà utilisée une fois dans notre vie (venant d'un programmeur), mais que nous ne connaissons peut-être pas en détail, la métaprogrammation !

Le code ci-dessous est un exemple d'utilisation de la construction Proxy et de l'API Reflect , dans un composant React. Le composant MyComponent utilise l'état local de React ( useState ) pour gérer un objet proxyData qui a une propriété « test » initialement vide. Le composant comporte trois boutons qui, lorsqu'ils sont cliqués, modifient la propriété « test » de l' objet proxyData .

import React, { useState } from 'react';

function MyComponent() {
  const [proxyData, setProxyData] = useState({ test: '' });
  const [render, setRender] = useState(false);

  const handleClickMemo = React.useCallback(() => {
    const updatedData = new Proxy(proxyData, {
      set(target, prop, value) {
        const success = Reflect.set(target, prop, value);
        if (success) {
          console.log(`Prop ${prop} modified with value ${value}`);
        }
        return success;
      },
    });
    updatedData.test = crypto.randomUUID();
    setProxyData(updatedData);
  }, []);

  // without useCallback, do new proxy every time
  const handleClick = () => {
    const updatedData = new Proxy(proxyData, {
      set(target, prop, value) {
        const success = Reflect.set(target, prop, value);
        if (success) {
          console.warn(`Prop ${prop} modified with value ${value}`);
        }
        return success;
      },
    });
    updatedData.test = crypto.randomUUID();
    setProxyData(updatedData);
  };

  // case where react doesn't render, but in console,
  // if it is a proxy the object is updated
  // BE CAREFUL when using proxies
  const handleClickNoRender = () => {
    proxyData.test = crypto.randomUUID();
    setProxyData(proxyData);
  };

  return (
    <div>
      <pre>{JSON.stringify(proxyData)}</pre>
      <button onClick={handleClickMemo}>handleClickMemo</button>
      <button onClick={handleClick}>handleClick</button>
      <button onClick={handleClickNoRender}>handleClickNoRender</button>
      <br />
      <button onClick={() => setRender(!render)}>re-render</button>
    </div>
  );
}

export default function App() {
  return <MyComponent />;
}

Le premier bouton, handleClickMemo , utilise la fonction React.useCallback pour créer une fonction mise en cache par React. Ceci est utile pour éviter que la fonction ne soit reconstruite à chaque mise à jour du composant. La fonction crée un nouvel objet Proxy basé sur l' objet proxyData , met à jour la propriété "test" avec une nouvelle valeur générée aléatoirement et définit l'état de proxyData avec l'objet nouvellement créé.

const handleClickMemo = React.useCallback(() => {
    const updatedData = new Proxy(proxyData, {
      set(target, prop, value) {
        const success = Reflect.set(target, prop, value);
        if (success) {
          console.log(`Prop ${prop} modified with value ${value}`);
        }
        return success;
      },
    });
    updatedData.test = crypto.randomUUID();
    setProxyData(updatedData);
  }, []);

// without useCallback, do new proxy every time
  const handleClick = () => {
    const updatedData = new Proxy(proxyData, {
      set(target, prop, value) {
        const success = Reflect.set(target, prop, value);
        if (success) {
          console.warn(`Prop ${prop} modified with value ${value}`);
        }
        return success;
      },
    });
    updatedData.test = crypto.randomUUID();
    setProxyData(updatedData);
  };

// case where react doesn't render, but in console,
  // if it is a proxy the object is updated
  // BE CAREFUL when using proxies
  const handleClickNoRender = () => {
    proxyData.test = crypto.randomUUID();
    setProxyData(proxyData);
  };

Il est important de noter que l'utilisation d'un proxy peut avoir des effets secondaires et entraîner des problèmes de performances s'il n'est pas utilisé correctement. De plus, la mise à jour des états via un objet Proxy peut ne pas être détectée par React, il faut donc faire preuve de prudence lors de l'utilisation de proxys dans un composant React.

Dans ce lien , vous pouvez voir dans la console le comportement de chaque bouton.