Comment accélérer le rendu de votre application React

Nov 27 2022
La livraison rapide d'applications est facile, la livraison d'applications rapides est tellement plus difficile. Voici quelques conseils pour que votre application React rende vos composants plus rapidement.

La livraison rapide d'applications est facile, la livraison d'applications rapides est tellement plus difficile. Voici quelques conseils pour que votre application React rende vos composants plus rapidement.

Alors que nous essayons de livrer nos applications plus rapidement, nous avons souvent oublié de nous concentrer sur les meilleures pratiques. Après un certain temps, nous créons des composants si complexes une fois que vous avez tapé une clé, les endroits n'ont pas besoin d'être rendus. Voici la liste de ce qu'il faut faire et de ce qu'il ne faut pas faire.

Utiliser React.Memo

Nous ne voulons généralement pas que notre composant soit rendu à chaque fois que son parent est rendu. Nous pouvons dire React pour ne pas rendre nos composants à moins que cela ne soit nécessaire.

// Wrong usage
// Without React.memo this component will get rendered each time 
// something causes its parent to render
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

export default YourHeavyComponent
// Good usage
// This component will not render unless name, title or onClick changes
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

export default React.memo(YourHeavyComponent)
// This component will not render unless title changes
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

// This usage might better for some stations, use this second argument
// to specify which props  will cause render
export default React.memo(YourHeavyComponent, (prevProps, nextProps) => {
    return prevProps.title === nextProps.title
})

Ne pas avoir plus de 4 accessoires par composant

Avoir de nombreux accessoires est toujours problématique, chaque accessoire signifie une variable de plus à prendre en compte en termes de performances et cela rendra votre code plus difficile à lire et à maintenir. Au lieu de créer des composants avec 10 accessoires, créez 3 petits morceaux avec 3 à 4 accessoires.

// this component has too many filters
// it's not a good idea to add filters inside this component
// instead we need to create a seperate component to add filter inputs
// and we can also remove title from here
const List = (title, items, sortBy, keys, filters, onFilterChange, children, pagination) => {
    // Render  something really heavy like whole page here
}
// here userslist wont rendered unless we get a new list
// pagination payload won't cause rendering in filters
const [payload, setPayload] = useState()
const [list, setList] = useState([])
return <>
    <Filters onFilterChange={setPayload} filters={payload.filters} />
    <UserList items={list}/>
    <Pagination onPaginationChange={setPayload} pagination={payload.pagination} />
</>

hook lorsque vous avez des variables qui seront calculées après un changement. C'est utile car si votre calcul prend du temps ou après le calcul, vous obtiendrez différentes références de mémoire pour le tableau ou les objets.

N'oubliez pas que lorsque vos variables sont modifiées et que vous les transmettez aux enfants, le composant enfants sera restitué.

// here we use useMemo for purely caching purposes, if we don't use useMemo
// each time this components rendered the calculation will have to re-run 
const heavyCalculated = useMemo(() => doSomeHeavyCalculation(variable1), [variable])
// an example of wrong usage
// each time this components get rendered this
// styles variable will point to different memory address
// and this will cause rerender of YourHeavyComponent
const styles = {
  container:{
    marginLeft: left,
    marginRight: right,
    marginTop: top,
    marginBottom: bottom,
  }
}
// correct usage of useMemo
// this will cache your value and its memory point won't change
// so even if this components gets rendered your YourHeavyComponent won't be rendered again
const styles = useMemo(() => ({
  container:{
    marginLeft: left,
    marginRight: right,
    marginTop: top,
    marginBottom: bottom,
  }
}), [left, right, top, bottom])

Vous devez utiliser le crochet useCallback lorsque vous passerez votre fonction à un composant enfant. N'oubliez pas que les fonctions pointeront vers un point mémoire différent chaque fois que vos composants seront rendus. Nous devons conserver le rappel précédent en mémoire afin qu'il ne change pas chaque fois que nous faisons quelque chose dans le composant principal et que nous provoquons un rendu dans les enfants.

// wrong usage
// each time App components get rendered for some reason
// onClickCallback will be recreated and it will point to different memory address
// so it will cause YourHeavyComponent to re render
const onClickCallback = () => {
  // do some stuff
}

return <div>
  <YourHeavyComponent onClick={onClickCallback} />
</div>

// good usage of onClickCallback
// each time App components if your variable1 don't change
// onClickCallback will point to same memory point 
// so UourHeavyCallback won't render.
const onClickCallback = () => {
  // do some stuff
}

return <div>
  <YourHeavyComponent onClick={onClickCallback} />
</div>

Nous nous sommes habitués à utiliser ce crochet pour le transmettre aux enfants et à utiliser leurs instances, mais ce n'est pas seulement un cas d'utilisation disponible pour cela. Nous pouvons l'utiliser comme une sorte de mécanisme de mise en cache et d'état sans provoquer de rendu.

// in this example we look for useRef value instead of setting
// sent reference is being used for checking if we send this message before or not
// this could hold in the state but if we put into state it will cause render
// it into state, because we don't want to re-render after we set sent.current
const sent = useRef(false)
const postMessage = useCallback(() => {
  if (!sent.current) {
    // make your api call here
    sent.current = true
  }
}, [])

N'oubliez jamais cela, chaque fois que vos composants sont rendus, les variables à l'intérieur de ce composant pointent vers l'adresse mémoire différente, quand il s'agit des types primitifs comme la chaîne et l'entier cela ne causera aucun problème mais si vous travaillez avec des tableaux, des objets et des fonctions (parce que chaque fonction sont en fait des objets derrière le capot). Voyons quelques exemples

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    return <YourHeavyComponent style={{
        marginLeft: 10,
        marginRight: 10
    }} />
}
const style = {
    marginLeft: 10,
    marginRight: 10
}

// We move styles outside of component
// this way style will point the same memory address regardless of 
// how many tames InlineTest gets rendered

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    return <YourHeavyComponent style={style} />
}

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    // because onClick will be assigned to a new function each time
    return <YourHeavyComponent onClick={() => {
        console.log('clicked');
    }} />;
}
import {useCallback} from "react";

const InlineTest = () => {
    // the usage below is correct way to do it
    // using useCallback will make sure onClick function
    // is the same between renders and it won't cause render in below component
    
    const onClick = useCallback(() => {
        console.log('clicked');
    }, [])
    return <YourHeavyComponent onClick={onClick} />;
}

Vous pourriez penser que la gestion des formulaires en réaction est facile, mais laissez-moi vous garantir que ce n'est pas le cas. Lorsque vos formulaires deviennent compliqués et que vous devez effectuer des validations, etc., vous provoquerez trop de rendus. C'est pourquoi il est important d'utiliser une bonne bibliothèque. Ces deux dernières années, j'ai essayé de nombreuses bibliothèques différentes, mais il n'y en a qu'une qui mérite d'être mentionnée ici; forme de crochet de réaction.

import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const onSubmit = data => console.log(data);


  return (
    /* "handleSubmit" will validate your inputs before invoking "onSubmit" */
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* register your input into the hook by invoking the "register" function */}
      <input defaultValue="test" {...register("example")} />
      
      {/* include validation with required or other standard HTML validation rules */}
      <input {...register("exampleRequired", { required: true })} />
      {/* errors will return when field validation fails  */}
      {errors.exampleRequired && <span>This field is required</span>}
      
      <input type="submit" />
    </form>
  );
}