发布于 2026-01-06 6 阅读
0

按照“React思维”构建应用程序

按照“React思维”构建应用程序

如果我们深入研究React 文档,会发现一篇名为“React 思维”的精彩文章。它是主要概念的最后一章,因此在继续阅读本文之前,务必先阅读前面的所有章节。在本文中,我们将按照该章节中的 5 个步骤,使用 React 构建一个简单的应用程序。

(此应用的代码托管在GitHub上)

首先,我们要画一个模型,可以用纸笔,也可以用软件——市面上有很多软件可供选择。

替代文字


第一步:将用户界面拆分成组件层级结构

我们的应用程序包含五个组件。

  1. 应用(绿色):它是顶层组件,包含应用内部的所有内容。
  2. AddDate(红色):接收用户输入的日期
  3. 日期列表(棕色):根据用户输入的日期显示卡片列表。
  4. 日期(蓝色):显示每个日期对应的卡片,并接收用户输入的任务信息。
  5. 任务(橙色):显示任务段落

替代文字

我们的组件层级结构如下:

  • 应用程序
    • 添加日期
    • 日期列表
      • 日期
        • 任务

(在模拟模型中,出现在另一个组件内部的组件应该在层级结构中显示为子组件)


步骤 2:在 React 中构建静态版本

此步骤的 git 分支

现在是时候添加组件,以便获得应用程序的静态布局。此步骤不涉及任何交互。正如文档所述,对于简单的应用程序,自顶向下构建组件通常更容易(在我们的示例中,从 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;
Enter fullscreen mode Exit fullscreen mode

添加日期

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;
Enter fullscreen mode Exit fullscreen mode

日期列表

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;
Enter fullscreen mode Exit fullscreen mode

日期

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;
Enter fullscreen mode Exit fullscreen mode

任务

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;
Enter fullscreen mode Exit fullscreen mode

步骤 3:确定 UI 状态的最小(但完整)表示

为了给我们的应用程序添加交互性,我们必须在数据模型中创建状态片段。

我们应用程序中的数据如下:

  1. 我们将日期列表传递给 DateList 组件。
  2. 我们从用户输入中获取的新日期
  3. 当用户输入的日期已存在时,会显示此错误消息。
  4. 当用户删除输入框中选择的日期并提交空日期时,会出现此错误消息。
  5. 卡片上显示的标题为卡片日期
  6. 用户在任务文本框中输入的卡片文本
  7. 提交“添加任务”后,以段落形式显示的卡片任务

现在我们需要通过三个问题来确定哪条数据属于状态。

  1. 它是通过 props 从父级传递过来的吗?如果是,那它可能就不是状态。
  2. 它是否随时间保持不变?如果是,那它可能就不是状态。
  3. 你能根据组件中的其他状态或属性计算出它吗?如果可以,那它就不是状态。

日期列表和我们从用户那里得到的新日期会随着时间而改变,并且不能基于任何其他状态或属性进行计算,因此将成为状态。

错误信息会随时间变化,我们可以从 render 方法中的 'dates' props 和 'date' state 计算出这些错误信息。但是,我们希望错误信息只在提交时显示,而不是每次页面重新渲染时都显示,因此我们将它们视为状态的一部分。

卡片日期会随时间变化,但可以从“日期”状态计算出来,所以它不是状态。

卡片文本是一种状态,因为它会随着时间而改变,并且不能基于任何其他状态或属性进行计算。

卡片任务会随时间变化。虽然可以根据“值”状态计算得出,但我们只需要在用户提交后才在段落中显示文本,因此我们应该将其视为一种状态。

最后,我们的状态是:

  • 日期列表
  • 用户输入的新日期
  • 相同的日期错误消息
  • 日期为空的错误信息
  • 卡片中文本框的值
  • 卡片中以段落形式传递的任务

第四步:确定你的州应该住在哪里

对于我们应用程序中的每个状态:

  • 找出所有基于该状态渲染内容的组件。
  • 找到一个公共所有者组件(位于层次结构中所有需要该状态的组件之上的单个组件)。
  • 状态的所有权应该归属于共同所有者,或者归属于层级结构中更高层级的其他组件。
  • 如果找不到合适的组件来持有状态,那就创建一个专门用于持有状态的新组件,并将其添加到公共持有组件之上的层级结构中。

datesDateList 组件渲染日期列表。AddDate
组件根据日期列表中是否已包含用户输入的日期显示错误信息。为了让这两个组件都能访问日期列表的状态,我们需要将日期列表的状态移至它们的父组件——即 App 组件。

date
这段状态信息位于 AddDate 组件中,因为用户在该组件中选择日期,而我们希望控制输入框的行为。

dateExists / dateEmpty这些状态信息应该放在 AddDate 组件中,因为
如果日期已存在或日期字段为空,该组件将需要显示错误消息。

value
这段状态信息位于 Date 组件中,因为用户在该组件中输入文本,而我们希望控制此输入框的行为。

task
这段状态信息存在于 Date 组件中,因为我们可以通过该组件获取用户的文本并将其传递给 Task 组件。

此步骤的 git 分支

应用程序

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;
Enter fullscreen mode Exit fullscreen mode

添加日期

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;
Enter fullscreen mode Exit fullscreen mode

日期列表

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;
Enter fullscreen mode Exit fullscreen mode

日期

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;
Enter fullscreen mode Exit fullscreen mode

任务

import React from 'react';

const Task = (props) => {
  return (
    <div className="App__task">
      <h3>Task</h3>
      <p>{props.task}</p>
    </div>
  );
};

export default Task;
Enter fullscreen mode Exit fullscreen mode

步骤 5:添加反向数据流

在这一步中,我们希望反向访问数据:从子组件到父组件。组件应该只更新自身的状态,因此当用户在 AddDate 组件中添加新日期时,它不能直接访问 App 组件内部的日期状态。我们可以通过从 App 组件向 AddDate 组件传递一个回调函数来实现访问,该回调函数会在状态需要更新时触发。onAddDate 回调函数将作为 prop 传递给 AddDate 组件,当添加新日期时,回调函数会运行,并将新日期传递给 App 组件。

此步骤的 git 分支

应用程序

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;
Enter fullscreen mode Exit fullscreen mode

添加日期

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;
Enter fullscreen mode Exit fullscreen mode

日期列表

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;
Enter fullscreen mode Exit fullscreen mode

日期

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;
Enter fullscreen mode Exit fullscreen mode

任务

import React from 'react';

const Task = (props) => {
  return (
    <div className="App__task">
      <h3>Task</h3>
      <p>{props.task}</p>
    </div>
  );
};

export default Task;
Enter fullscreen mode Exit fullscreen mode

终点线

现在我们有了将用户界面拆分成小块并创建不同版本的指南。一个静态版本仅使用我们的数据模型渲染用户界面,另一个最终版本则添加了交互功能。

希望您在学习这篇 React 应用开发教程的过程中玩得开心!

你可以在这里找到这个应用的代码

该应用在这里也已上线运行。

感谢阅读!

文章来源:https://dev.to/pablopap/building-an-app-according-to-thinking-in-react-243e