React 初学者完整指南
React 是我最喜欢的技术之一,所以我想写一篇 React 简介。这篇文章需要你具备 HTML 和 JavaScript 的知识——我坚信,在学习 React 之类的库之前,你应该先了解这些知识。
如果您有兴趣首先学习带有 Hooks 的 React,请查看这篇文章的 2020 年重写版!
什么是 React
React 是 Facebook 开发团队于 2013 年构建的一个 JavaScript 库,旨在使用户界面更加模块化(或可重用)且更易于维护。根据 React 官网介绍,它用于“构建可管理自身状态的封装组件,然后将它们组合起来以创建复杂的 UI”。
我将在这篇文章中使用大量 Facebook 示例,因为他们首先编写了 React。
还记得 Facebook 从点赞功能升级到回复功能的时候吗?现在,你不仅可以点赞帖子,还可以用心形、笑脸或点赞来回应任何帖子。如果这些回复功能主要用 HTML 实现,那么将所有点赞功能改为回复功能并确保其正常运行将是一项巨大的工作。
这就是 React 的作用所在——我们没有实施从第一天起就给开发人员留下深刻印象的“关注点分离”,而是在 React 中采用了一种不同的架构,它基于组件结构增加了模块化,而不是分离不同的编程语言。
今天,我们将 CSS 分开,但如果您愿意,甚至可以使该组件特定。
React 与 Vanilla JavaScript
当我们谈论“原生” JavaScript 时,通常指的是不使用 JQuery、React、Angular 或 Vue 等额外库的 JavaScript 代码。如果你想了解更多关于这些库以及框架的知识,我有一篇关于 Web 框架的文章。
开始之前的一些简短说明
- 为了使本教程更加简洁,一些代码示例在
...
其之前或之后,这意味着省略了一些代码。 - 我在某些地方使用 Git diff 来显示将要更改的代码行,因此如果您复制和粘贴,则需要删除
+
行首的。 - 我有完整的 CodePens,其中包含每个部分的完整版本 - 因此您可以使用它们来赶上进度。
- 对于本教程来说并非必不可少的更高级的概念都在块引用中,这些大多只是我认为有趣的事实。
设置
如果你正在创建一个生产级的 React 应用,那么你需要使用像 Webpack 这样的构建工具来打包代码,因为 React 使用的一些模式在浏览器中默认不起作用。Create React App在这方面非常有用,因为它可以为你完成大部分配置。
目前,由于我们希望快速启动并运行,以便能够编写实际的 React 代码,我们将使用 React CDN,它仅用于开发目的。我们还将使用 Babel CDN,以便使用一些非标准的 JavaScript 功能(稍后我们将详细讨论)。
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.25.0/babel.min.js"></script>
我还制作了一个Codepen 模板供您使用!
在完整的 React 项目中,我会将组件拆分成不同的文件,但同样,出于学习目的,我们现在将 JavaScript 合并到一个文件中。
成分
在本教程中,我们将构建一个 Facebook 状态小部件,因为 Facebook 首先编写了 React。
想想这个小部件在 Facebook 上有多少地方like
出现——你可以给某个状态点赞,或者给某个链接帖子点赞,或者给某个视频帖子点赞,或者给一张图片点赞,甚至给一个页面点赞!每次 Facebook 调整点赞功能时,他们都不想在所有这些地方都进行修改。所以,组件就派上用场了。网页中所有可复用的部分都被抽象成一个可以反复使用的组件,我们只需要在一个地方修改代码就可以更新它。
让我们看一张 Facebook 状态的图片并分解其中的不同组成部分。
状态本身将是一个组件——Facebook 时间线内有很多状态,因此我们肯定希望能够重复使用状态组件。
在该组件中,我们将拥有子组件或父组件内的组件。这些组件也是可重用的——因此,我们可以将“点赞”按钮组件设置为父PhotoStatus
组件和LinkStatus
父组件的子组件。
也许我们的子组件看起来像这样:
我们甚至可以在子组件中嵌套子组件!所以,点赞、评论和分享的分组可以是一个独立的ActionBar
组件,其中包含点赞、评论和分享的组件!
根据您在应用程序中重复使用这些功能的位置,您可以使用多种方法来分解这些组件和子组件。
入门
我想用 React 的“Hello World”来开始这个教程——毕竟这是传统!然后我们再来看稍微复杂一点的状态示例。
在我们的 HTML 文件中,我们只添加一个元素 ——div
带有 id 的 a 元素。按照惯例,你通常会看到 div 元素带有 id "root",因为它将成为我们 React 应用的根元素。
<div id="root"></div>
如果你在CodePen 模板中编写代码,则可以直接在js
部分中编写此 JavaScript。如果你在计算机上编写此代码,则必须添加一个带有 类型的 script 标签text/jsx
,因此:
<script type="text/jsx"></script>
现在,让我们开始我们的 React 代码!
class HelloWorld extends React.Component {
render() {
// Tells React what HTML code to render
return <h1>Hello World</h1>
}
}
// Tells React to attach the HelloWorld component to the 'root' HTML div
ReactDOM.render(<HelloWorld />, document.getElementById("root"))
所发生的一切就是“Hello World”作为 H1 显示于页面上!
让我们来看看这里发生了什么。
首先,我们使用一个继承自该类的 ES6 类React.Component
。我们将在大多数 React 组件中都使用这种模式。
接下来,我们的类中有一个方法——一个叫做 的特殊方法render
。React 会查找这个render
方法来决定在页面上渲染什么。这个名字很有意义。从该render
方法返回的任何内容都将由该组件渲染。
在这种情况下,我们返回带有文本“Hello World”的 H1——这正是 HTML 文件中通常的内容。
最后,我们有:
ReactDOM.render(<HelloWorld />, document.getElementById("root"))
我们正在使用 ReactDOM 功能将我们的反应组件附加到 DOM。
React 使用了一种称为虚拟 DOM 的技术,它是您在原生 JavaScript 或 JQuery 中通常与之交互的 DOM 的虚拟表示。它
reactDOM.render
会将虚拟 DOM 渲染为实际的 DOM。在后台,当界面上的内容需要更改时,React 会进行大量工作来高效地编辑和重新渲染 DOM。
我们的组件<HelloWorld />
看起来像一个 HTML 标签!这个语法是JSX的一部分,JSX 是 JavaScript 的扩展。你无法在浏览器中原生使用它。还记得我们是如何用 Babel 处理 JavaScript 的吗?Babel 会将我们的 JSX 转译(或转换)成常规 JavaScript,以便浏览器能够理解。
JSX 在 React 中实际上是可选的,但你会看到它在绝大多数情况下被使用。
然后,我们使用 JavaScript 的内置功能document.getElementById
来获取我们在 HTML 中创建的根元素。
总而言之,在这个ReactDOM.render
语句中,我们将我们的HelloWorld
组件附加到我们div
在 HTML 文件中创建的组件。
入门代码
好的——现在我们已经完成了“Hello World”,我们可以开始使用我们的 Facebook 组件了。
首先,我希望你能够尝试一下这个 demo。我们将在本教程的剩余部分继续进行这方面的工作。你也可以随意查看代码,但不必担心看不懂。这正是本教程剩余部分的目的!
让我们首先对小部件的 HTML 进行“硬编码”:
<div class="content">
<div class="col-6 offset-3">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-2">
<img src="https://zen-of-programming.com/react-intro/selfiesquare.jpg" class="profile-pic">
</div>
<div class="col-10 profile-row">
<div class="row">
<a href="#">The Zen of Programming</a>
</div>
<div class="row">
<small class="post-time">10 mins</small>
</div>
</div>
</div>
<p>Hello World!</p>
<div>
<span class="fa-stack fa-sm">
<i class="fa fa-circle fa-stack-2x blue-icon"></i>
<i class="fa fa-thumbs-up fa-stack-1x fa-inverse"></i>
</span>
</div>
<div>
<hr class="remove-margin">
<div>
<button type="button" class="btn no-outline btn-secondary">
<i class="fa fa-thumbs-o-up fa-4 align-middle" aria-hidden="true"></i>
<span class="align-middle">Like</span>
</button>
</div>
</div>
</div>
<div class="card-footer text-muted">
<textarea class="form-control" placeholder="Write a comment..."></textarea>
<small>120 Remaining</small>
</div>
</div>
</div>
</div>
添加一些 CSS 后,看起来如下所示:
为了本教程的目的,我们将创建四个组件:一个Status
作为父组件的组件,一个Like
包含点赞逻辑的组件,以及一个Comment
包含输入评论逻辑的组件。该Like
组件还将包含一个子组件LikeIcon
,用于控制点赞按钮的显示或隐藏。
组件架构
让我们继续将我们编写的 HTML 代码分成这些组件。
我们将从组件的外壳开始,并且我们还将渲染它以确保它正常工作!
class Status extends React.Component {
render() {
return (
<div className="col-6 offset-3">
<div className="card">
<div className="card-block">
<div className="row">
<div className="col-10 profile-row">
<div className="row">
<a href="#">The Zen of Programming</a>
</div>
<div class="row">
<small className="post-time">10 mins</small>
</div>
</div>
</div>
</div>
<p>Hello world!</p>
<div className="card-footer text-muted" />
</div>
</div>
)
}
}
ReactDOM.render(<Status />, document.getElementById("root"))
关于上述内容,有一点需要注意,那就是我们必须将“class”属性改为“className”。Class 在 JavaScript 中已经有一定的含义了——它指的是 es6 中的类!某些属性在 JSX 中的命名与 HTML 中的不同。
我们还可以删除 HTML 的内容,只留下 ID 为 root 的元素 —— 父“内容”div 仅用于样式。
<body>
<div class="content">
<div id="root"></div>
</div>
</body>
这是要添加到 Status 组件中的 HTML。请注意,部分原始 HTML 尚未添加到 Status 组件中,它们将添加到我们的子组件中。
让我们创建第二个组件,然后将其包含在我们的Status
组件中。
class Comment extends React.Component {
render() {
return (
<div>
<textarea className="form-control" placeholder="Write a comment..." />
<small>140 Remaining</small>
</div>
)
}
}
这是我们评论的组件。它只包含textarea
需要输入的内容,以及剩余字符数的文本。注意,两者都被包裹在一个 HTML 标签中div
——这是因为 React 要求我们将组件的所有内容包裹在一个 HTML 标签中——如果没有父级,div
我们将返回一个标签textarea
和一个 HTMLsmall
标签。
所以,现在我们需要将它包含在我们的Status
组件中,因为它将成为我们的子组件。我们可以使用渲染 Status 组件时使用的 JSX 语法来实现。
class Status extends React.Component {
render() {
return (
<div className="col-6 offset-3">
<div className="card">
<div className="card-block">
<div className="row">
<div className="col-10 profile-row">
<div className="row">
<a href="#">The Zen of Programming</a>
</div>
<div className="row">
<small className="post-time">10 mins</small>
</div>
</div>
</div>
</div>
<div className="card-footer text-muted">
+ <Comment />
</div>
</div>
</div>
)
}
}
好的,现在我们只需要对我们的喜欢做同样的事情!
class LikeIcon extends React.Component {
render() {
return (
<div>
<span className="fa-stack fa-sm">
<i className="fa fa-circle fa-stack-2x blue-icon" />
<i className="fa fa-thumbs-up fa-stack-1x fa-inverse" />
</span>
</div>
)
}
}
class Like extends React.Component {
render() {
return (
<div>
{/* Include the LikeIcon subcomponent within the Like component*/}
<LikeIcon />
<hr />
<div>
<button type="button">
<i
className="fa fa-thumbs-o-up fa-4 align-middle"
aria-hidden="true"
/>
<span className="align-middle">Like</span>
</button>
</div>
</div>
)
}
}
然后我们需要将其包含到我们原来的Status
组件中!
class Status extends React.Component {
render() {
return (
<div className="col-6 offset-3">
<div className="card">
<div className="card-block">
<div className="row">
<div className="col-10 profile-row">
<div className="row">
<a href="#">The Zen of Programming</a>
</div>
<div className="row">
<small className="post-time">10 mins</small>
</div>
</div>
</div>
+ <Like />
</div>
<div className="card-footer text-muted">
<Comment />
</div>
</div>
</div>
)
}
}
太棒了,现在我们已经将原始 HTML 用 React 进行了处理,但它仍然没有任何作用!让我们开始修复它!
总而言之,本节的代码将类似于CodePen!
状态和属性
我们想要实现两种不同的用户交互:
- 我们希望只有按下“点赞”按钮时才会显示“点赞”图标
- 我们希望剩余的字符数随着人物的
让我们开始着手解决这些问题吧!
道具
想象一下,我们希望评论框在不同位置允许输入不同数量的字符。例如,在状态栏上,我们希望用户能够写 200 个字符的回复。然而,在图片上,我们只希望他们能够写 100 个字符的回复。
PictureStatus
React 允许我们从组件和组件传递 props(属性的缩写)来Status
指定我们希望在响应中允许多少个字母,而不是有两个不同的评论组件。
props 的语法如下所示:
<Comment maxLetters={20} />
<Comment text='hello world' />
<Comment show={false} />
var test = 'hello world'
<Comment text={test} />
props 看起来像 HTML 属性!如果通过 props 传递字符串,则不需要括号,但其他数据类型或变量都需要放在括号内。
然后,在我们的组件中,我们可以使用我们的道具:
console.log(this.props.maxLetters)
props
它们在实例的属性中捆绑在一起,以便可以通过 访问它们this.props.myPropName
。
因此,让我们将硬编码的 140 个字符更改为可在组件外部轻松更改。
首先,我们将更改在 Status 组件中实例化 Comment 组件的位置(注意省略了一些代码!):
class Status extends React.Component {
...
<div className="card-footer text-muted">
+ <Comment maxLetters={280} />
</div>
</div>
</div>
)
}
}
然后我们将更改评论组件中的硬编码 140 个字符的限制。
class Comment extends React.Component {
...
<div>
<textarea className="form-control" placeholder="Write a comment..." />
+ <small>{this.props.maxLetters} Remaining</small>
</div>
...
}
状态
我们从一个组件传递到另一个组件的 props在子组件中永远不会改变——它们可以在父组件中改变,但不能在子组件中改变。但是——很多时候,我们会有一些属性需要在组件的生命周期内更改。例如,我们想要记录用户在 textarea 中输入的字符数,以及跟踪状态是否为“喜欢”。我们会将这些需要在组件中更改的属性存储在其state中。
您会注意到 React 中有很多不变性——它受到功能范式的极大影响,因此也不鼓励产生副作用。
我们希望每次创建组件的新实例时都能创建此状态,因此我们将使用 ES6 类构造函数来创建它。如果您想快速回顾一下 ES6 类,MDN是一个不错的资源。
State 是一个包含我们想要包含的任何键值对的对象。在本例中,我们需要一个 characterCount 值,用于记录用户输入的字符数。我们暂时将其设置为零。
class Comment extends React.Component {
constructor () {
super()
this.state = {
characterCount: 0
}
}
...
现在让我们从maxLetters
道具中减去它,这样我们就知道还剩下多少个字符!
<small>{this.props.maxLetters - this.state.characterCount} Remaining</small>
如果增加characterCount
,显示的剩余字符数就会减少。
但是——当你输入时什么也没有发生。我们永远不会改变 的值characterCount
。我们需要为 添加一个事件处理程序,以便在用户输入时textarea
更改。characterCount
事件处理程序
你以前写过 JavaScript 的时候,可能写过事件处理程序来与用户输入交互。在 React 中我们也要做同样的事,只是语法略有不同。
我们将为 添加一个onChange
处理程序textarea
。在其中,我们将放置一个事件处理方法的引用,该方法将在用户每次输入 时运行textarea
。
<textarea className="form-control" placeholder="Write a comment..." onChange={this.handleChange}/>
现在我们需要创建一个handleChange
方法:
class Comment extends React.Component {
constructor () {
super()
this.state = {
characterCount: 0
}
}
handleChange (event) {
console.log(event.target.value)
}
...
现在,我们只是console.log
-ing 了event.target.value
- 它将以与非 React JavaScript 相同的方式工作(尽管如果你深入研究一下,事件对象会略有不同)。如果你查看控制台,你会发现我们正在打印出我们在文本框中输入的内容!
现在我们需要更新characterCount
state 中的属性。在 React 中,我们永远不会直接修改 state,所以我们不能这样做:this.state.characterCount = event.target.value.length
。我们需要使用this.setState
方法。
handleChange (event) {
this.setState({
characterCount: event.target.value.length
})
}
但是!你收到了一个错误——“Uncaught TypeError: this.setState is not a function”。这个错误告诉我们需要在事件处理程序中保留 ES6 类的上下文。我们可以通过this
在构造函数中绑定方法来做到这一点。如果你想了解更多,这里有一篇很棒的文章。
class Comment extends React.Component {
constructor () {
super()
this.handleChange = this.handleChange.bind(this)
...
好的!快完成了!我们只需要添加切换like
显示的功能。
我们需要为组件添加一个构造函数Like
。在该构造函数中,我们需要实例化组件的状态。在组件的生命周期内,状态是否被点赞会发生变化。
class Like extends React.Component {
constructor() {
super()
this.state = {
liked: false
}
}
...
现在我们需要添加一个事件处理程序来改变状态是否被喜欢。
class Like extends React.Component {
constructor() {
super()
this.state = {
liked: false
}
this.toggleLike = this.toggleLike.bind(this)
}
toggleLike () {
this.setState(previousState => ({
liked: !previousState.liked
}))
}
...
this.setState
这里的区别在于,接收参数 --的回调函数previousState
。正如您可能从参数名称中猜到的那样,这是this.setState
调用之前的状态值。setState
是异步的,因此我们不能依赖于this.state.liked
其中的使用。
现在,我们需要:
a) 每当用户点击“赞”按钮时调用事件处理程序: b) 仅在为 true
时显示 LikeIconliked
render() {
return (
<div>
{/* Use boolean logic to only render the LikeIcon if liked is true */}
+ {this.state.liked && <LikeIcon />}
<hr />
<div>
+ <button type="button" className="btn no-outline btn-secondary" onClick={this.toggleLike}>
<i
className="fa fa-thumbs-o-up fa-4 align-middle"
aria-hidden="true"
/>
<span className="align-middle">Like</span>
</button>
</div>
</div>
)
}
太棒了!现在我们所有的功能都已经到位了。
福利:功能组件
如果你觉得已经够难了,可以直接跳过这部分,但我想对这个项目再做一次快速重构。如果我们创建没有关联状态的组件(我们称之为无状态组件),我们可以将组件变成函数,而不是 ES6 类。
在这种情况下,我们LikeIcon
可能看起来像这样:
const LikeIcon = () => {
return (
<div>
<span className="fa-stack fa-sm">
<i className="fa fa-circle fa-stack-2x blue-icon" />
<i className="fa fa-thumbs-up fa-stack-1x fa-inverse" />
</span>
</div>
)
}
我们只是返回组件的 UI,而不是使用该render
方法。
这是实现此重构的 CodePen。
备忘单
我喜欢备忘单,所以我用这篇文章的内容制作了一个!
您也可以在此处将其下载为 PDF !
后续步骤
回顾一下,我们讨论了组件架构、基本的 React 语法和 JSX、状态和属性、事件处理程序和功能组件。
如果您想查看本教程中的所有 CodePens,这里有一个集合!
如果您想尝试扩展本教程中的代码,我建议将喜欢更改为反应或创建一个可重复使用我们制作的一些组件的照片组件!
此外,这里还有一些学习 React 的好地方:
保持联系
如果你对更多类似的帖子感兴趣,我还有另外两篇初学者指南:一篇是CSS,一篇是Vue
您还可以关注我的推特,以了解我的最新帖子。
文章来源:https://dev.to/aspittel/a-complete-beginners-guide-to-react-2cl6