Я изучаю React, и у меня проблема с обновлением дочернего компонента, когда состояние родителя изменяется другим дочерним элементом. Я обнаружил, что должен использовать componentWillReceiveProps(), и он работает, но с задержкой в один клик. Что я должен изменить, чтобы получить немедленное обновление?
Я разместил свой код на CodePen, чтобы упростить объяснение. Если лучше опубликовать его здесь, пожалуйста, дайте мне знать, и я обновлю.
Проблемы:
Можно ли это сделать только с React или стоит начать изучать Redux?
Обновлено: мой код
import React from 'react';
import './PomodoroClock.scss';
class PomodoroClock extends React.Component {
constructor(props) {
super(props);
this.state = {
sessionLength: 25,
breakLength: 5,
};
this.handleTime = this.handleTime.bind(this);
this.handleReset = this.handleReset.bind(this);
}
handleTime(type, time) {
this.setState({
[type]: time
});
}
handleReset() {
this.setState({
sessionLength: 25,
breakLength: 5,
});
}
render() {
return (
<div className = "container">
<ClockSetter clockType = "break" initialLength = {this.state.breakLength} handleTime = {this.handleTime} />
<ClockSetter clockType = "session" initialLength = {this.state.sessionLength} handleTime = {this.handleTime} />
<Timer sessionLength = {this.state.sessionLength} reset = {this.handleReset}/>
<span>TEST: State session - {this.state.sessionLength} State break - {this.state.breakLength}</span>
</div>
);
}
}
class ClockSetter extends React.Component {
constructor(props) {
super(props);
this.state = {
length: this.props.initialLength
}
this.handleDecrease = this.handleDecrease.bind(this);
this.handleIncrease = this.handleIncrease.bind(this);
this.refresh = this.refresh.bind(this);
}
handleDecrease() {
if (this.state.length > 1) {
this.setState ({
length: this.state.length - 1
});
}
this.props.handleTime(this.props.clockType+'Length', this.state.length - 1);
}
handleIncrease() {
if (this.state.length < 60) {
this.setState ({
length: this.state.length + 1
});
}
this.props.handleTime(this.props.clockType+'Length', this.state.length + 1);
}
refresh() {
this.setState({
length: this.props.initialLength
});
}
componentWillReceiveProps(props) {
if (this.state.length !== this.props.initialLength) {
this.refresh();
}
}
render() {
let type = this.props.clockType;
return(
<div className = "clock-setter">
<div id = {type + '-label'} className = "first-letter-capitalize">{type + ' Length'}</div>
<span id = {type + '-decrement'} className = "button" onClick = {this.handleDecrease}>-</span>
<span id = {type + '-length'}>{this.state.length}</span>
<span id = {type + '-increment'} className = "button" onClick = {this.handleIncrease}>+</span>
</div>
);
}
}
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
activeCountdown: 'Session',
length: this.props.sessionLength
}
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({
length: this.props.sessionLength
});
}
componentWillReceiveProps(props) {
if (this.state.length !== this.props.sessionLength) {
this.refresh();
}
}
render() {
return(
<div className = "timer">
<span id = "timer-label">{this.state.activeCountdown}</span>
<div id = "time-left">{this.state.length}</div>
<span id = "start_stop" className = "button">Start/Stop</span>
<span id = "reset" className = "button" onClick = {this.props.reset}>Reset</span>
</div>
);
}
}
export default PomodoroClock;
Вы следовали руководству, чтобы получить код, который у вас есть сейчас? В вашем коде много проблем. Трудно решить только одну вашу проблему, не объясняя, почему общая архитектура плоха. Например.. componentWillReceiveProps на самом деле устарел сейчас, потому что это привело к именно тому типу проблем, с которыми вы имеете дело сейчас.
Кроме того, есть древняя поговорка: «Вы поймете, когда вам понадобится Flux/Redux». :)
@grooveplex эта пословица глупа. Многие люди этого не знают, и в настоящее время существует множество конкурирующих решений для редукции в зависимости от потребностей приложения.
Да, это вполне возможно сделать только с помощью React. Вы можете построить что угодно и что угодно только с помощью React. Redux и другие помощники по управлению состоянием делают именно это... помогают. но вы не необходимость их. Это не означает, что вы не должны их использовать, но важно знать, как делать все с React, чтобы вы могли принять более обоснованное решение о том, когда вам нужна дополнительная помощь от большего количества библиотек/шаблонов.
Ответ готов! надеюсь, это поможет





Давайте проведем рефакторинг вашего кода таким образом, чтобы решить насущную проблему, и в то же время решить некоторые неправильные практики и антипаттерны, которые вызовут у вас головную боль в дальнейшем.
И поскольку вы только начинаете, это идеальное время, чтобы узнать о хуки, потому что они сделают ваш код намного проще и понятнее.
Основная ловушка в вашем коде — дублирование состояния. Начнем с компонента Timer.
Вы устанавливаете его начальное состояние length равным значению его родительского состояния sessionLength. Даже если вы можете воспринимать это как некое «начальное» состояние, а затем после начала обратного отсчета length таймера не будет зависеть от sessionLength, в этом нет необходимости. На самом деле... дублирование состояния не требуется в 99% ситуаций.
Итак, какое состояние у должен у Таймера? Я бы предположил, что у таймера может быть собственное внутреннее состояние счетчика, чтобы вы отображали текущее время, например this.props.sessionLength - this.state.elapsedTime, но в вашем случае таймер на самом деле не отсчитывает время. В любом случае вы отслеживаете текущее время на родительском уровне.
Зная это... каким должно быть состояние таймера? Ничего такого! Ответ - нет государства. Таймер может быть функцией, а не классом, получать свойства и отображать их.
function Timer(props) {
return (
<div className = "timer">
<span id = "timer-label">Session</span>
<div id = "time-left">{props.sessionLength}</div>
<span id = "start_stop" className = "button">
Start/Stop
</span>
<span id = "reset" className = "button" onClick = {props.reset}>
Reset
</span>
</div>
)
}
Если это все, что вы меняете, это уже решает ваш вопрос.
Далее давайте посмотрим на компонент ClockSetter. Вы дублируете состояние здесь точно таким же образом, и не только это, у вас есть дополнительные обработчики, которые просто вызывают обработчик родителей handleTime, внося дополнительные шаги и сложность, которые добавляют ненужный шум в ваше приложение. Давайте вообще избавимся от состояния и дополнительных обработчиков, и поэтому мы снова сможем использовать функцию вместо класса:
function ClockSetter(props) {
let type = props.clockType
return (
<div className = "clock-setter">
<div id = {type + '-label'} className = "first-letter-capitalize">
{type + ' Length'}
</div>
<span
id = {type + '-decrement'}
className = "button"
onClick = {() => props.handleTime(type + 'Length', props.length - 1)}
>
-
</span>
<span id = {type + '-length'}>{props.length}</span>
<span
id = {type + '-increment'}
className = "button"
onClick = {() => props.handleTime(type + 'Length', props.length + 1)}
>
+
</span>
</div>
)
}
Я встроил обработчики onClick для краткости. Вы можете написать именованные функции handleDecrease и handleIncrease над оператором return и передать им onClick, если хотите. Хотя это всего лишь вопрос предпочтений.
*Примечание: реквизит теперь length, а не initialLength. Обязательно обновите это при рендеринге компонентов ClockSetter.
Для этого последнего рефакторинга я обновил ваш cdn React, чтобы он указывал на последнюю стабильную версию 16.8.3, так как она включает хуки.
Вместо использования класса давайте напишем обычную функцию и воспользуемся хуком React.useState. Теперь код выглядит так:
function PomodoroClock() {
let [sessionLength, setSessionLength] = React.useState(25)
let [breakLength, setBreakLength] = React.useState(5)
function handleReset() {
setSessionLength(25)
setBreakLength(5)
}
return (
<div className = "container">
<ClockSetter
clockType = "break"
length = {breakLength}
handleTime = {setBreakLength}
/>
<ClockSetter
clockType = "session"
length = {sessionLength}
handleTime = {setSessionLength}
/>
<Timer
sessionLength = {sessionLength}
reset = {handleReset}
/>
<span>
Parent's state TEST: session - {sessionLength} break -
{breakLength}
</span>
</div>
)
}
и вместо того, чтобы иметь один объект состояния с ключами, которые ссылаются на каждый таймер, поскольку раньше это был единственный способ для компонентов с состоянием, мы дважды вызываем useState каждый с их соответствующим состоянием и обработчиками. Теперь мы можем удалить аргумент type + Length в нашем компоненте ClockSetter:
onClick = {() => props.handleTime(props.length + 1)}
Теперь вся программа:
function PomodoroClock() {
let [sessionLength, setSessionLength] = React.useState(25)
let [breakLength, setBreakLength] = React.useState(5)
function handleReset() {
setSessionLength(25)
setBreakLength(5)
}
return (
<div className = "container">
<ClockSetter
clockType = "break"
length = {breakLength}
handleTime = {setBreakLength}
/>
<ClockSetter
clockType = "session"
length = {sessionLength}
handleTime = {setSessionLength}
/>
<Timer
sessionLength = {sessionLength}
reset = {handleReset}
/>
<span>
Parent's state TEST: session - {sessionLength} break -
{breakLength}
</span>
</div>
)
}
function ClockSetter(props) {
let type = props.clockType
return (
<div className = "clock-setter">
<div id = {type + '-label'} className = "first-letter-capitalize">
{type + ' Length'}
</div>
<span
id = {type + '-decrement'}
className = "button"
onClick = {() => props.handleTime(props.length - 1)}
>
-
</span>
<span id = {type + '-length'}>{props.length}</span>
<span
id = {type + '-increment'}
className = "button"
onClick = {() => props.handleTime(props.length + 1)}
>
+
</span>
</div>
)
}
function Timer(props) {
return (
<div className = "timer">
<span id = "timer-label">Session</span>
<div id = "time-left">{props.sessionLength}</div>
<span id = "start_stop" className = "button">
Start/Stop
</span>
<span id = "reset" className = "button" onClick = {props.reset}>
Reset
</span>
</div>
)
}
ReactDOM.render(
<PomodoroClock />,
document.getElementById('root')
);
Мы сократили более 50 строк кода, его стало намного легче читать, и нет потенциальной проблемы с дублированием состояния.
Надеюсь, что это поможет и удачного кодирования! Пожалуйста, задавайте любые вопросы, если вам нужно.
Вау! Ты восхитителен. Большое тебе спасибо. Я даже не знал, что есть новая реакция (я использовал create-react-app, когда она была 16.7, и все еще копировал и изменял старые проекты вместо того, чтобы снова использовать create-react-app), поэтому мне нужно обновить свою реакцию, узнать о ловушках а потом попробую воспользоваться вашими советами. Большое холодное пиво для вас, и я обновлю, когда справлюсь с этим :)
@MiloK Вы слышали о npx? Это похоже на npm, за исключением того, что каждый раз будет загружаться последняя версия приложения create-react-app (и других пакетов). Если у вас есть недавняя версия npm (6.x или около того), она должна быть у вас. :)
вы можете обновить существующий проект create-react-app, установив npm последнюю версию react-scripts. не нужно снова вызывать create-реагировать-приложение или что-либо переносить
По поводу
is it better to post it here directly: да, было бы. Если вы редактировать свой вопрос, есть значок файла с</>, который вы можете использовать, чтобы встроить фрагмент в свой вопрос.