Я использую контекст React для хранения данных и обеспечения функциональности для изменения этих данных.
Теперь я пытаюсь преобразовать компонент класса в функциональный компонент, используя React Hooks.
Хотя в классе все работает как положено, я не могу заставить его работать в функциональном компоненте.
Поскольку код моих приложений немного сложнее, я создал этот небольшой пример ( JSFiddle link ), которая позволяет воспроизвести проблему:
Сначала контекст, который одинаков как для класса, так и для функционального компонента:
const MyContext = React.createContext();
class MyContextProvider extends React.Component {
constructor (props) {
super(props);
this.increase = this.increase.bind(this);
this.reset = this.reset.bind(this);
this.state = {
current: 0,
increase: this.increase,
reset: this.reset
}
}
render () {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
increase (step) {
this.setState((prevState) => ({
current: prevState.current + step
}));
}
reset () {
this.setState({
current: 0
});
}
}
Теперь, вот компонент Class, который работает просто отлично:
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.increaseByOne = this.increaseByOne.bind(this);
}
componentDidMount () {
setInterval(this.increaseByOne, 1000);
}
render () {
const count = this.context;
return (
<div>{count.current}</div>
);
}
increaseByOne () {
const count = this.context;
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}
}
MyComponent.contextType = MyContext;
Ожидаемый результат - он считается до 5 с интервалом в одну секунду, а затем снова начинается с 0.
А вот преобразованный функциональный компонент:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, []);
React.useEffect(() => {
setInterval(increaseByOne, 1000);
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
Вместо сброса счетчика на 5 он возобновляет счет.
Проблема в том, что count.current
в строке if (count.current === 5) {
всегда 0
, так как я t не использует последнее значение.
Единственный способ заставить это работать - настроить код следующим образом:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, [count]);
React.useEffect(() => {
console.log('useEffect');
const interval = setInterval(increaseByOne, 1000);
return () => {
clearInterval(interval);
};
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
Теперь обратный вызов increaseByOne
воссоздается при каждом изменении контекста, что также означает, что эффект вызывается каждую секунду.
В результате он очищает интервал и устанавливает новый при каждом изменении контекста (вы можете увидеть это в консоль браузера).
Это может работать в этом небольшом примере, но оно изменило исходную логику c и имеет намного больше накладных расходов.
Мое приложение не использует интервал, но оно слушает для события. Удаление прослушивателя событий и добавление его позже, означало бы, что я могу потерять некоторые события, если они будут запущены между удалением и привязкой слушателя, что выполняется асинхронно с помощью React.
Есть кто-то Идея, как это должно реагировать, чтобы решить эту проблему без изменения общего лога c?
Я создал скрипку, чтобы поиграться с кодом выше:
https://jsfiddle.net/Jens_Duttke/78y15o9p/