React 101 - 实用介绍
你看过 React 官方的井字游戏教程吗?如果你看过,你可能会注意到简介里这段粗体字
您可能想跳过它,因为您没有构建游戏 - 但给它一个机会。
我猜 React 团队也知道构建井字游戏并不那么有趣,因为他们添加了那条线。虽然该项目确实让你了解了 React,但你需要有坚强的意志来完成本教程。
别误会,我很感激这个入门项目,但我就是不喜欢它。如果你也有同样的想法,我将在本教程中介绍一些可以构建的替代项目来学习 React。
那么,你可以选择哪些前端项目来学习 React 呢?在浏览教程和博客文章的过程中,我注意到一个好的 React 入门教程必须做到以下几点:
- 教授 React 的基础知识,如组件、状态和 props
- 处理动态数据、状态和属性的变化
- 展示生命周期方法的使用
嗯,实际上,主要概念选项卡中的几乎所有内容。
在本教程结束时,您将更好地理解 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>
);
}
然后创建一个渲染<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>
);
}
不要忘记ReactDOM.render
在代码底部添加调用。这段代码负责将我们的 React 应用程序引入到 HTML 元素中。
ReactDOM.render(<CardList />, document.getElementById('root'));
现在我们需要将自己的数据包含到这些卡片中,因此将一些数据传递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>
);
}
然后在我们的<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>
);
}
现在这个<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 原理。但由于我们state
在本练习中已经提到,因此我们需要引入一个新的原理:
- 状态用于存储动态数据
我们将不再只有一个表单组件,而是拥有一个父组件和三个子组件。在上图中,<MasterForm/>
父组件将通过 props 向子组件发送数据和函数,子组件将触发handleChange()
函数来设置 的状态值<MasterForm/>
。我们还需要一个函数来将表单从一个步骤移动到另一个步骤。
就像如何CardList
将道具发送给一样Card
,这些子组件将从和道具<MasterForm/>
中接收道具。value
onChange
<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>
)
}
由于子组件彼此之间看起来几乎相似,因此上面我只展示了其中一个。您可以查看演示以获取完整代码。请注意,我们使用了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 . . .
}
接下来,我们在 的 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>
)
}
由于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
我们将编写函数来检查当前步骤是 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;
}
剩下的就是渲染下一个和上一个按钮
/*
* add buttons to our form in render
*/
render(){
return(
<form onSubmit={this.handleSubmit}>
{/*
... other codes
*/}
{this.previousButton()}
{this.nextButton()}
</form>
)
}
如果你好奇为什么我们()
在上面的按钮调用中使用了 on ,那是因为我们需要实际执行按钮函数。_next
和_previous
函数仅在按钮点击时执行,因此它们不应该出现()
在 on 调用中。
呼!组件和状态之间有很多交互,但我希望你现在已经理解了它state
在 React 应用中的用法。总结一下,state
状态就是我们在组件中定义的任意数据,它会永远成为该组件的一部分。我们可以将它传递给另一个组件,可以更新它,还可以根据state
组件当前的状态执行条件操作。
在这个示例表单中,我们使用状态来跟踪用户的输入和向导表单的当前步骤。由于 React 是从父组件到子组件的单向数据流,因此请始终记住,只有组件的所有者state
才能对其进行修改或更新。
我们可以使用state
ES6 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--}
让我们先编写一个大标题组件来准备构建应用程序。它实际上只是一个静态的 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>
);
};
现在我们来考虑如何创建输入表单。我们需要:
- 搜索表单
- 提交搜索表单时调用 Github API
- 在卡片列表中显示搜索结果
我们首先声明 API 常量
const API = 'https://api.github.com/';
然后让我们用两个状态值初始化“顶部”组件:searchText
和data
。
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);
}
fetchSearch
函数将从 API URL 获取数据,将其转换为 JSON 对象,然后data
使用新获取的数据更新我们的状态。它将在组件生命周期方法中被调用componentDidMount
。如果您不熟悉生命周期方法,它们基本上是在构建和渲染组件过程中的特定时间运行的方法。除此之外,还有其他方法componentDidMount
,包括constructor
方法。并非所有生命周期方法都经常使用,其中一些方法的使用频率会比其他方法更高。
让我们继续我们的应用程序,编写组件render
的方法App
:
render() {
return (
<div>
<MyHeader />
<SearchForm
fetchSearch={this.fetchSearch}
/>
<Profiles
data={this.state.data}
/>
</div>
);
}
通过查看代码您可能已经猜到了我们需要创建另外两个组件,即<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);
}
}
如你所见,我们使用 获取用户名值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>
}
}
如果你从一开始就关注本教程,我想你应该能理解这个<Profiles/>
组件的作用。它会props
从父组件接收命名数据,然后根据这些 props 执行一些操作。我们用map
函数迭代数组并写入 JSX 元素data
。然后,它就被返回并渲染了。
注意key
props 是如何传递到<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