reactjs - ReactJS:如何在没有 1 单击延迟的情况下刷新组件
问题描述
我正在学习 React,当父母的状态被另一个孩子改变时,我在刷新子组件时遇到了问题。我发现我应该使用componentWillReceiveProps()
它并且它可以工作,但是一键延迟。我应该改变什么来获得即时更新?
我已经在 CodePen 上发布了我的代码,以便于解释。如果直接在这里发布更好,请告诉我,我会更新。
问题:
- 当我在 ClockSetter(+ 和 - 按钮)中增加或减少长度时,父母的状态会立即改变,但 Timer 中的会话长度会显示以前的值。
- 当我单击重置按钮时,父级的状态会立即更改,但 ClockSetter 的长度和计时器的长度不会更改为 ClockSetter 的。再次单击重置后,两个孩子的长度都更改为父母的状态。
- 如果我在重置后尝试增加或减少(而不是第二次重置点击)它会发疯(我找不到它如何改变的规则)
是否可以仅使用 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;
解决方案
让我们重构您的代码,以解决眼前的问题,同时解决一些不良做法和反模式,这些做法和反模式会让您在前进时感到头疼。
而且因为您才刚刚开始,所以这是学习钩子的最佳时机,因为它们将使您的代码更简单,更容易理解。
代码中的主要缺陷是state 的重复。让我们从 Timer 组件开始。
您将其初始状态设置length
为其父状态的值sessionLength
。即使您可以将其视为某种类型的“初始”状态,然后一旦倒计时开始,计时器length
将独立sessionLength
,但这不是必需的。事实上.. 在 99% 的情况下不需要重复状态。
那么Timer应该有什么状态呢?我认为计时器可能有自己的内部计数器状态,以便您显示当前时间this.props.sessionLength - this.state.elapsedTime
,但在您的情况下,计时器实际上并没有做任何计时。无论如何,您都在跟踪父级的当前时间。
知道这一点.. Timer 状态应该是什么?没有什么!答案是没有状态。Timer 可以是一个函数,而不是一个类,它可以接收 props 并显示它们。
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
处理程序。您可以在 return 语句上方编写命名handleDecrease
和函数,并根据需要传递它们。不过,这只是一个偏好问题。handleIncrease
onClick
*注意:道具现在length
不是initialLength
. 确保在渲染 ClockSetter 组件时更新它
对于最后一次重构,我更新了您的 React cdn 以指向最新的稳定版本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 多行代码,它更易于阅读,并且不存在状态重复的潜在问题。
希望对您有所帮助并祝您编码愉快!如果您需要,请提出任何问题。
推荐阅读
- google-cloud-firestore - Firestore分页-如何在查询后查找是否有更多数据(使用限制)
- c++ - C++ 零初始化具有可变数组长度的模板数组
- javascript - 如何让菜单在移动设备上向下滑动并在桌面上向右滑动?
- sql - 如何在 BigQuery 中解析位于 ("%m/%d/%Y %H:%M:%S") 的 CURRENT_DATETIME()
- batch-file - Cmd 合并 .txt 文件并删除行
- javascript - 有没有办法在 ASP.NET 中从 C# 运行 JavaScript 函数?
- android - 使用Android Maven Plugin 4+的android项目中非标准位置的文件或文件夹
- c# - 使用实体框架从修改列表中更新数据库表
- android - 使用 vue.js 和 android 的移动应用程序的区别
- php - 当我使用 docker multi-stage build 构建生产应用程序时,如何删除 composer dev 依赖项?