2018 年 React.js 综合指南
本文最初发表于 2015 年 1 月,但最近已更新至 React 16.3 及其包含的所有优点。
React.js基础知识:
组件是 React 的基石。如果你有 Angular 背景,组件与指令非常相似。如果你有其他背景,它们本质上是小部件或模块。你可以将组件视为 HTML、CSS、JS 以及一些特定于该组件的内部数据的集合。我喜欢将 React 组件视为Web 上的Kolaches。它们拥有你所需的一切,并包装在一个美味的可组合包中。这些组件可以用纯 JavaScript 定义,也可以用 React 团队称之为“JSX”的语言定义。如果你决定使用 JSX(你很可能会使用,它非常标准——这也是我们在本教程中将使用的一种语言),你需要一些编译阶段将 JSX 转换为 JavaScript,我们稍后会讲到这一点。
React 构建用户界面如此便捷的原因在于,数据要么从组件的父组件接收,要么包含在组件本身中。在开始编写代码之前,我们先来了解一下组件的大致情况。
上面是我的 Twitter 个人资料图片。如果我们要在 React 中重新创建这个页面,我们会将不同的部分拆分成不同的组件(突出显示)。注意,组件内部可以嵌套组件。我们可以将左侧组件(粉色)命名为UserInfo
组件。UserInfo
组件内部还有另一个组件(橙色),我们可以将其命名为UserImages
组件。这种父子关系的工作原理是,我们的UserInfo
组件(或父组件)是其自身和组件(子组件)数据“状态”的UserImages
存储地。如果我们想在子组件中使用父组件的任何部分数据(我们确实这样做了),我们会将该数据作为属性传递给子组件。在这个例子中,我们将UserImages
用户拥有的所有图片(当前位于UserInfo
组件中)传递给组件。稍后我们将更详细地介绍代码,但我希望您能够更全面地了解这里发生的事情。这种父子层次结构使我们的数据管理相对简单,因为我们确切地知道数据的位置,并且我们不应该在其他任何地方操作这些数据。
以下主题是我认为 React 的基本方面。如果你理解了所有这些主题及其用途,那么读完本教程后,你将受益匪浅。
JSX — Allows us to write HTML like syntax which gets
transformed to lightweightJavaScript objects.
Virtual DOM — A JavaScript representation of the actual
DOM.
React.Component — The way in which you create a new component.
render (method) — Describes what the UI will look like for
the particular component.
ReactDOM.render — Renders a React component to a DOM node.
state — The internal data store (object) of a component.
constructor (this.state) - The way in which you establish
the initial state of a component.
setState — A helper method used for updating the state of a
component and re-rendering the UI
props — The data which is passed to the child component
from the parent component.
propTypes — Allows you to control the presence, or types of
certain props passed to the child component.
defaultProps — Allows you to set default props for your component.
Component LifeCycle
- componentDidMount — Fired after the component mounted
- componentWillUnmount — Fired before the component will unmount
- getDerivedStateFromProps - Fired when the component mounts and
whenever the props change. Used to update the state of a
component when its props change
Events
- onClick
- onSubmit
- onChange
我知道这看起来很多,但你很快就会看到每个部分对于使用 React 构建强大的应用程序是多么重要(当我说我希望这是一个全面的指南时,我并没有开玩笑)。
到这里,你应该已经大致了解了 React 的工作原理。现在,我们来看一些代码。
创建您的第一个组件(JSX、虚拟 DOM、render、ReactDOM.render)
让我们继续构建我们的第一个 React 组件。
要创建 React 组件,你需要使用 ES6 类。如果你不熟悉类,可以继续阅读下文,或者点击此处深入了解。
import React from 'react'
import ReactDOM from 'react-dom'
class HelloWorld extends React.Component {
render() {
return (
<div>Hello World!</div>
)
}
}
ReactDOM.render(<HelloWorld />, document.getElementById('root'));
请注意,我们类中唯一的方法是render
。每个组件都需要有一个 render 方法。原因是 render 描述了组件的 UI(用户界面)。所以在这个例子中,在渲染此组件的屏幕上显示的文本是 Hello World!现在让我们看看 ReactDOM 在做什么。ReactDOM.render 接受两个参数。第一个参数是要渲染的组件,第二个参数是要渲染组件的 DOM 节点。(请注意,我们使用的是 ReactDOM.render 而不是 React.render。这是 React .14 中的一项更改,旨在使 React 更加模块化。当你认为 React 可以渲染到更多东西而不仅仅是 DOM 元素时,这是有道理的)。在上面的例子中,我们告诉 React 获取我们的 HelloWorld 组件并将其渲染到 ID 为 的元素root
。由于我们之前讨论过的 React 的父/子关系,你通常只需要在应用程序中使用一次 ReactDOM.render,因为通过渲染最父组件,所有子组件也将被渲染。
现在,您可能会觉得将“HTML”放入 JavaScript 中有点奇怪。自从您开始学习 Web 开发以来,您就被告知应该将逻辑放在视图之外,也就是说,将 JavaScript 与 HTML 分离。这种范例很强大,但也存在一些弱点。我不想让本教程太长,试图说服您这个想法是朝着正确方向迈出的一步,所以如果这个想法仍然困扰您,您可以查看此链接。随着您对 React 的了解越来越多,这种不安应该会很快消退。您在 render 方法中编写的“HTML”实际上不是 HTML,而是 React 所说的“JSX”。JSX 只是允许我们编写类似 HTML 的语法,这些语法(最终)会转换为轻量级 JavaScript 对象。然后,React 能够获取这些 JavaScript 对象,并从它们形成“虚拟 DOM”或实际 DOM 的 JavaScript 表示。这将创建一个双赢的局面,您可以获得模板的可访问性以及 JavaScript 的强大功能。
看下面的例子,这就是你的 JSX 最终将被编译成的内容。
class HelloWorld extends React.Component {
render() {
return React.createElement("div", null, "Hello World");
}
}
现在,你可以放弃 JSX -> JS 的转换阶段,
像上面的代码一样编写 React 组件,但可以想象,这会相当棘手。
据我所知,没有人不使用 JSX。有关 JSX 编译结果的更多信息,
请查看React Elements vs React Components
到目前为止,我们还没有真正强调过我们正在使用的这种新的虚拟 DOM 范式的重要性。React 团队之所以采用这种方法,是因为虚拟 DOM 是实际 DOM 的 JavaScript 表示,因此 React 可以跟踪当前虚拟 DOM(在某些数据更改后计算)与前一个虚拟 DOM(在某些数据更改前计算)之间的差异。然后,React 会隔离新旧虚拟 DOM 之间的更改,并且仅使用必要的更改来更新实际 DOM 。更通俗地说,由于操作实际 DOM 的速度很慢,因此 React 能够通过跟踪虚拟 DOM 并仅在必要时使用必要的更改来更新实际 DOM,从而最大限度地减少对实际 DOM 的操作。(更多信息请点击此处)。通常,UI 具有大量状态,这使得状态管理变得困难。通过在每次发生任何状态更改时重新渲染虚拟 DOM,React 可以更轻松地思考应用程序所处的状态。
该过程如下所示,
一些改变应用程序状态的用户事件 → 重新渲染虚拟 DOM → 将之前的虚拟DOM与新的虚拟 DOM 进行区分 → 仅使用必要的更改来更新真实 DOM。
由于存在从 JSX 到 JS 的转换过程,因此您需要在开发过程中设置某种转换阶段。在本系列的第二部分中,我将介绍如何使用 Webpack 和 Babel 进行这种转换。
让我们回顾一下“React 的最重要部分”清单,看看我们现在处于什么位置。
<b>JSX — Allows us to write HTML like syntax which gets transformed
to lightweight JavaScript objects.</b>
<b>Virtual DOM — A JavaScript representation of the actual
DOM.</b>
<b>React.Component — The way in which you create a new component.</b>
<b>render (method) — Describes what the UI will look like for
the particular component.</b>
<b>ReactDOM.render — Renders a React component to a DOM node.</b>
state — The internal data store (object) of a component.
constructor (this.state) - The way in which you establish
the initial state of a component.
setState — A helper method used for updating the state of a
component and re-rendering the UI
props — The data which is passed to the child component
from the parent component.
propTypes — Allows you to control the presence, or types of
certain props passed to the child component.
defaultProps — Allows you to set default props for your component.
Component LifeCycle
- componentDidMount — Fired after the component mounted
- componentWillUnmount — Fired before the component will unmount
- getDerivedStateFromProps - Fired when the component mounts and
whenever the props change. Used to update the state of a
component when its props change
Events
- onClick
- onSubmit
- onChange
我们进展顺利。所有粗体部分都是我们已经讲过的内容,你
至少应该能够解释这些组件是如何融入
React 生态系统的。
向组件添加状态(state)
接下来是state
。之前我们讨论了管理用户
界面是多么困难,因为它们通常具有许多不同的状态。
这是 React 真正开始闪耀的地方。每个组件都能够管理自己的状态并在需要时将其状态传递给子组件。回到之前的 Twitter 示例,UserInfo
组件(上面以粉红色突出显示)负责管理用户信息的状态(或数据)。如果另一个组件也需要此状态/数据,但该状态不是该UserInfo
组件的直接子组件,那么您将创建另一个组件作为的直接父组件UserInfo
和另一个组件(或两个都需要该状态的组件),然后将状态作为 props 传递给子组件。换句话说,如果您有多组件层次结构,则公共父组件应该管理状态并通过 props 将其传递给其子组件。
让我们看一个使用它自己的内部状态的示例组件。
class HelloUser extends React.Component {
constructor(props) {
super(props)
this.state = {
username: 'tylermcginnis'
}
}
render() {
return (
<div>
Hello {this.state.username}
</div>
)
}
}
我们在这个例子中引入了一些新的语法。首先你会注意到的是构造函数方法。根据上面的定义,构造函数方法是“设置组件状态的方式”。换句话说,你this.state
在构造函数中放入的任何数据都将成为该组件状态的一部分。在上面的代码中,我们告诉组件我们希望它跟踪一个username
。username
现在可以通过在组件内部执行 来使用它{this.state.username}
,这正是我们在 render 方法中所做的。
关于状态,最后要讨论的是,我们的组件需要能够修改其自身的内部状态。我们使用名为setState的方法来实现这一点。还记得之前我们讨论过数据发生变化时虚拟 DOM 的重新渲染吗?
发出信号通知我们的应用程序某些数据已更改→重新渲染虚拟 DOM→将之前的虚拟 DOM 与新的虚拟 DOM 进行区分→仅对真实 DOM 进行必要的更改。
那个“通知应用某些数据已更改的信号”实际上就是 setState。每当调用 setState 时,虚拟 DOM 都会重新渲染,diff 算法会运行,并且真实 DOM 会根据必要的更改进行更新。
补充一下,当我们在下面的代码中引入 setState 时,我们也会引入列表中的几个事件。一举两得。
因此,在下一个代码示例中,我们将有一个输入框,每当有人在其中输入时,它都会自动更新我们的状态并更改用户名。
class HelloUser extends React.Component {
constructor(props) {
super(props)
this.state = {
username: 'tylermcginnis'
}
this.handleChange = this.handleChange.bind(this)
}
handleChange (e) {
this.setState({
username: e.target.value
})
}
render() {
return (
<div>
Hello {this.state.username} <br />
Change Name:
<input
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
</div>
)
}
}
注意,我们还引入了一些其他的东西。首先是handleChange
方法。每次用户在输入框中输入内容时,都会调用此方法。handleChange
调用时,它会setState
根据输入框中输入的内容(e.target.value)重新定义用户名。记住,每次setState
调用时,React 都会创建一个新的虚拟 DOM,进行 diff 操作,然后更新真实 DOM。
现在让我们看看我们的渲染方法。我们添加了一行新代码,其中包含一个输入字段。输入字段的类型显然是text
。其值将是用户名的值,该用户名最初在 getInitialState 方法中定义,并将在handleChange
方法中更新。请注意,这里有一个你可能从未见过的新属性onChange
。onChange
是 React 的特性,每次输入框中的值发生变化时,它都会调用你指定的方法,在本例中,我们指定的方法是handleChange
。
上述代码的流程大致如下。
用户在输入框中输入内容 → 调用 handleChange → 将组件的状态设置为新值 → React 重新渲染虚拟 DOM → React Diff 更改 → 更新真实 DOM。
稍后当我们介绍 props 时,我们将看到一些处理状态的更高级的用例。
我们快到了!如果你无法解释下面粗体部分的内容,请重新阅读该部分。关于真正学习 React 的一个建议是,不要让被动阅读这篇文章给你一种虚假的安全感,让你以为自己真的了解发生了什么,并且可以重现我们正在做的事情。前往 CodeSandbox,尝试在不参考我之前内容的情况下重新创建(或创建你自己的)组件。这是你真正开始学习如何使用 React 构建的唯一方法。这适用于本教程以及后续教程。
<b>JSX — Allows us to write HTML like syntax which gets transformed
to lightweight JavaScript objects.</b>
<b>Virtual DOM — A JavaScript representation of the actual
DOM.</b>
<b>React.Component — The way in which you create a new component.</b>
<b>render (method) — Describes what the UI will look like for the particular component.</b>
<b>ReactDOM.render — Renders a React component to a DOM node.</b>
<b>state — The internal data store (object) of a component.</b>
<b>constructor (this.state) - The way in which you establish the initial state of a component.</b>
<b>setState — A helper method used for updating the state of a
component and re-rendering the UI</b>
props — The data which is passed to the child component from the parent component.
propTypes — Allows you to control the presence, or types of certain props passed to the child component.
defaultProps — Allows you to set default props for your component.
Component LifeCycle
- componentDidMount — Fired after the component mounted
- componentWillUnmount — Fired before the component will unmount
- getDerivedStateFromProps - Fired when the component mounts and
whenever the props change. Used to update the state of a
component when its props change
Events
- onClick
- onSubmit
- onChange
从父组件接收状态(props、propTypes、getDefaultProps)
我们已经讨论过几次 props 了,因为没有它们真的很难做很多事情。根据我们上面的定义,props 是从父组件传递给子组件的数据。这使得我们的 React 架构保持相当简洁。在需要使用特定数据的最高层父组件中处理状态,如果你的子组件也需要这些数据,就将这些数据作为 props 向下传递。
这是一个使用道具的非常基本的例子。
class HelloUser extends React.Component {
render() {
return (
<div> Hello, {this.props.name}</div>
)
}
}
ReactDOM.render(<HelloUser name="Tyler"/>, document.getElementById('root'));
注意,在第 9 行,我们有一个名为 name 的属性,其值为“Tyler”。现在,在我们的组件中,我们可以使用它{this.props.name}
来获取“Tyler”。
我们来看一个更高级的例子。现在有两个组件。一个父组件,一个子组件。父组件会跟踪状态,并将部分状态作为 props 传递给子组件。我们先来看看父组件。
父组件:
class FriendsContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Tyler McGinnis',
friends: ['Jake Lingwall', 'Sarah Drasner', 'Merrick Christensen']
}
}
render() {
return (
<div>
<h3> Name: {this.state.name} </h3>
<ShowList names={this.state.friends} />
</div>
)
}
}
这个组件里其实没什么我们之前没见过的功能。我们有一个初始状态,并将该初始状态的一部分传递给另一个组件。大部分新代码将来自这个子组件,所以让我们仔细看看它。
子组件:
class ShowList extends React.Component {
render() {
return (
<div>
<h3> Friends </h3>
<ul>
{this.props.names.map((friend) => <li>{friend}</li>)}
</ul>
</div>
)
}
}
请记住,从 render 方法返回的代码代表了真实 DOM 应该是什么样子。如果你不熟悉Array.prototype.map,这段代码可能看起来有点奇怪。map 所做的就是创建一个新数组,对数组中的每个元素调用我们的回调函数,并将对每个元素调用回调函数的结果填充到新数组中。例如,
const friends = ['Jake Lingwall', 'Sarah Drasner', 'Merrick Christensen'];
const listItems = friends.map((friend) => {
return "<li> " + friend + "</li>";
});
console.log(listItems);
// ["<li> Jake Lingwall</li>", "<li> Sarah Drasner</li>", "<li> Merrick Christensen</li>"];
上面的 console.log 返回 。["<li> Jake Lingwall</li>", "<li> Murphy
Randall</li>", "<li> Merrick Christensen</li>"]
请注意,发生的一切是我们
创建了一个新数组并将其添加<li> </li>
到原始数组中的每个项目。
map 的优点在于它完美适配 React(并且内置于 JavaScript)。因此,在上面的子组件中,我们通过映射名称,将每个名称包装在一对
tags, and saving that to our listItems variable. Then, our render method returns an unordered list with all of our friends.
Let’s look at one more example before we stop talking about props. It’s important to understand that wherever the data lives, is the exact place you want to manipulate that data. This keeps it simple to reason about your data. All getter/setter method for a certain piece of data will always be in the same component where that data was defined. If you needed to manipulate some piece of data outside where the data lives, you’d pass the getter/setter method into that component as props. Let’s take a look at an example like that.
```javascript
class FriendsContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Tyler McGinnis',
friends: [
'Jake Lingwall',
'Sarah Drasner',
'Merrick Christensen'
],
}
this.addFriend = this.addFriend.bind(this)
}
addFriend(friend) {
this.setState((state) => ({
friends: state.friends.concat([friend])
}))
}
render() {
return (
<div>
<h3> Name: {this.state.name} </h3>
<AddFriend addNew={this.addFriend} />
<ShowList names={this.state.friends} />
</div>
)
}
}
🚨。请注意,我们在 addFriend 方法中引入了一种调用 setState 的新方法。我们不再传递对象,而是传递一个函数,然后再传递
state
。每当你根据先前的状态设置组件的新状态时(就像我们对 friends 数组所做的那样),你都需要向 setState 传递一个函数,该函数接收当前状态并返回与新状态合并的数据。
class AddFriend extends React.Component {
constructor(props) {
super(props)
this.state = {
newFriend: ''
}
this.updateNewFriend = this.updateNewFriend.bind(this)
this.handleAddNew = this.handleAddNew.bind(this)
}
updateNewFriend(e) {
this.setState({
newFriend: e.target.value
})
}
handleAddNew() {
this.props.addNew(this.state.newFriend)
this.setState({
newFriend: ''
})
}
render() {
return (
<div>
<input
type="text"
value={this.state.newFriend}
onChange={this.updateNewFriend}
/>
<button onClick={this.handleAddNew}> Add Friend </button>
</div>
)
}
}
class ShowList extends React.Component {
render() {
return (
<div>
<h3> Friends </h3>
<ul>
{this.props.names.map((friend) => {
return <li> {friend} </li>
})}
</ul>
</div>
)
}
}
你会注意到上面的代码与前一个示例基本相同,只是现在我们可以在好友列表中添加姓名了。注意我创建了一个新的 AddFriend 组件来管理我们要添加的新好友。这样做的原因是,父组件(FriendContainer)并不关心你添加的新好友,它只关心你所有好友的整体情况(好友数组)。但是,由于我们坚持只从关心数据的组件操作数据的规则,我们将 addFriend 方法作为 prop 传递给了 AddFriend 组件,并在调用 handleAddNew 方法后,将新好友传递给它。
此时,我建议您在卡住 3-4 分钟后,尝试使用上面的代码作为指导,自行重新创建相同的功能。
在继续讨论 props 之前,我想介绍一下 React 中与 props 相关的另外两个特性:propTypes和defaultProps。由于它们都比较简单易懂,这里我就不赘述了。
prop-types允许你控制传递给子组件的某些 props 的存在状态或类型。使用 propTypes,你可以指定某些 props 是必需的,或者将某些 props 指定为特定类型。
从 React 15 开始,PropTypes 不再包含在 React 包中。你需要单独运行 来安装它
npm install prop-types
。
defaultProps允许您为某些 props 指定默认(或备份)值,以防这些 props 从未传递到组件中。
我修改了之前的组件,使用 propTypes 要求 addFriend 是一个函数,并将其传递给 AddFriend 组件。我还使用 defaultProps 指定,如果 ShowList 组件没有传入任何好友数组,则默认为空数组。
import React from 'react'
import PropTypes from 'prop-types'
class AddFriend extends React.Component {
constructor(props) {
super(props)
this.state = {
newFriend: ''
}
}
updateNewFriend(e) {
this.setState({
newFriend: e.target.value
})
}
handleAddNew() {
this.props.addNew(this.state.newFriend)
this.setState({
newFriend: ''
})
}
render() {
return (
<div>
<input type="text" value={this.state.newFriend} onChange={this.updateNewFriend} />
<button onClick={this.handleAddNew}> Add Friend </button>
</div>
)
}
}
AddFriend.propTypes: {
addNew: PropTypes.func.isRequired
}
class ShowList extends React.Component {
render() {
return (
<div>
<h3> Friends </h3>
<ul>
{this.props.names.map((friend) => {
return <li> {friend} </li>
})}
</ul>
</div>
)
}
}
ShowList.defaultProps = {
names: []
}
好了,第一个教程就到此为止了。我们先来看看
指南,看看还剩下什么。
<b>JSX — Allows us to write HTML like syntax which gets transformed
to lightweight JavaScript objects.</b>
<b>Virtual DOM — A JavaScript representation of the actual
DOM.</b>
<b>React.Component — The way in which you create a new component.</b>
<b>render (method) — Describes what the UI will look like for
the particular component.</b>
<b>ReactDOM.render — Renders a React component to a DOM node.</b>
<b>state — The internal data store (object) of a component.</b>
<b>constructor (this.state) - The way in which you establish
the initial state of a component.</b>
<b>setState — A helper method used for updating the state of a
component and re-rendering the UI</b>
<b>props — The data which is passed to the child component
from the parent component.</b>
<b>prop-types — Allows you to control the presence, or types of
certain props passed to the child component.</b>
<b>defaultProps — Allows you to set default props for your component.</b>
Component LifeCycle
- componentDidMount — Fired after the component mounted
- componentWillUnmount — Fired before the component will unmount
- getDerivedStateFromProps - Fired when the component mounts and
whenever the props change. Used to update the state of a
component when its props change
<b>Events
- onClick
- onSubmit
- onChange
</b>
我们很接近了!
组件生命周期
你创建的每个组件都有各自的生命周期事件,这些事件可用于各种用途。例如,如果我们想在初始渲染时发出 Ajax 请求并获取一些数据,我们应该在哪里执行?或者,如果我们想在 props 发生变化时运行一些逻辑,我们应该怎么做?不同的生命周期事件可以解决这两个问题。让我们来详细分析一下。
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'Tyler McGinnis'
}
}
componentDidMount(){
// Invoked once the component is mounted to the DOM
// Good for making AJAX requests
}
static getDerivedStateFromProps(nextProps, prevState) {
// The object you return from this function will
// be merged with the current state.
}
componentWillUnmount(){
// Called IMMEDIATELY before a component is unmounted
// Good for cleaning up listeners
}
render() {
return (
<div>
Hello, {this.state.name}
</div>
)
}
}
componentDidMount - 首次渲染后调用一次。由于调用此方法时组件已经被调用,因此您可以根据需要访问虚拟 DOM。您可以通过调用this.getDOMNode()来访问。因此,这是一个生命周期事件,您将在此发出 AJAX 请求来获取一些数据。*
componentWillUnmount - 此生命周期在组件从 DOM 中卸载之前立即调用。您可以在此处进行必要的清理。
getDerivedStateFromProps - 有时你需要根据传入的 props 更新组件的状态。这是一个生命周期方法,你可以使用它来执行此操作。它会传入 props 和状态,然后返回的对象将与当前状态合并。
好吧,如果你坚持到这里,那就太好了。希望本教程对你有所帮助,并且你现在至少对 React 有了些许了解。
文章来源:https://dev.to/tylermcginnis/a-compressive-guide-to-reactjs-in-2018--4nbc要更深入地了解 React 的基础知识,请查看我们的React 基础课程。