1. 快速入门
1.1. 安装
React是灵活的,可以在各种项目中使用。您可以使用它创建新的应用程序,但是您也可以在不重写的情况下逐步将其引入到现有的代码中。
下面是一些开始的方式:
- 尝试React
- 创建新的应用
- 添加React到现有的应用
1.1.1. 尝试React
//TODO
1.1.2. 创建新的应用
Create React App是开始构建一个新的React应用程序的最佳方式。它设置了您的开发环境,以便您可以使用最新的JavaScript特性,提供良好的开发体验,并面向产品优化您的应用程序。您需要在您的机器上有大于等于6的Node。
npm install -g create-react-app
create-react-app my-app
cd my-app
npm start
如果您已经安装了npm 5.2.0+,您可以使用npx代替。
npx create-react-app my-app
cd my-app
npm start
Create React App不处理后端逻辑或数据库,它只是创建了一个前端构建途径,所以您可以使用它与任何后端。它在底层使用像Babel和webpack这样的构建工具,但是是零配置工作。
当您准备部署到生产时,运行npm run build将在build文件夹中创建您的应用程序的优化构建。你可以从它的README和用户指南中学到更多关于Create React App的信息。
1.1.3. 添加React到现有的应用
//TODO
1.2. Hello World
最简单的开始React的方法是使用CodePen上的Hello World示例代码。你不需要安装任何东西,您可以在另一个选项卡中打开它,并在我们浏览示例时跟随它。如果您更愿意使用本地开发环境,请参考安装页面。
最小的React例子看起来像这样:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
它在页面上渲染一个标题:“Hello, world!”。
接下来的几节将逐步介绍你使用React。我们将介绍React应用程序的构建块:元素和组件。一旦你掌握了它们,你就可以从小的可重复使用的部分创建复杂的应用程序。
1.2.1. 关于JavaScript的提示
React是一个JavaScript库,因此它假定您对JavaScript语言有基本的了解。如果你感觉不太自信,我们建议你更新你的JavaScript知识,这样你就可以更容易地跟上。
我们还在示例中使用了一些ES6语法。我们尽量少使用它,因为它仍然相对较新,但是我们鼓励您熟悉arrow functions、classes、template literals、let和const语句。您可以使用Babel REPL检查ES6代码编译的内容。
1.3. 介绍JSX
思考这个常量声明:
const element = <h1>Hello, world!</h1>;
这个有趣的标签语法既不是字符串也不是HTML。
它被称为JSX,它是JavaScript的语法扩展。我们推荐在React中使用它来描述UI应该是什么样的。JSX可能会使您想起一种模板语言,但它具有JavaScript的全部功能。
JSX产生React“元素”。我们将在下一节中探索将它们呈现给DOM。下面,您可以找到开始React需要的JSX的基础知识。
//TODO
1.4. 渲染元素
元素是React应用程序的最小构建块。
一个元素描述了您希望在屏幕上看到的内容:
const element = <h1>Hello, world</h1>;
与浏览器DOM元素不同,React元素是普通对象,而且创建起来很便捷。React DOM负责更新DOM以匹配React元素。
注意:
有人可能会混淆元素与更广为人知的“组件”的概念。我们将在下一节介绍组件。元素是由组件组成的,我们鼓励您在跳过前阅读本节。
1.4.1. 将一个元素渲染到DOM中
假设在HTML文件中某个位置有一个< div >:
<div id="root"></div>
我们将此称为“根”DOM节点,因为它内部的所有内容都由React DOM管理。
React构建的应用程序通常只有一个根DOM节点。如果您正在将React集成到一个现有的应用程序,那么您可能会有正如您所需要的许多单独的根DOM节点。
要将一个React元素呈现到一个根DOM节点中,请将两个参数传递给ReactDOM.render():
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
它在页面上显示“Hello,world”。
1.4.2. 更新已渲染的元素
//TODO
1.5. 组件和属性
组件让您将UI分割成独立的可重用的块,并独立地考虑每一个块。
从概念上讲,组件就像JavaScript函数。他们接受任意输入(称为“props”)并返回描述什么应该出现在屏幕上的React元素。
1.5.1. 功能的和类组件
定义组件最简单的方法是编写一个JavaScript函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
这个函数是一个有效的React组件,因为它接受一个单独的“props”(它代表属性)数据对象参数,并返回一个React元素。我们称这些组件为“功能性的”,因为它们实际上是JavaScript函数。
您还可以使用ES6类来定义组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
上述两个组件从React的角度来说是等价的。
类有一些额外的特性,我们将在下一节中讨论。在此之前,我们将使用功能组件,因为他们比较简洁。
1.5.2. 渲染一个组件
先前,我们只遇到代表DOM标记的React元素:
const element = <div />;
然而,元素也可以表示为用户定义的组件:
const element = <Welcome name="Sara" />;
当React看到代表用户定义的组件的元素时,它将JSX属性作为单个对象传递给该组件。我们称这个对象为“props”。
例如,该代码在页面上渲染“Hello, Sara”:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
让我们回顾一下在这个例子中发生了什么:
- 我们调用ReactDOM.render()并传入<Welcome name="Sara" />元素。
- React用{name: 'Sara'}作为属性来调用Welcome组件。
- 我们的Welcome组件返回一个<h1>Hello, Sara</h1>元素作为结果。
- React DOM有效地更新DOM以匹配<h1>Hello, Sara</h1>。
告诫
总是用大写字母开始组件名称。
例如,<div />表示一个DOM标记,但是<Welcome />表示一个组件,且需Welcome在范围内。
1.5.3. 构成组件
组件可以在其输出中涉及其他组件。这使我们可以对任何级别的细节使用相同的组件抽象。一个按钮,一个表单,一个对话框,一个屏幕: 在React应用程序中,所有这些都被普遍表示为组件。
例如,我们可以创建一个App组件,它可以多次渲染Welcome:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
通常,新的React应用程序在最顶部有一个App组件。然而,如果你整合React到一个现有的应用程序,你可以通过一个类似于Button的小组件开始自底向上,然后逐渐地进入到视图层级的顶端。
1.5.4. 提取组件
不要害怕将组件分割成更小的组件。
例如,请思考以下Comment组件:
function Comment(props) {
return (
<div className="comment">
<div className="user-info">
<img className="avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="user-info-name">
{props.author.name}
</div>
</div>
<div className="comment-text">
{props.text}
</div>
<div className="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
它接受author(一个对象)、text (一个字符串)和date(一个日期)作为props,并在社交媒体网站上描述评论。
由于所有的嵌套,这个组件可能很难更改,而且很难重用它的个别部分。让我们从它中提取一些组件。
首先,我们将提取Avatar:
function Avatar(props) {
return (
<img className="avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
Avatar不需要知道它是在Comment中渲染的。这就是为什么我们给它提供了一个更通用的属性名:user而不是author。
我们建议使用组件本身的视角命名属性,而不是使用它的上下文。
现在我们可以让Comment简化一点:
function Comment(props) {
return (
<div className="comment">
<div className="user-info">
<Avatar user={props.author} />
<div className="user-info-name">
{props.author.name}
</div>
</div>
<div className="comment-text">
{props.text}
</div>
<div className="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
接下来,我们将提取一个UserInfo组件,该组件将在用户名旁边渲染一个Avatar:
function UserInfo(props) {
return (
<div className="user-info">
<Avatar user={props.user} />
<div className="user-info-name">
{props.user.name}
</div>
</div>
);
}
这让我们进一步简化Comment:
function Comment(props) {
return (
<div className="comment">
<UserInfo user={props.author} />
<div className="comment-text">
{props.text}
</div>
<div className="comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
首先,提取组件似乎是一种枯燥乏味的工作,但是拥有一个可重用组件的调色板在更大的应用程序中是有回报的。一个好的经验法则是,如果你的UI的一部分被多次使用(Button、Panel、Avatar),或者它自己足够复杂(App、Avatar),那么它是可重用组件的一个好的候选者。
1.5.5. props是只读的
//TODO
1.6. 状态和生命周期
思考前面一个小节中的滴答时钟的例子。
到目前为止,我们只学习了一种更新UI的方法。
我们调用ReactDOM.render()来更改渲染的输出:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
在本节中,我们将学习如何使Clock组件真正地可重用和封装。它将设置自己的计时器,并每秒钟更新一次。
我们可以从封装时钟的外观开始:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
然而,它忽略了一个关键的要求:为Clock设置一个计时器,每秒钟更新UI应该是Clock的实现细节。
理想情况下,我们想一旦写了它,就让Clock自己更新:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
要实现这一点,我们需要向Clock组件添加“state”。
状态类似于属性,但它是私有的,由组件完全控制。
我们之前提到过,定义为类的组件有一些额外的特性。本地状态就是这样:只提供给类的特性。
1.6.1. 将函数转换为类
您可以在五个步骤内将像Clock这样的功能组件转换为一个类:
- 创建一个具有相同名称的ES6类,它继承React.Component。
- 添加一个名为render()的空方法。
- 将函数体移动到render()方法中。
- 在render()方法体中,用this.props替换props。
- 删除剩余的空函数声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock现在被定义为一个类而不是一个函数。
这使我们可以使用其他特性,例如本地状态和生命周期钩子。
1.6.2. 给类添加本地状态
我们将三个步骤把date从props移动到state:
1. 在render()方法中用this.state.date取代this.props.date:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2. 添加一个类构造器,它分配初始的this.state。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
注意我们如何将props传递给基础构造函数:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
类组件应该始终使用props调用基构造函数。
3. 从<Clock / >元素中移除日期属性:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
稍后我们将把计时器代码添加到组件本身。
结果看起来像这样:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
接下来,我们将让Clock设置自己的计时器,并每秒钟更新一次。
1.6.3. 向类中添加生命周期方法
在有许多组件的应用程序中,当组件被销毁时,释放组件占用的资源是非常重要的。
我们想每当Clock第一次被渲染到DOM时设置一个计时器。在React中这被称为“挂载”。
我们还想每当Clock产生的DOM被移除时清除计时器,在React中这被称为“ 卸载”。
我们可以在组件类上声明特殊方法,当组件挂载和卸载时运行一些代码:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
这些方法被称为“生命周期钩子”。
componentDidMount()钩子在组件输出被渲染到DOM之后运行。这是一个设置计时器的好地方:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
注意我们如何在this中保存定时器ID。
虽然this.props是由React自己设置的,并且this.state有特殊的含义,但是如果您需要存储不用于可视化输出的内容,您可以自由地在类中手动添加其他字段。
如果您不在render()中使用某些东西,它也不应该出现在状态中。
我们将在componentWillUnmount()生命周期钩子中拆除计时器:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我们将实现一个名为tick()的方法,Clock组件将每秒钟运行一次。
它将使用this.setState()来调度更新组件的本地状态:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
现在时钟每秒钟运行一次。
让我们快速回顾一下正在发生的事情以及调用方法的顺序:
//TODO
1.7. 事件处理
React元素的事件处理非常类似于DOM元素的事件处理。有一些句法上的差异:
- React事件以camelCase命名,而不是lowercase。
- 使用JSX,您传递一个函数作为事件处理程序,而不是字符串。
例如,HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
在React中稍微不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
另一个不同之处在于,您不能返回false阻止React中的默认行为。您必须明确地调用preventDefault。例如,使用纯HTML,为了阻止打开新页面的默认链接行为,您可以编写:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
在React中,这可以被替换成:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
这里,e是一个合成事件。根据W3C规范,React定义了这些合成事件,因此您不必担心跨浏览器的兼容性。查看SyntheticEvent参考指南了解更多信息。
当使用React时,通常不需要调用addEventListener在元素创建之后将侦听器添加到DOM元素。相反,只要在最初渲染元素时提供一个侦听器。
当您使用ES6类定义组件时,通常的模式是将事件处理程序定义成类的方法。例如,这个Toggle组件渲染一个按钮,让用户在“ON”和“OFF”状态之间切换:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
//TODO