36
votes

componentWillReceiveProps, componentDidUpdate pour React Hook

Je rencontre deux défis:

  • Même si, selon la directive React, l' état dérivé est déconseillé, mais certains cas extrêmes en ont encore besoin.
    En termes de composant fonctionnel avec React Hook, quelle est l'implémentation équivalente avec React Hook, si j'ai besoin d'un état dérivé? qui dans le composant de classe, sera mis à jour dans componentWillReceiveProps sur chaque rendu parent

voir ci-dessous l'exemple de code:

class App extends Component {


  /*What is the equivalent implementation when functional component with React Hook is used here */
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.groupName !== this.props.groupName) {
      console.log('Let'
        's say, I do want to do some task here only when groupName differs');
    } else if (prevProps.companyName !== this.props.companyName) {
      console.log('Let'
        's say,I do want to do some different task here only when companyName differs');
    }

  }


  render() {
    /*for simplicity, render code is ignored*/
    return null;
  }
}

export default App;
  • Quant à componentDidUpdate, componentDidUpdate a son équivalent lorsque React Hook est utilisé, vous devez l'utiliser comme,

      React.useEffect(() => {
        return () => {
    
         };
      }, [parentProp]);
    

    le deuxième paramètre pour useEffect s'assure que le code est exécuté uniquement lorsque le prop change, mais que faire si je veux faire des tâches respectives en fonction de plusieurs changements d'accessoires respectifs ? comment le faire avec useEffect ?

voir ci-dessous l'exemple de code:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.count > 100 ? 100 : props.count,
    }

  }

  /*What is the equivalent implementation when React Hook is used here componentWillReceiveProps*/
  componentWillReceiveProps(nextProps) {
    if (nextProps.count !== this.props.count) {
      this.setState({
        count: nextProps.count > 100 ? 100 : nextProps.count
      });
    }
  }

  render() {
    return ( <
      div > {
        this.state.count
      } <
      /div>
    );
  }
}

export default App;


0 commentaires

8 Réponses :


7
votes

Vous pouvez utiliser le hook useMemo pour stocker un calcul et placer props.count dans le tableau donné comme second argument pour recalculer la valeur lorsqu'elle change.

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>
const { useState, useEffect } = React;

function App() {
  const [groupName, setGroupName] = useState('foo');
  const [companyName, setCompanyName] = useState('foo');

  useEffect(() => {
    setTimeout(() => {
      setGroupName('bar');
    }, 1000);
    setTimeout(() => {
      setCompanyName('bar');
    }, 2000);
  }, []);

  return <DisplayGroupCompany groupName={groupName} companyName={companyName} />;
}

function DisplayGroupCompany(props) {
  useEffect(() => {
    console.log("Let's say, I do want to do some task here only when groupName differs");
  }, [props.groupName])
  useEffect(() => {
    console.log("Let's say,I do want to do some different task here only when companyName differs");
  }, [props.companyName])

  return <div> {props.groupName} - {props.companyName} </div>;
}

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

Le moyen le plus simple de créer des effets séparés lorsque des accessoires distincts changent est de créer plusieurs hooks useEffect qui ne sont exécutés que lorsque l'un des accessoires séparés change.

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>
const { useState, useEffect, useMemo } = React;

function App() {
  const [count, setCount] = useState(50);

  useEffect(() => {
    setTimeout(() => {
      setCount(150);
    }, 2000);
  }, []);

  return <DisplayCount count={count} />;
}

function DisplayCount(props) {
  const count = useMemo(() => props.count > 100 ? 100 : props.count, [props.count]);

  return <div> {count} </div>;
}

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


0 commentaires

0
votes

Je sais que votre exemple "d'état dérivé" est intentionnellement simple, mais comme il y a si peu de cas légitimes d'état dérivé, il est difficile de faire une recommandation sur le remplacement sauf au cas par cas car cela dépend de la raison pour laquelle vous utilisent l'état dérivé. Dans l'exemple particulier que vous avez fourni, il n'y avait aucune raison d'utiliser l'état dérivé dans le cas de classe et il n'y a donc toujours aucune raison dans le cas de crochet (la valeur peut simplement être dérivée localement sans la mettre à l'état). Si la valeur dérivée est chère, vous pouvez utiliser useMemo comme Tholle présente. Si ceux-ci ne correspondent pas aux cas les plus réalistes que vous avez en tête, vous devrez présenter un cas plus spécifique qui nécessite vraiment un état dérivé.

En ce qui concerne votre exemple componentDidUpdate , si ce que vous voulez faire pour les différents accessoires est indépendant, alors vous pouvez utiliser des effets séparés pour chacun (c'est-à-dire plusieurs appels useEffect ). Si vous voulez faire exactement ce qui est dans votre exemple (c'est-à-dire ne faire quelque chose pour un changement de companyName si groupName n'a pas également changé comme indiqué par votre else if ), alors vous pouvez utiliser des refs pour des conditions plus sophistiquées. Vous ne devez pas muter la référence pendant le rendu (il y a toujours la possibilité que le rendu soit supprimé / refait une fois que le mode concurrent est pris en charge), donc l'exemple utilise le dernier effet pour mettre à jour les références. Dans mon exemple, j'utilise une référence pour éviter de faire un travail d'effet sur le rendu initial (voir la réponse de Tholle dans cette question connexe ) et pour détecter si groupName changé ou non lors de la décision de travailler ou non en fonction d'un changement de companyName .

<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
const { useState, useEffect, useRef } = React;

const DerivedStateFromProps = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>
      Derived from {count}: {derivedCount}{" "}
    </div>
  );
};
const ComponentDidUpdate = ({ groupName, companyName }) => {
  const initialRender = useRef(true);
  const lastGroupName = useRef(groupName);
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when groupName changes", groupName);
      }
    },
    [groupName]
  );
  useEffect(
    () => {
      if (!initialRender.current) {
        console.log("Do something when companyName changes", companyName);
      }
    },
    [companyName]
  );
  useEffect(
    () => {
      if (!initialRender.current && groupName === lastGroupName.current)
        console.log(
          "Do something when companyName changes only if groupName didn't also change",
          companyName
        );
    },
    [companyName]
  );
  useEffect(
    () => {
      // This effect is last so that these refs can be read accurately in all the other effects.
      initialRender.current = false;
      lastGroupName.current = groupName;
    },
    [groupName]
  );

  return null;
};
function App() {
  const [count, setCount] = useState(98);
  const [groupName, setGroupName] = useState("initial groupName");
  const [companyName, setCompanyName] = useState("initial companyName");
  return (
    <div>
      <div>
        <DerivedStateFromProps count={count} />
        <button onClick={() => setCount(prevCount => prevCount + 1)}>
          Increment Count
        </button>
      </div>
      <div>
        <ComponentDidUpdate groupName={groupName} companyName={companyName} />
        groupName:{" "}
        <input
          type="text"
          value={groupName}
          onChange={event => setGroupName(event.target.value)}
        />
        <br />
        companyName:{" "}
        <input
          type="text"
          value={companyName}
          onChange={event => setCompanyName(event.target.value)}
        />
        <br />
        change both{" "}
        <input
          type="text"
          onChange={event => {
            const suffix = event.target.value;
            setGroupName(prev => prev + suffix);
            setCompanyName(prev => prev + suffix);
          }}
        />
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Modifier l'état dérivé et le composantDidUpdate


0 commentaires

2
votes

Dans votre scénario, vous n'avez pas du tout besoin d'utiliser ou de getDerivedStateFromProps . Il vous suffit de créer une nouvelle variable pour obtenir la nouvelle forme de données. L'utilisation de l'état dans ce scénario provoquera simplement un autre re-rendu qui n'est pas de bonne qualité en termes de performances.

import React, { useState } from 'react';

const App = ({ count }) => {
  const [derivedCounter, setDerivedCounter] = useState(
    count > 100 ? 100 : count
  );

  useEffect(() => {
    setDerivedCounter(count > 100 ? 100 : count);
  }, [count]); // this line will tell react only trigger if count was changed

  return <div>Counter: {derivedCounter}</div>;
};

Démo ici: https://codesandbox.io/embed/qzn8y9y24j?fontsize=14

Vous pouvez en savoir plus sur les différentes façons de résoudre ce type de scénarios sans utiliser getDerivedStateFromProps ici: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Si vous avez vraiment besoin d'utiliser un état séparé, vous pouvez utiliser quelque chose comme ça

import React from 'react';

const App = ({ count }) => {
  const derivedCount = count > 100 ? 100 : count;

  return (
    <div>Counter: {derivedCount}</div>
  );
}

App.propTypes = {
  count: PropTypes.number.isRequired
}


1 commentaires

n'est-il pas vrai que l'accessoire est en lecture seule? et changer d'accessoire rompt également la fonction pure. droite?



25
votes

Le hook useEffect équivalent aux anciens props componentWillReceive peut être fait en utilisant le hook useEffect , en spécifiant simplement le prop que nous voulons écouter pour les changements dans le tableau de dépendances.

C'est à dire:

export default (props) => {

    useEffect( () => {
        console.log('counter updated');
    })

    return <div>Hi {props.counter}</div>
}

Pour componentDidUpdate simplement en omettant le tableau de dépendances, la fonction useEffect sera appelée après chaque nouveau rendu.

C'est à dire:

export default (props) => {

    useEffect( () => {
        console.log('counter updated');
    }, [props.counter])

    return <div>Hi {props.counter}</div>
}


1 commentaires

Après avoir fait quelques recherches approfondies sur ce sujet, je dirais que votre explication de componentWillReceiveProps est erronée. Le problème est donc que le remplacement de componentWillReceiveProps est délicat car il s'agit d'une fonction de pré-rendu. Ce n'est pas le cas avec les hooks de réaction. Vous pouvez utiliser useRef () pour stocker l'accessoire précédent et le comparer à l'actuel dans la fonction useEffect pour essayer de répliquer la fonctionnalité, mais même cela peut ne pas être exactement un remplacement 1: 1.



5
votes

setCount déclenchera un nouveau rendu. L'utilisation de useEffect avec [count] comme tableau de dépendances garantit que le hook n'appellera setCount lorsque la valeur de count change.

C'est ainsi que vous remplacez la logique componentWillReceiveProps que vous auriez autrement écrite dans l'ancien style React basé sur les classes. Je trouve utile le principe « Chaque rendu a ses propres accessoires et états »: si vous voulez déclencher un nouveau rendu uniquement lorsque des accessoires spécifiques changent, vous pouvez avoir plusieurs hooks useEffect .

useEffect(() => {
  count > 100 ? setCount(100) : setCount(count)
}, [count]) 
 
useEffect(() => {
  console.log('groupName has changed');
  // do something with groupName
}, [groupName])
    
useEffect(() => {
  console.log('companyName has changed');
  // do something with companyName
}, [companyName]) 


0 commentaires

1
votes

simplement en utilisant useEffect comme ceci.

useEffect( () => {
    props.actions.fetchSinglePost(props.match.params.id); //> I'm dispatching an action here.
}, [props.comments]) //> and here to watch comments and call the action in case there is any change.


0 commentaires

0
votes

1.) Quelle est l'implémentation équivalente avec React Hook, si j'ai besoin d'un état dérivé?

État dérivé pour Hooks = définir l'état sous condition et directement dans la phase de rendu:

React.useEffect(() => {
  return () => { };
}, [parentProp, secondProp]);

Cet état met à jour sans phase de validation supplémentaire par opposition à useEffect . Le modèle ci-dessus est pris en charge par le mode React Strict (pas d'avertissement):

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js" integrity="sha256-4gJGEx/zXAxofkLPGXiU2IJHqSOmYV33Ru0zw0TeJ30=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.min.js" integrity="sha256-9xBa2Hcuh2S3iew36CzJawq7T9iviOAcAVz3bw8h3Lo=" crossorigin="anonymous"></script>
<div id="root"></div>
const App = () => {
  const [row, setRow] = React.useState(1);

  React.useEffect(() => {
    setTimeout(() => {
      setRow(2);
    }, 3000);
  }, []);

  return (
    <React.StrictMode>
      <Comp row={row} />
    </React.StrictMode>
  );
}

const Comp = ({ row }) => {
  const [isScrollingDown, setIsScrollingDown] = React.useState(false);
  const [prevRow, setPrevRow] = React.useState(null);

  console.log("render, prevRow:", prevRow)

  if (row !== prevRow) {
    console.log("derive state");
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
};

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

Remarque 1 : componentWillReceiveProps est obsolète depuis un certain temps. getDerivedStateFromProps est le successeur des composants de classe en termes d'état dérivé.

Remarque 2 : vérifiez les solutions préférées avant de recourir à l'état dérivé.


2.) Que faire si je veux effectuer des tâches respectives en fonction de plusieurs changements d'accessoires respectifs?

Vous pouvez soit laisser useEffect deps complètement, soit ajouter de préférence un autre accessoire:

constComp = (props) => {
  const [derivedState, setDerivedState] = useState(42);
  if (someCondition) {
    setDerivedState(...);
  }
  // ...
}

0 commentaires

1
votes

Si vous utilisez le hook useMemo sur votre composant et que vous le faites dépendre de tous vos accessoires, il s'exécute avant que tout change à chaque fois que les accessoires changent. useEffect est déclenché après le rendu mis à jour et depuis dépendant de tous les accessoires, il se déclenche après un rendu en fonction de tous les accessoires.

const Component = (...props) => {
   // useState, useReducer if have
   useMemo(() => {
     // componentWillReceiveProps
   },[...props]);
   // ...other logic and stuff
   useEffect(() => {
     // componentDidUpdate
   }, [...props]);
};


1 commentaires

Je suis heureux de savoir qu'il est vraiment utile d'identifier la différence entre ceux-ci. Merci ..!