R

React 101 - 实用介绍

2025-06-07

React 101 - 实用介绍

你看过 React 官方的井字游戏教程吗?如果你看过,你可能会注意到简介里这段粗体字

您可能想跳过它,因为您没有构建游戏 - 但给它一个机会。

我猜 React 团队也知道构建井字游戏并不那么有趣,因为他们添加了那条线。虽然该项目确实让你了解了 React,但你需要有坚强的意志来完成本教程。

别误会,我很感激这个入门项目,但我就是不喜欢它。如果你也有同样的想法,我将在本教程中介绍一些可以构建的替代项目来学习 React。

那么,你可以选择哪些前端项目来学习 React 呢?在浏览教程和博客文章的过程中,我注意到一个好的 React 入门教程必须做到以下几点:

  • 教授 React 的基础知识,如组件、状态和 props
  • 处理动态数据、状态和属性的变化
  • 展示生命周期方法的使用

嗯,实际上,主要概念选项卡中的几乎所有内容。

React 主要概念

在本教程结束时,您将更好地理解 React 概念(如组件、状态和生命周期方法)的用途以及它们在常见的 Web 应用程序 UI 中的使用方式。

注意:本教程的 CSS 部分将使用 Bootstrap 来使其更美观,而无需编写自己的 CSS。您可以放心地忽略className示例代码中的部分内容,因为它们来自 Bootstrap。

卡片列表的前端乐趣

让我们从使用 JSX、组件和 props 开始——它们是 React UI 的基本组成部分。这是我们的最终产品:

那么让我们开始构建它吧。我们要做的就是创建<Card/>返回 JSX 元素的组件:

function Card(props) {
    return (
      <div className="card">
        <img className="card-img-top" 
          src="https://via.placeholder.com/600x250.png" 
          alt="cap image" />
          <div className="card-body">
          <h5 className="card-title">Title Placeholder</h5>
          <p className="card-text">Description Placeholder</p>
          <a href="#" className="btn btn-primary">Learn more</a>
        </div>
      </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

然后创建一个渲染<Card/>三次的父组件。我们可以将其命名为<CardList/>

function CardList() {
  return (
    <div className="row">
      <div className="col-sm-4">
        <Card />
      </div>
      <div className="col-sm-4">
        <Card />
      </div>
      <div className="col-sm-4">
        <Card />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

不要忘记ReactDOM.render在代码底部添加调用。这段代码负责将我们的 React 应用程序引入到 HTML 元素中。

ReactDOM.render(<CardList />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

现在我们需要将自己的数据包含到这些卡片中,因此将一些数据传递props到卡片中

function CardList() {
  return (
    <div className="row">
      <div className="col-sm-4">
        <Card
          featureImage="https://sebhastian.com/static/eb0e936c0ef42ded5c6b8140ece37d3e/fcc29/feature-image.png"
          title="How To Make Interactive ReactJS Form"
          description="Let's write some interactive form with React"
          link="https://sebhastian.com/interactive-react-form"
        />
      </div>
      <div className="col-sm-4">
        <Card
          // your data
        />
      </div>
      <div className="col-sm-4">
        <Card
          // your data
        />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

然后在我们的<Card/>组件中使用这些道具:

function Card(props) {
  return (
    <div className="card">
      <img className="card-img-top" src={props.featureImage} alt="cap image" />
      <div className="card-body">
        <h5 className="card-title">{props.title}</h5>
        <p className="card-text">{props.description}</p>
        <a href={props.link} className="btn btn-primary">Learn more</a>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

现在这个<Card/>组件在其 JSX 中使用了 JavaScript,与模板引擎非常相似,不是吗?

您可能会想,“为什么我们使用function而不是class来声明组件?”

这是因为我们不保留state或使用生命周期方法。为了使用这两个方法,React 组件被声明为class(虽然现在我们也可以使用 React hooks 来实现,但我们暂时先不讨论 hooks)。

正如我们在示例中看到的,React 的 UI 由三个基本要素组成:组件、JSX 和 props。

  • 组件是由方法和 JSX 组成的单个 UI。
  • JSX是用 JS 增强的 HTML,使我们能够使用 JavaScript 语法描述 UI。
  • Props是我们传递到组件的任意输入。

我们确实无法从这个简单的静态卡片接受基本 UI 模式中学到太多东西,所以让我们继续进行更复杂的任务。

使用向导表单变得复杂

在第二个练习中,我们将构建一个向导表单。这是一个多步骤表单,旨在简化冗长复杂表单的填写过程。通过在屏幕上仅显示几个输入项,用户会感到有动力填写空白,而不是感到不知所措甚至可能放弃填写。

让我们看看如何使用 React 构建这样的表单:

创建多步骤表单最简单的方法是创建一个容器表单元素,其中包含所有向导步骤组件。此图将帮助您清晰地理解它。

React 向导图

虽然向导表单看起来比常规表单更复杂,但它仍然使用相同的 React 原理。但由于我们state在本练习中已经提到,因此我们需要引入一个新的原理:

  • 状态用于存储动态数据

我们将不再只有一个表单组件,而是拥有一个父组件和三个子组件。在上图中,<MasterForm/>父组件将通过 props 向子组件发送数据和函数,子组件将触发handleChange()函数来设置 的状态值<MasterForm/>。我们还需要一个函数来将表单从一个步骤移动到另一个步骤。

就像如何CardList将道具发送给一样Card,这些子组件将从和道具<MasterForm/>中接收道具valueonChange

  • <Step1/>组件将呈现电子邮件地址输入
  • <Step2/>将呈现用户名输入
  • <Step3/>将呈现密码输入和提交按钮

父组件<MasterForm/>将向子组件提供数据和功能,子组件将使用其将用户输入传递回父组件props

首先,我们将创建表单子组件。本示例每个表单步骤仅包含一个输入。注释将显示 的用法props

function Step1(props) {
  if (props.currentStep !== 1) {
    return null
  } 
  return(
    <div className="form-group">
      <label htmlFor="email">Email address</label>
      <input
        className="form-control"
        id="email"
        name="email"
        type="text"
        placeholder="Enter email"
        value={props.email}
        onChange={props.handleChange}
        />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

由于子组件彼此之间看起来几乎相似,因此上面我只展示了其中一个。您可以查看演示以获取完整代码。请注意,我们使用了function而不是class,因为我们没有使用state或 生命周期方法。

然后,我们可以将这个子组件放入主表单render()函数中,并传入必要的 props。为了处理用户在文本中输入内容的事件,我们使用了onChange合成事件,它是 React 核心库中用于处理事件的一部分。更多详情请点击此处

让我们创建<MasterForm/>组件并初始化它的状态和方法。currentStep状态将初始化为 1。这用于步骤指示器,以便我们的表单知道我们当前处于哪个步骤。由于此组件需要本地状态,我们将使用 ES6 类:

class MasterForm extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      currentStep: 1,
      email:  '',
      username: '',
      password: '', 
    }
  }

  // creating functions with ES6 arrow function syntax

  handleChange = event => {
    const {name, value} = event.target
    this.setState({
      [name]: value
    })    
  }

  handleSubmit = event => {
    event.preventDefault()
    const { email, username, password } = this.state
    alert(`Your registration detail: \n 
           Email: ${email} \n 
           Username: ${username} \n
           Password: ${password}`)
  }

  // render method here . . .
}
Enter fullscreen mode Exit fullscreen mode

接下来,我们在 的 render 方法中添加步骤<MasterForm/> 。它将handleChange()函数和必需的state值作为 props 发送,请注意突出显示的代码块:

render() {    
  return (
    <React.Fragment>
    <h1>A Wizard Form!</h1>
    <p>Step {this.state.currentStep} </p> 

    <form onSubmit={this.handleSubmit}>
    {/* 
      render the form steps and pass required props in
    */}

      <Step1 
        currentStep={this.state.currentStep} 
        handleChange={this.handleChange}
        email={this.state.email}
      />
      <Step2 
        currentStep={this.state.currentStep} 
        handleChange={this.handleChange}
        username={this.state.username}
      />
      <Step3 
        currentStep={this.state.currentStep} 
        handleChange={this.handleChange}
        password={this.state.password}
      />       

    </form>
    </React.Fragment>
  )
}
Enter fullscreen mode Exit fullscreen mode

由于render()必须返回单个元素,该<React.Fragment>组件允许您在 render() 方法中返回多个元素,而无需创建额外的 DOM 元素。更多详情请见此处

然后我们添加下一个或上一个步骤函数,它将检查当前步骤是否有上一个或下一个步骤。如果有,它将currentStep向上或向下推送:

class MasterForm extends Component {
  /*
  * Test current step with ternary
  * _next and _previous functions will be called on button click
  */

  _next = () => {
    let currentStep = this.state.currentStep
    currentStep = currentStep >= 2? 3: currentStep + 1
    this.setState({
      currentStep: currentStep
    })
  }

  _prev = () => {
    let currentStep = this.state.currentStep
    currentStep = currentStep <= 1? 1: currentStep - 1
    this.setState({
      currentStep: currentStep
    })
  }

  // ... the rest of the code
Enter fullscreen mode Exit fullscreen mode

我们将编写函数来检查当前步骤是 1 还是 3。这是因为我们的向导表单有 3 个步骤。如果您有更多步骤,可以更改它们。如果当前步骤没有下一步或上一步,按钮将会消失。这些按钮将调用我们的_next_previous方法。

/*
* the functions for our button
*/
previousButton(){
  let currentStep = this.state.currentStep;
  if(currentStep !==1){
    return (
      <button 
        className="btn btn-secondary" 
        type="button" onClick={this._prev}>
      Previous
      </button>
    )
  }
  return null;
}

nextButton(){
  let currentStep = this.state.currentStep;
  if(currentStep <3){
    return (
      <button 
        className="btn btn-primary float-right" 
        type="button" onClick={this._next}>
      Next
      </button>        
    )
  }
  return null;
}
Enter fullscreen mode Exit fullscreen mode

剩下的就是渲染下一个和上一个按钮

/*
* add buttons to our form in render
*/
render(){
  return(
    <form onSubmit={this.handleSubmit}>
      {/* 
        ... other codes
      */}

      {this.previousButton()}
      {this.nextButton()}
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

如果你好奇为什么我们()在上面的按钮调用中使用了 on ,那是因为我们需要实际执行按钮函数。_next_previous函数仅在按钮点击时执行,因此它们不应该出现()在 on 调用中。

呼!组件和状态之间有很多交互,但我希望你现在已经理解了它state在 React 应用中的用法。总结一下,state状态就是我们在组件中定义的任意数据,它会永远成为该组件的一部分。我们可以将它传递给另一个组件,可以更新它,还可以根据state组件当前的状态执行条件操作。

在这个示例表单中,我们使用状态来跟踪用户的输入和向导表单的当前步骤。由于 React 是从父组件到子组件的单向数据流,因此请始终记住,只有组件的所有者state才能对其进行修改或更新。

我们可以使用stateES6 Class 或 React Hooks(将在另一个教程中解释)。

还想再做个练习吗?那就开始吧!

PS:如果你正在使用 React 表单,请查看Arinich的深入教程

GitHub 搜索应用程序

现在开始我们的第三个练习,让我们实际使用一些 ES6 功能从 GitHub API 获取数据并显示其结果。本练习将涵盖我们从之前的项目和新项目中学到的所有内容:生命周期方法渲染列表

https://codepen.io/nathansebhastian/pen/LqpvrB

注意:我为这个应用编写了一些额外的 CSS。如果你没有 fork 这个 Codepen,请务必访问上面 Codepen 的 CSS 标签并粘贴它。

首先,我们来了解一下即将使用的Github API 。由于我们只通过用户名搜索,因此需要以下 API url:

https://api.github.com/search/users?q={--search-string--}
Enter fullscreen mode Exit fullscreen mode

让我们先编写一个大标题组件来准备构建应用程序。它实际上只是一个静态的 Bootstrap Jumbotron:

const Header = () => {
  return (
    <div className="jumbotron">
      <h1>Github Search App</h1>
      <h2>Search users in GitHub using this simple React application.</h2>
      <p>Click on the card to see more detail about individual user. The search default is nsebhastian (me!)</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

现在我们来考虑如何创建输入表单。我们需要:

  1. 搜索表单
  2. 提交搜索表单时调用 Github API
  3. 在卡片列表中显示搜索结果

我们首先声明 API 常量

const API = 'https://api.github.com/';
Enter fullscreen mode Exit fullscreen mode

然后让我们用两个状态值初始化“顶部”组件:searchTextdata

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      searchText: 'nsebhastian',
      data: '',
    }
  }

  fetchSearch = username => {
    let url = `${API}search/users?q=${username}`;
    fetch(url)
    .then((res) => res.json() )
    .then((data) => {
      this.setState({
        data: data
      });
    })
    .catch((error) => console.log('Oops! . There Is A Problem' + error) )
  }

  componentDidMount() {
    this.fetchSearch(this.state.searchText);
  }
Enter fullscreen mode Exit fullscreen mode

fetchSearch函数将从 API URL 获取数据,将其转换为 JSON 对象,然后data使用新获取的数据更新我们的状态。它将在组件生命周期方法中被调用componentDidMount。如果您不熟悉生命周期方法,它们基本上是在构建和渲染组件过程中的特定时间运行的方法。除此之外,还有其他方法componentDidMount,包括constructor方法。并非所有生命周期方法都经常使用,其中一些方法的使用频率会比其他方法更高。

让我们继续我们的应用程序,编写组件render的方法App

render() {
  return (
    <div>
      <MyHeader />
      <SearchForm 
        fetchSearch={this.fetchSearch}
      />
      <Profiles 
        data={this.state.data}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

通过查看代码您可能已经猜到了我们需要创建另外两个组件,即<SearchForm/><Profiles/>

让我们从 开始<SearchForm/>。我们之前在 React 中写过表单,所以这并不难。我们只需要一个文本输入框和一个提交按钮。另外,让我向你展示另一种不使用 来获取输入值的方法state

class SearchForm extends React.Component {
  render() {
    return (
        <div className="search-bar">
          <form
            className="input-group"
            onSubmit={this.handleForm}>
            <input
              type="search"
              ref="username"
              placeholder="Type Username here"
              className="form-control"/>
            <span className="input-group-btn">
              <button type="submit" className="btn btn-warning">Submit</button>
            </span>
          </form>
        </div>

    )
  }

  handleForm = event => {
    event.preventDefault();
    let username = this.refs.username.value
    this.props.fetchSearch(username);
  }
}
Enter fullscreen mode Exit fullscreen mode

如你所见,我们使用 获取用户名值ref。这样我们根本不需要初始化state。我们必须使用 ES6 类来声明组件,因为我们需要编写handleForm函数。

现在是时候编写最终的组件了<Profiles/>。我将利用这个机会向你展示组件声明——箭头函数风格

Profiles = props => {
    if(props.data){
      let data = props.data;

      if (data.message === 'Not Found')
        return (
           <div className="notfound">
              <h2>Oops !!!</h2>
              <p>The Component Couldn't Find The You Were Looking For . Try Again </p>
           </div>
        );
        else{
          // map the users into JSX elements
          let userList = data.items.map((name) => {
            return (
                <a key={name.id} href={name.html_url} target="blank">
                <div className="bs-callout bs-callout-info">
                  <img className="user" src={name.avatar_url} alt={`${name.login}`}/>
                  <h4>Username : {name.login}</h4>
                  <p> Url : {name.html_url}</p>
                  <p> Score : {name.score} </p>
                </div>
                </a>
            );
          })
          // then render it
          return (
            <div>{userList}</div>
          );
        }
    }
    else {
      return <div>Fetching data . . .</div>
    }
}
Enter fullscreen mode Exit fullscreen mode

如果你从一开始就关注本教程,我想你应该能理解这个<Profiles/>组件的作用。它会props从父组件接收命名数据,然后根据这些 props 执行一些操作。我们用map函数迭代数组并写入 JSX 元素data。然后,它就被返回并渲染了。

注意keyprops 是如何传递到<a>元素中的,以便 React 能够识别列表中的单个元素。更多详情请见此处

现在,您可以搜索并点击结果,跳转到 GitHub 用户资料。干得漂亮!我们实际上可以使用 React Router 来改进应用,并创建用户页面来详细查看每个用户的详细信息。不过,现在就到此为止吧,等我们真正了解React Router 之后,再进行 React Router 重构。

结论

我们设计了三个练习来学习 React 基础知识,从简单的静态卡片列表,到更复杂的 React 应用程序(该应用程序从 GitHub API 获取数据并显示)。我们还学习了如何使用 进行动态数据管理state

这些教程的要点是简单且可重用的 React 模式,您几乎可以在任何 React 应用程序中看到它们:

  • 组件是由方法和 JSX 组成的单个 UI。
  • JSX是用 JS 增强的 HTML,使我们能够使用 JavaScript 语法描述 UI。
  • Props是我们传递到组件的任意输入。
  • 状态用于存储动态数据。我们可以使用它来渲染 UI 并存储获取的数据。
  • 生命周期方法用于组件渲染时需要调用的方法。最简单的例子是调用 API 并获取数据

感觉怎么样?通过实际构建一些在项目中更可能用到的组件来学习 React,是不是感觉更有趣?对你来说是不是太难了?请给我一些反馈,这样我就能提高写作水平了。

我很快会介绍更多关于 React 的内容——比如如何使用 Hooks 或 React router——所以如果你对此感兴趣,请务必关注我或订阅我的新闻通讯。我保证不会给你的邮箱发垃圾邮件!

感谢阅读:)

最初发表于sebhastian.com

文章来源:https://dev.to/codewithnathan/react-101---the-practical-introduction-2ehh
PREV
关注你的情绪健康——开发者指南
NEXT
Firebase 作为 React 应用程序的简单数据库