Я сталкиваюсь с двумя проблемами:
см. пример кода ниже:
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;Что касается componentDidUpdate, у componentDidUpdate есть аналог, когда используется React Hook, вы должны использовать его так:
React.useEffect(() => {
return () => {
};
}, [parentProp]);
Второй параметр для useEffect гарантирует, что код будет выполняться только при изменении пропса, но что, если я хочу выполнять соответствующие задачи на основе нескольких соответствующих изменений реквизита? как это сделать с использованиемЭффект?
см. пример кода ниже:
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.info('Let'
's say, I do want to do some task here only when groupName differs');
} else if (prevProps.companyName !== this.props.companyName) {
console.info('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;


![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете использовать хук useMemo для хранения вычислений и поместить props.count в массив, указанный в качестве второго аргумента, чтобы пересчитать значение при его изменении.
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"));<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>Самый простой способ сделать отдельные эффекты при изменении отдельных реквизитов — создать несколько хуков useEffect, которые запускаются только при изменении одного из отдельных реквизитов.
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.info("Let's say, I do want to do some task here only when groupName differs");
}, [props.groupName])
useEffect(() => {
console.info("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"));<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>Я понимаю, что ваш пример «производного состояния» намеренно прост, но поскольку существует так мало законных случаев производного состояния, трудно дать рекомендацию по замене, кроме как в каждом конкретном случае, поскольку это зависит от причины, по которой вы используют производное состояние. В приведенном вами конкретном примере не было причин использовать производное состояние в случае класса, и поэтому по-прежнему нет причин в случае ловушки (значение может быть просто получено локально, не помещая его в состояние). Если полученное значение дорого, вы можете использовать useMemo как подарок Tholle. Если они не соответствуют более реалистичным случаям, которые вы имеете в виду, вам нужно будет представить более конкретный случай, который действительно требует производного состояния.
Что касается вашего примера componentDidUpdate, если то, что вы хотите сделать для разных реквизитов, является независимым, то вы можете использовать отдельные эффекты для каждого (например, несколько вызовов useEffect). Если вы хотите сделать точно, как в вашем примере (т. е. сделать что-то только для companyName изменения, если groupName также не изменилось, как указано вашим else if), вы можете использовать судьи для более сложных условий. Вы должны нет изменить ссылку во время рендеринга (всегда существует вероятность того, что рендеринг будет отброшен/переделан после поддержки параллельного режима), поэтому в примере используется последний эффект для обновления ссылок. В моем примере я использую ссылку, чтобы избежать работы с эффектами при первоначальном рендеринге (см. ответ Толле в этот связанный вопрос) и определить, изменилось ли groupName, при принятии решения о том, следует ли выполнять работу на основе companyName изменения.
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.info("Do something when groupName changes", groupName);
}
},
[groupName]
);
useEffect(
() => {
if (!initialRender.current) {
console.info("Do something when companyName changes", companyName);
}
},
[companyName]
);
useEffect(
() => {
if (!initialRender.current && groupName === lastGroupName.current)
console.info(
"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);<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>В вашем сценарии вам вообще не нужно использовать или повторно реализовывать getDerivedStateFromProps. Вам просто нужно создать новую переменную, чтобы получить новую форму данных. Использование состояния в этом сценарии просто вызовет еще один повторный рендеринг, что не очень хорошо с точки зрения производительности.
import React from 'react';
const App = ({ count }) => {
const derivedCount = count > 100 ? 100 : count;
return (
<div>Counter: {derivedCount}</div>
);
}
App.propTypes = {
count: PropTypes.number.isRequired
}
Демо здесь: https://codesandbox.io/embed/qzn8y9y24j?fontsize=14
Вы можете узнать больше о различных способах решения таких сценариев без использования getDerivedStateFromProps здесь: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
Если вам действительно нужно использовать отдельное состояние, вы можете использовать что-то вроде этого
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>;
};
Хук реакции, эквивалентный старому реквизиту componentWillReceive, можно сделать с помощью хука useEffect, просто указав реквизит, который мы хотим прослушивать на предмет изменений в массиве зависимостей.
То есть:
export default (props) => {
useEffect( () => {
console.info('counter updated');
}, [props.counter])
return <div>Hi {props.counter}</div>
}
Для componentDidUpdate просто опуская массив зависимостей, функция useEffect будет вызываться после каждого повторного рендеринга.
То есть:
export default (props) => {
useEffect( () => {
console.info('counter updated');
})
return <div>Hi {props.counter}</div>
}
Проведя обширное исследование по этому вопросу, я бы сказал, что ваше объяснение componentWillReceiveProps неверно. Итак, проблема в том, что замена componentWillReceiveProps сложна, потому что это функция предварительного рендеринга. Это не относится к реактивным хукам. Вы можете использовать useRef(), чтобы сохранить предыдущий реквизит и сравнить его с текущим в функции useEffect, чтобы попытаться воспроизвести функциональность, но даже это не может быть полной заменой 1:1.
setCount вызовет повторный рендеринг. Использование useEffect с [count] в качестве массива зависимостей гарантирует, что хук будет вызывать setCount только при изменении значения count.
Вот как вы заменяете любую componentWillReceiveProps логику, которую вы могли бы написать в старом стиле React, основанном на классах. Я считаю полезным принцип «Каждый рендер имеет свои реквизиты и состояние»: если вы хотите инициировать повторный рендеринг только при изменении определенных свойств, у вас может быть несколько хуков useEffect.
useEffect(() => {
count > 100 ? setCount(100) : setCount(count)
}, [count])
useEffect(() => {
console.info('groupName has changed');
// do something with groupName
}, [groupName])
useEffect(() => {
console.info('companyName has changed');
// do something with companyName
}, [companyName])
просто используя useEffect вот так.
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.
1.) What is the equivalent implementation with React Hook, If I do need derived state?
Производное состояние для хуков = установить состояние условно и напрямую на этапе рендеринга:
constComp = (props) => {
const [derivedState, setDerivedState] = useState(42);
if (someCondition) {
setDerivedState(...);
}
// ...
}
Это обновляет состояние без дополнительной фазы фиксации, в отличие от useEffect. Приведенный выше шаблон поддерживается Реагировать на строгий режим (без предупреждений):
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.info("render, prevRow:", prevRow)
if (row !== prevRow) {
console.info("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"));<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>Примечание 1: componentWillReceiveProps уже давно устарел. getDerivedStateFromProps является преемником компонентов класса с точки зрения производного состояния.
Заметка 2: Проверьте предпочтительные решения, прежде чем вы прибегнете к производному состоянию.
2.) What if I want to do respective tasks based on multiple respective props changes?
Вы можете либо полностью убрать useEffect deps, либо добавить еще один реквизит:
React.useEffect(() => {
return () => { };
}, [parentProp, secondProp]);
Если вы используете хук useMemo поверх вашего компонента и он зависит от всех ваших реквизитов, он запускается перед всем при каждом изменении реквизитов. useEffect срабатывает после обновленного рендеринга и, поскольку зависит от всех реквизитов, он срабатывает после повторного рендеринга в зависимости от всех реквизитов.
const Component = (...props) => {
// useState, useReducer if have
useMemo(() => {
// componentWillReceiveProps
},[...props]);
// ...other logic and stuff
useEffect(() => {
// componentDidUpdate
}, [...props]);
};
Я рад узнать, что это действительно полезно, чтобы определить разницу между ними. Спасибо ..!
В случае, если вы хотите реплицировать систему журналов, такую как почему ты рендерил, и вам нужен nextProps здесь помогает хук.
const useComponentWillReceiveProps(nextProps, callback) {
const props = useRef(nextProps)
useEffect(() => {
callback(nextProps, props.current)
props.current = nextProps
})
}
const diffLogger = (nextProps, props, { name = "Component" } = {}) => {
Object.keys(nextProps)
.filter((key) => nextProps[key] !== props[key])
.forEach((key) => {
console.info(
`%c${name}:%c${key}`,
"color:#ff5722; font-size:1rem; font-weight:bold;",
"color:#ffc107; font-size:1.2rem; font-weight:bold;",
{
from: props[key],
to: nextProps[key],
}
)
})
}
const Button = (props) => {
useComponentWillReceiveProps(props, diffLogger, {name: 'Button'})
return <button onClick = {props.onClick}>Button</button>
}
В настоящее время Если ваша кнопка перерисовывается, например. по onClick новой ссылке вы получите что-то вроде этого:

Я использую React Native. Я добавил это в свой экранный компонент, где мне нужна была функциональность componentWillReceiveProps:
const propsRef = useRef(props)
// component will receive props
const componentWillReceiveProps = () => {
const propsHaveChanged = !isEqual(propsRef.current, props)
propsRef.current = props
if (propsHaveChanged){
setIndex(0)
}
}
Здесь isEqual — это функция lodash, которая выполняет глубокое сравнение. Затем просто вызовите функцию.
componentWillReceiveProps()
Это вызывается в верхней части функционального компонента. Таким образом, его следует вызывать до того, как функциональный компонент отобразит свое представление.
не правда ли, что реквизит доступен только для чтения? и изменение реквизита также нарушает чистую функцию. правильно?