ライフサイクルメソッドをフックに反応させる

Dec 16 2020

記事付きの簡単なブログがあります。そして、クラスを機能コンポーネントとフックに書き直したいと思います。これで、編集/追加フォームを使用して、ページのライフサイクルメソッドでこのロジックを取得しました。正常に機能します。

componentDidUpdate(prevProps, prevState) {
 if (this.props.match.params.id !== prevProps.match.params.id) {
    if (prevProps.match.params.id) {
        this.props.onUnload();
      }

      this.id = this.props.match.params.id;
        if (this.id) {
            return this.props.onLoad(userService.articles.get(this.id));
        }
        this.props.onLoad(null);
   }
   this.isLoading = false;
}

componentDidMount() {
  if (this.id) {
    this.isLoading = true;
    return this.props.onLoad(userService.articles.get(this.id));
  }
  this.isLoading = false;
  this.props.onLoad(null);
}
   
componentWillUnmount() {
   this.props.onUnload();
}
   
shouldComponentUpdate(newProps, newState) {
   if (this.props.match.params.id !== newProps.match.params.id) {
      this.isLoading = true;
   }
   return true;
}

私はそれをすべてそのようなフックに書き直しました:

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);
  }, []);

  useEffect(()=> {
      prevId.current = id;
      }, [id]
  );

  //componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  });

  //componentWillUnmount
  useEffect(() => {
     return props.onUnload();
  }, []);

  1. 「再レンダリングが多すぎます」というエラーが発生しました。codesandboxで完全なコード:codesandbox

奇妙ですが、ローカルホストでは「再レンダリングが多すぎます」というエラーはありません。

  1. クラス「shouldComponentUpdate」メソッドをフックに書き換える方法がわかりません。「メモ」を試しましたが、この場合の書き方がわかりません。

  2. そしてとにかく、それはすべて機能しないので、私は何かが欠けています-それはフォームフィールドを適切に更新していません。

あなたがreactフックについての十分な知識を持っているなら、助けてください、またはいくつかのアドバイスを与えてください-それを修正する方法は?

回答

3 Mhmdrz_A Dec 18 2020 at 21:24

依存関係のない効果が原因"Too many re-renders."です:レンダリングのたびに実行され、次にsetIsLoadingstate(loading)を更新するために呼び出されます。これにより、コンポーネントが再レンダリングされ、effect再度実行され、が再度setState呼び出さeffectれます。

//componentDidUpdate
  useEffect(() => {
    //const id = props.match.params.id;
    if (id !== prevId.current) {
      if (prevId.current) {
        props.onUnload();
      }
      if (id) {
        return props.onLoad(userService.articles.get(id));
      }
      props.onLoad(null);
    }
    setIsloading(false);
  })

問題を修正するには、を削除するか、依存関係としてsetIsLoading追加IsLoadingします。

//componentDidUpdate
  useEffect(() => {
    ...
    setIsloading(false);
  },[isLoading]);

2つのマウントエフェクトを次のように1つにマージすることもできます(あなたも機能していますが、これはスタイル的に好ましいと思います):

//componentDidMount
  useEffect(() => {
    if (id) {
      setIsloading(true);
      return props.onLoad(userService.articles.get(id));
    }
    setIsloading(false);
    props.onLoad(null);

    //componentWillUnmount
    return function () {
      props.onUnload()
    }
  }, []);

2番目の箇条書きの場合:コンポーネントの書き換えについてshouldComponentUpdate; 最初にshouldComponentUpdate、あなたは常にを返しtrueloading状態をトリガーするためにのみそれを使用しているので、あなたの既存のものは合理的に聞こえないことを指摘しなければなりません。また、React.memo(shouldComponentUpdateクラスコンポーネントと同等)で作成することはできません。したがって、loading状態を判断するために必要なのは、すべての小道具の変更で実行する必要があるだけです。そのために、次のpropsように依存関係としてエフェクトを使用できます。

//manually keeping old props id
const prevPropsId = useRef();

useEffect(() => {
   if (prevPropsId.current !== props.match.params.id) {
      setIsLoading(true);
   }
   prevPropsId.current = props.match.params.id;
 }, [props.match.params.id]);
// this hook only run if `props.match.params.id` change 

私の認識に基づいて、これは仕事をします(そもそもなぜこのようにロジックを書くのか理解するのに苦労しているにもかかわらず)そしてそれがうまくいかない場合はあなたのニーズに合うようにロジックを少し調整するかもしれません、あなたは小道具の変更効果がどのように機能するかを考えます。また、あなたは多くの処理する必要がありますtypeError場合idparamsまたはmatch存在せず、任意の連鎖はここに便利なツールをすることができます- >props.match?.params?.id

2 deckele Dec 23 2020 at 23:26

古いライフサイクルフックを見逃した場合は、いつでもカスタムフックとして再作成できます。

function useComponentDidMount(effect) {
  useEffect(() => {
    effect();
  }, []);
}
function useComponentWillUnmount(effect) {
  useEffect(() => {
    return effect;
  }, []);
}
function useComponentDidUpdate(effect, dependencies) {
  const hasMountedRef = useRef(false);
  const prevDependenciesRef = useRef(null)
  useEffect(() => {
    // don't run on first render, only on subsequent changes
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      prevDependenciesRef.current = dependencies;
      return;
    }
    // run effect with previous dependencies, like in componentDidUpdate!
    effect(...prevDependenciesRef.current || []);
    prevDependenciesRef.current = dependencies;
  }, dependencies)
}

使用法(リファクタリングされた例):

  //componentDidMount
  useComponentDidMount(() => {
    if (id) {
      setIsloading(true);
      props.onLoad(userService.articles.get(id));
      return;
    }
    setIsloading(false);
    props.onLoad(null);
  });

  //componentDidUpdate
  useComponentDidUpdate((prevId) => {
    if (prevId) {
      props.onUnload();
    }
    if (id) {
      props.onLoad(userService.articles.get(id));
      return;
    }
    props.onLoad(null);
    setIsloading(false);
  }, [id]);

  //componentWillUnmount
  useComponentWillUnmount(() => {
     props.onUnload();
  });