Реагировать на методы жизненного цикла на хуки

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. У меня ошибка - «Слишком много повторных рендеров». в codeandbox полный код: codesandbox

Странно, но на localhost нет ошибки "Слишком много повторных рендеров".

  1. Не знаю, что делать с методом моего класса shouldComponentUpdate, как его переписать на хуки. Пробовал «записку», но понятия не имел, как писать в этом случае.

  2. И в любом случае мне чего-то не хватает, потому что все это не сработает - это неправильно обновляет поля формы.

Если вы хорошо разбираетесь в перехватчиках реакции, пожалуйста, помогите или дайте совет - как это исправить?

Ответы

3 Mhmdrz_A Dec 18 2020 at 21:24

Эффект без зависимости вызывает "Too many re-renders.": он запускается после каждого рендеринга, затем он вызывает setIsLoadingupdate state ( 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]);

вы также можете объединить два эффекта монтирования в один, вот так (ваш тоже работает, но я думаю, что это стилистически предпочтительнее):

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

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

для второго пункта: о переписывании вашего компонента shouldComponentUpdate; сначала я должен указать, что ваше существование shouldComponentUpdateне кажется разумным, поскольку вы всегда возвращаетесь trueи используете его только для запуска loadingсостояния; и его нельзя писать с помощью 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в случае id, paramsили 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();
  });