Реагировать на методы жизненного цикла на хуки
У меня простой блог со статьями. И я хочу переписать классы на функциональные компоненты и хуки. Теперь у меня есть такая логика в методах жизненного цикла моей страницы с формой редактирования / добавления: она отлично работает.
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();
}, []);
- У меня ошибка - «Слишком много повторных рендеров». в codeandbox полный код: codesandbox
Странно, но на localhost нет ошибки "Слишком много повторных рендеров".
Не знаю, что делать с методом моего класса shouldComponentUpdate, как его переписать на хуки. Пробовал «записку», но понятия не имел, как писать в этом случае.
И в любом случае мне чего-то не хватает, потому что все это не сработает - это неправильно обновляет поля формы.
Если вы хорошо разбираетесь в перехватчиках реакции, пожалуйста, помогите или дайте совет - как это исправить?
Ответы
Эффект без зависимости вызывает "Too many re-renders."
: он запускается после каждого рендеринга, затем он вызывает setIsLoading
update 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
Если вы пропустите старые хуки жизненного цикла, вы всегда можете воссоздать их как пользовательские хуки:
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();
});