按照“React思维”构建应用程序
如果我们深入研究React 文档,会发现一篇名为“React 思维”的精彩文章。它是主要概念的最后一章,因此在继续阅读本文之前,务必先阅读前面的所有章节。在本文中,我们将按照该章节中的 5 个步骤,使用 React 构建一个简单的应用程序。
(此应用的代码托管在GitHub上)
首先,我们要画一个模型,可以用纸笔,也可以用软件——市面上有很多软件可供选择。
第一步:将用户界面拆分成组件层级结构
我们的应用程序包含五个组件。
- 应用(绿色):它是顶层组件,包含应用内部的所有内容。
- AddDate(红色):接收用户输入的日期
- 日期列表(棕色):根据用户输入的日期显示卡片列表。
- 日期(蓝色):显示每个日期对应的卡片,并接收用户输入的任务信息。
- 任务(橙色):显示任务段落
我们的组件层级结构如下:
- 应用程序
- 添加日期
- 日期列表
- 日期
- 任务
- 日期
(在模拟模型中,出现在另一个组件内部的组件应该在层级结构中显示为子组件)
步骤 2:在 React 中构建静态版本
现在是时候添加组件,以便获得应用程序的静态布局。此步骤不涉及任何交互。正如文档所述,对于简单的应用程序,自顶向下构建组件通常更容易(在我们的示例中,从 App 组件开始)。
应用程序
import React, { Component } from 'react';
import './App.css';
import AddDate from './AddDate';
import DateList from './DateList';
class App extends Component {
render() {
const dates = ['2018-04-23', '2019-06-13', '2014-09-29'];
return (
<div className="App">
<header className="App-header">
<h1>Time Machine</h1>
</header>
<AddDate dates={dates} />
<DateList dates={dates} />
</div>
);
}
}
export default App;
添加日期
import React, { Component } from 'react';
class AddDate extends Component {
render() {
return (
<div className="App__form">
<form className="App__form--date">
<div className="App__form--body">
<label>Choose Your Past:</label>
<input type="date" max={new Date().toISOString().split('T')[0]} />
</div>
<div className="App__form--btn">
<button type="submit">Add Date</button>
</div>
</form>
</div>
);
}
}
export default AddDate;
日期列表
import React, { Component } from 'react';
import Date from './Date';
class DateList extends Component {
render() {
const { dates } = this.props;
return (
<div className="App__list">
<h2 className="App__list--title">Missions</h2>
<ul className="App__list--items">
{dates.map((date) => (
<Date date={date} key={date} />
))}
</ul>
</div>
);
}
}
export default DateList;
日期
import React, { Component } from 'react';
import Task from './Task';
class Date extends Component {
render() {
const { date } = this.props;
return (
<li>
<div className="App__card--inner">
<h2>{date}</h2>
<form onSubmit={this.handleFormSubmit} className="App__card">
<div className="App__card--form">
<label>Add Your Task</label>
<textarea
rows="3"
cols="30"
placeholder="type here..."
required
></textarea>
</div>
<div className="App__card--btn">
<button type="submit">Add Task</button>
</div>
</form>
<Task />
</div>
</li>
);
}
}
export default Date;
任务
import React from 'react';
const Task = () => {
return (
<div className="App__task">
<h3>Task</h3>
<p>this is the task paragraph</p>
</div>
);
};
export default Task;
步骤 3:确定 UI 状态的最小(但完整)表示
为了给我们的应用程序添加交互性,我们必须在数据模型中创建状态片段。
我们应用程序中的数据如下:
- 我们将日期列表传递给 DateList 组件。
- 我们从用户输入中获取的新日期
- 当用户输入的日期已存在时,会显示此错误消息。
- 当用户删除输入框中选择的日期并提交空日期时,会出现此错误消息。
- 卡片上显示的标题为卡片日期
- 用户在任务文本框中输入的卡片文本
- 提交“添加任务”后,以段落形式显示的卡片任务
现在我们需要通过三个问题来确定哪条数据属于状态。
- 它是通过 props 从父级传递过来的吗?如果是,那它可能就不是状态。
- 它是否随时间保持不变?如果是,那它可能就不是状态。
- 你能根据组件中的其他状态或属性计算出它吗?如果可以,那它就不是状态。
日期列表和我们从用户那里得到的新日期会随着时间而改变,并且不能基于任何其他状态或属性进行计算,因此将成为状态。
错误信息会随时间变化,我们可以从 render 方法中的 'dates' props 和 'date' state 计算出这些错误信息。但是,我们希望错误信息只在提交时显示,而不是每次页面重新渲染时都显示,因此我们将它们视为状态的一部分。
卡片日期会随时间变化,但可以从“日期”状态计算出来,所以它不是状态。
卡片文本是一种状态,因为它会随着时间而改变,并且不能基于任何其他状态或属性进行计算。
卡片任务会随时间变化。虽然可以根据“值”状态计算得出,但我们只需要在用户提交后才在段落中显示文本,因此我们应该将其视为一种状态。
最后,我们的状态是:
- 日期列表
- 用户输入的新日期
- 相同的日期错误消息
- 日期为空的错误信息
- 卡片中文本框的值
- 卡片中以段落形式传递的任务
第四步:确定你的州应该住在哪里
对于我们应用程序中的每个状态:
- 找出所有基于该状态渲染内容的组件。
- 找到一个公共所有者组件(位于层次结构中所有需要该状态的组件之上的单个组件)。
- 状态的所有权应该归属于共同所有者,或者归属于层级结构中更高层级的其他组件。
- 如果找不到合适的组件来持有状态,那就创建一个专门用于持有状态的新组件,并将其添加到公共持有组件之上的层级结构中。
datesDateList 组件渲染日期列表。AddDate
组件根据日期列表中是否已包含用户输入的日期显示错误信息。为了让这两个组件都能访问日期列表的状态,我们需要将日期列表的状态移至它们的父组件——即 App 组件。
date:
这段状态信息位于 AddDate 组件中,因为用户在该组件中选择日期,而我们希望控制输入框的行为。
dateExists / dateEmpty这些状态信息应该放在 AddDate 组件中,因为
如果日期已存在或日期字段为空,该组件将需要显示错误消息。
value:
这段状态信息位于 Date 组件中,因为用户在该组件中输入文本,而我们希望控制此输入框的行为。
task:
这段状态信息存在于 Date 组件中,因为我们可以通过该组件获取用户的文本并将其传递给 Task 组件。
应用程序
import React, { Component } from 'react';
import './App.css';
import AddDate from './AddDate';
import DateList from './DateList';
class App extends Component {
state = {
dates: [],
};
render() {
const dates = ['2018-04-23', '2019-06-13', '2014-09-29'];
return (
<div className="App">
<header className="App-header">
<h1>Time Machine</h1>
</header>
<AddDate dates={dates} />
<DateList dates={dates} />
</div>
);
}
}
export default App;
添加日期
import React, { Component } from 'react';
class AddDate extends Component {
state = {
date: new Date().toISOString().split('T')[0],
dateExists: false,
dateEmpty: false,
};
render() {
return (
<div className="App__form">
<form onSubmit={this.handleFormSubmit} className="App__form--date">
<div className="App__form--body">
<label>Choose Your Past:</label>
<input type="date" max={new Date().toISOString().split('T')[0]} />
</div>
<div className="App__form--btn">
<button type="submit">Add Date</button>
</div>
</form>
</div>
);
}
}
export default AddDate;
日期列表
import React, { Component } from 'react';
import Date from './Date';
class DateList extends Component {
render() {
const { dates } = this.props;
return (
<div className="App__list">
<h2 className="App__list--title">Missions</h2>
<ul className="App__list--items">
{dates.map((date) => (
<Date date={date} key={date} />
))}
</ul>
</div>
);
}
}
export default DateList;
日期
import React, { Component } from 'react';
import Task from './Task';
class Date extends Component {
state = {
value: '',
task: '',
};
render() {
const { date } = this.props;
return (
<li>
<div className="App__card--inner">
<h2>{date}</h2>
<form onSubmit={this.handleFormSubmit} className="App__card">
<div className="App__card--form">
<label>Add Your Task</label>
<textarea
rows="3"
cols="30"
placeholder="type here..."
required
></textarea>
</div>
<div className="App__card--btn">
<button type="submit">Add Task</button>
</div>
</form>
<Task task={this.state.task} />
</div>
</li>
);
}
}
export default Date;
任务
import React from 'react';
const Task = (props) => {
return (
<div className="App__task">
<h3>Task</h3>
<p>{props.task}</p>
</div>
);
};
export default Task;
步骤 5:添加反向数据流
在这一步中,我们希望反向访问数据:从子组件到父组件。组件应该只更新自身的状态,因此当用户在 AddDate 组件中添加新日期时,它不能直接访问 App 组件内部的日期状态。我们可以通过从 App 组件向 AddDate 组件传递一个回调函数来实现访问,该回调函数会在状态需要更新时触发。onAddDate 回调函数将作为 prop 传递给 AddDate 组件,当添加新日期时,回调函数会运行,并将新日期传递给 App 组件。
应用程序
import React, { Component } from 'react';
import './App.css';
import AddDate from './AddDate';
import DateList from './DateList';
class App extends Component {
state = {
dates: [],
};
addDate = (date) => {
this.setState((currState) => ({
dates: [...currState.dates, date],
}));
};
render() {
return (
<div className="App">
<header className="App-header">
<h1>Time Machine</h1>
</header>
<AddDate dates={this.state.dates} onAddDate={this.addDate} />
<DateList dates={this.state.dates} />
</div>
);
}
}
export default App;
添加日期
import React, { Component } from 'react';
class AddDate extends Component {
state = {
date: new Date().toISOString().split('T')[0],
dateExists: false,
dateEmpty: false,
};
sameDateExists = (currDate) => {
const dates = this.props.dates;
for (let date of dates) {
if (date === currDate) {
return true;
}
}
return false;
};
handleFormSubmit = (event) => {
event.preventDefault();
const dateExists = this.sameDateExists(this.state.date);
if (!dateExists && this.state.date) {
this.props.onAddDate(this.state.date);
this.setState({ dateEmpty: false });
}
if (!this.state.date) {
this.setState({ dateEmpty: true });
}
if (dateExists) {
this.setState({ dateEmpty: false });
}
this.setState({ dateExists });
};
handleDateChange = (event) => {
const { value } = event.target;
this.setState((currState) => ({
...currState,
date: value,
}));
};
render() {
return (
<div className="App__form">
<form onSubmit={this.handleFormSubmit} className="App__form--date">
<div className="App__form--body">
<label>Choose Your Past:</label>
<input
type="date"
max={new Date().toISOString().split('T')[0]}
onChange={this.handleDateChange}
/>
</div>
<div className="App__form--btn">
<button type="submit">Add Date</button>
</div>
</form>
{this.state.dateExists ? (
<p className="App__form--error">This date has already been chosen</p>
) : (
''
)}
{this.state.dateEmpty ? (
<p className="App__form--error">Please choose a date</p>
) : (
''
)}
</div>
);
}
}
export default AddDate;
日期列表
import React, { Component } from 'react';
import Date from './Date';
class DateList extends Component {
render() {
const { dates } = this.props;
return (
<div className="App__list">
<h2 className="App__list--title">Missions</h2>
<ul className="App__list--items">
{dates.map((date) => (
<Date date={date} key={date} />
))}
</ul>
</div>
);
}
}
export default DateList;
日期
import React, { Component } from 'react';
import Task from './Task';
class Date extends Component {
state = {
value: '',
task: '',
};
handleFormSubmit = (event) => {
event.preventDefault();
this.setState({
task: this.state.value,
});
};
handleAddTask = (event) => {
this.setState({
value: event.target.value,
});
};
render() {
const { date } = this.props;
return (
<li>
<div className="App__card--inner">
<h2>{date}</h2>
<form onSubmit={this.handleFormSubmit} className="App__card">
<div className="App__card--form">
<label>Add Your Task</label>
<textarea
rows="3"
cols="30"
placeholder="type here..."
value={this.state.value}
onChange={this.handleAddTask}
required
></textarea>
</div>
<div className="App__card--btn">
<button type="submit">Add Task</button>
</div>
</form>
<Task task={this.state.task} />
</div>
</li>
);
}
}
export default Date;
任务
import React from 'react';
const Task = (props) => {
return (
<div className="App__task">
<h3>Task</h3>
<p>{props.task}</p>
</div>
);
};
export default Task;
终点线
现在我们有了将用户界面拆分成小块并创建不同版本的指南。一个静态版本仅使用我们的数据模型渲染用户界面,另一个最终版本则添加了交互功能。
希望您在学习这篇 React 应用开发教程的过程中玩得开心!
你可以在这里找到这个应用的代码。
该应用在这里也已上线运行。
感谢阅读!
文章来源:https://dev.to/pablopap/building-an-app-according-to-thinking-in-react-243e

