React初学者教程

来自tomtalk
Tom讨论 | 贡献2017年10月30日 (一) 09:25的版本 处理状态

跳转至: 导航搜索

React 简介

老式的多页设计

如果你几年前做过这种应用程序,可能会采用包含多个单页面的方式。

在多页设计方式下,对于大多数改变页面显示的行为,Web 应用会导航到一个完全不同的页面。用户会看到原页面被销毁,然后出来一个新页面,这种用户体验很不尽人意。

新派的单页应用

现代的应用程序趋向于采用一种称为单页应用(SPA)的模式。这种模式下,我们不需要导航到不同的页面,甚至不需要重新加载一个页面。应用的不同视图被加载和卸载到同一页面上。

用来创建真正可组合 UI 的 API

React 鼓励我们将视觉元素分为更小的组件,而不是一整大块。

http://www.w3cplus.com/sites/default/files/blogs/2016/1611/react_components_composable_72.png

完全在 JavaScript 中定义 UI

虽然这听起来有点不可理喻,(我们知道,在 Web 标准年代,崇尚的是结构、表现形式和行为分离,也就是 UI 的结构、表现形式和行为部分分别分离到 HTML、CSS 和 JavaScript 三个文件中。完全在 JavaScript 中定义 UI,岂不是跟 Web 标准背道而驰么?)。但是且听我说完。如果像过去一样采用 HTML 模板的方式定义 UI,除了古怪的语法外,还有另一个主要问题。在模板中,除了只是显示数据,我们被限制做很多事情。例如,如果你想根据特定条件,选择显示哪一块 UI,就不得不在应用中到处写 JavaScript,或者用一些古怪的框架特有的模板语法,才能让它起作用。

而 React 实现的方式就很优雅。UI 完全在 JavaScript 中定义,我们可以利用 JavaScript 提供的强大功能在模板内做各种事情。我们受到的限制只是 JavaScript 支持不支持,而不是模板框架的限制。

创建第一个 React 应用

React 的学习曲线相当陡峭,里面大大小小的障碍不少。

处理 JSX

除了标准的 HTML、CSS 和 JavaScript 外,很多 React 代码都会用 JSX 编写。JSX 是一门可以让我们很容易混合 JavaScript 和 类似 HTML 的标记,来定义用户界面元素以及其功能的语言。这听起来很酷,但是问题是:浏览器是不知道如何处理 JSX的。

要用 React 创建 Web 应用,我们需要一种方式采用 JSX,并将它转换为浏览器可以理解的标准 JavaScript,如果不这么做,React 应用就无法运行。目前将 JSX 转换为 JavaScript 有两种解决方案:

  • 围绕 Node 以及一些构建工具(比如 Webpack)来设置开发环境。
  • 让浏览器在运行时自动将 JSX 转换为 JavaScript。

在 React 入门阶段,我们打算用第二种方案。那么为什么我们不一直用第二种方案呢?原因是,浏览器每次要花时间把 JSX 翻译为 JS,这对性能是有影响的。在学习如何使用 React 时,这是完全可以接受的,但是在部署应用程序实际使用时,这肯定是完全不能接受的。所以后面我们会在已经熟悉了 React 后,再用第一解决方案,即设置开发环境。

上手 React

首先,我们需要一个空白 HTML 页面作为起点。

<!DOCTYPE html>
<html>
<head><title>React! React! React!</title>
    <script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
    <script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
<body>
<script></script>
</body>
</html>

第一行引入 核心 React 库,第二行引入 React DOM 库。如果没有这两个库,我们是没法创建 React 应用的。然后,我们还需要在两个 script 标记下添加对 Babel JavaScript 编译器的引用。

现在如果马上预览页面,我们会注意到页面是空的,什么都看不到。没关系,下面我们就要让它显示点什么。

显示你的姓名

你要做的第一件事情就是在屏幕上显示你的姓名。

ReactDOM.render(
    <h1>Sherlock Holmes</h1>,
    document.body
);

此时,不要担心这几行代码到底是什么意思。我们的目标是先在屏幕上显示点什么,不久之后我们就会搞清楚这几行代码到底在做什么。现在,在预览页面看看发生了什么之前,我们需要给这个 script 块标注一下,这样 babel 才能可以发挥其魔力。实现方法是将 script 标记的 type 属性设置为 text/babel。

之后,我们就可以在浏览器预览了。我们会看到单词 'Sherlock Holmes' 在屏幕上用很大的字符打印出来。恭喜!你刚用 React 创建一个 APP。

改变输出的目标

我们要做的第一件事情是改变 JSX 输出的目标。用 JavaScript 将输出直接放在 body 元素上肯定不是一个好主意。有时候会出错,特别是如果你将 React 与其它 JS 库和框架混合在一起用时。推荐的路径是专门创建一个元素作为新的根元素,让这个新元素作为 render 方法要用的目标。OK,我们回到 HTML,添加一个 id 值为 container 的 div 元素。

var destination = document.querySelector("#container");
ReactDOM.render(
    <h1>Sherlock Holmes</h1>,
    destination
);

完整示例

你的 React 应用的看起来应该是这个样子,最终结果依然 100% 的 HTML、CSS 和 JavaScript:

<!DOCTYPE html>
<html>
<head><title>React! React! React!</title>
    <script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
    <script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
    var destination = document.querySelector("#container");
    ReactDOM.render(
            <h1>Sherlock Holmes</h1>,
            destination
    );
</script>
</body>
</html>

我们可以看到,这里连一点 React 类似代码的痕迹都看不到。

React 中的组件

组件是让 React 变得美好的事情之一,它是定义人们在使用应用程序时所看到的视觉和交互的主要方式之一。

JSX 问题:输出多个元素

现在,我们让应用打印出几个超级英雄的姓名:

var destination = document.querySelector("#container");
ReactDOM.render(
        <div>
            <h1>Batman</h1>
            <h1>Iron Man</h1>
            <h1>Nicolas Cage</h1>
            <h1>Mega Man</h1>
        </div>,
        destination
);

这里要呼出一个重要的 JSX 细节。包含 h1 元素的 div 应该去掉,是因为这看起来像个好主意。但是在 JSX 和 JavaScript 之间的邪恶联盟眼里,它是无效的。这貌似是一个可怕的限制,但是应变方式很简单。虽然我们只能输入一个元素,但是一个元素可以有很多子节点。

创建一个 “Hello, World!” 组件

var HelloWorld = React.createClass({
    render: function () {
        return (
                <p>Hello, world!</p>
        );
    }
});
 
ReactDOM.render(
        <div>
            <HelloWorld/>
            <HelloWorld/>
            <HelloWorld/>
            <HelloWorld/>
            <HelloWorld/>
            <HelloWorld/>
        </div>,
        document.querySelector("#container")
);

指定属性

var HelloWorld = React.createClass({
    render: function () {
        return (
                <p>Hello, {this.props.greetTarget}!</p>
        );
    }
});
 
ReactDOM.render(
        <div>
            <HelloWorld greetTarget="Batman"/>
            <HelloWorld greetTarget="Iron Man"/>
            <HelloWorld greetTarget="Nicolas Cage"/>
            <HelloWorld greetTarget="Mega Man"/>
            <HelloWorld greetTarget="Bono"/>
            <HelloWorld greetTarget="Catwoman"/>
        </div>,
        document.querySelector("#container")
);

处理子元素

var button = document.querySelector("#button");
 
var Buttonify = React.createClass({
    render: function () {
        return (
                <div>
                    <button type={this.props.behavior}>{this.props.children}</button>
                </div>
        );
    }
});
 
ReactDOM.render(
        <div>
            <Buttonify behavior="Submit">SEND DATA</Buttonify>
        </div>,
        button
);

总结

如果你想用 React 创建应用,如果不用组件你不会走的太远。不用组件来创建 React 应用,就像不用函数来创建基于 JavaScript 的应用程序一样。

在 React 中设置样式

如何格式化 HTML 元素?样式应该放在哪里?你可能猜到我们会在哪里处理这点。如果把样式放在另一个地方,我们就没法有一个自包含的 UI 块。这就是为什么 React 鼓励我们把样式与 HTML 和 JavaScript 放在一起的原因。在本教程中,我们将学习这种神秘的,可能是骇人听闻的格式化内容的方法。

var destination = document.querySelector("#container");
 
var Letter = React.createClass({
    render: function () {
        var letterStyle = {
            padding: 10,
            margin: 10,
            backgroundColor: this.props.bgcolor,
            color: "#333",
            display: "inline-block",
            fontFamily: "monospace",
            fontSize: "32",
            textAlign: "center"
        };
 
        return (
                <div style={letterStyle}>
                    {this.props.children}
                </div>
        );
    }
});
 
ReactDOM.render(
        <div>
            <Letter bgcolor="#58B3FF">A</Letter>
            <Letter bgcolor="#FF605F">E</Letter>
            <Letter bgcolor="#FFD52E">I</Letter>
            <Letter bgcolor="#49DD8E">O</Letter>
            <Letter bgcolor="#AE99FF">U</Letter>
        </div>,
        destination
);

我们刚才所做的,是用普通 CSS 很难实现的。后面我们在学习那些内容会根据状态或者用户交互而改变的组件时,我们会看到更多的这样用 React 方式格式化内容的示例有更多好的优点。

创建复杂的组件

从界面到组件

迄今为止我们所看到的几个例子是很基础的。对于突出技术概念来说,这几个例子还不错,但是对于要为真实世界做准备来说,它们就一般般了。

在真实世界中,你要用 React 实现的肯定不会是像一个姓名列表,一个彩色元音字母块这么简单了。我们面对的往往是一些复杂用户界面的视觉。

现在,我们要做一个简单的调色板卡。

1. 识别主要的视觉元素

第一个步骤是识别我们要处理的所有视觉元素。即使是最小的视觉元素也不能省略。

2. 识别组件

我们需要搞清楚我们识别出来的视觉元素中哪些需要变成组件,哪些不需要。通用的规则是我们的组件应该只做一件事情。如果你发现你可能的组件将会做太多事情,可能就要将组件拆分成多个组件。另一方面,如果潜在的组件做的事情太少,可能就要完全略过让这个视觉元素成为一个组件。

创建组件

我们就要开始定义我们的三个组件了。这三个组件的名字将是 Card、Label 和 Square。

var destination = document.querySelector("#container");
 
var Card = React.createClass({
    render: function () {
        var cardStyle = {
            height: 200,
            width: 150,
            padding: 0,
            backgroundColor: "#FFF",
            WebkitFilter: "drop-shadow(0px 0px 5px #666)",
            filter: "drop-shadow(0px 0px 5px #666)"
        };
 
        return (
                <div style={cardStyle}>
                    <Square/>
                    <Label/>
                </div>
        );
    }
});
 
var Square = React.createClass({
    render: function () {
        var squareStyle = {
            height: 150,
            backgroundColor: "#FF6663"
        };
 
        return (
                <div style={squareStyle}>
 
                </div>
        );
    }
});
 
var Label = React.createClass({
    render: function () {
        var labelStyle = {
            fontFamily: "sans-serif",
            fontWeight: "bold",
            padding: 13,
            margin: 0
        };
 
        return (
                <p style={labelStyle}>#FF6663</p>
        );
    }
});
 
ReactDOM.render(
        <div>
            <Card/>
        </div>,
        destination
);

再次传递属性!

在当前示例中,我们把 Squar 和 Label 组件用的颜色值硬编码了。这是件奇怪的事情,可能会也可能不会故意地这样做以得到戏剧性的效果,但是修复它是很简单的。只需要指定一个属性名,并且通过this.props 方法该属性名就可以了。我们在前面已经这样做过了。不同的是这次我们将不得不多次这样做。

var destination = document.querySelector("#container");
 
var Card = React.createClass({
    render: function () {
        var cardStyle = {
            height: 200,
            width: 150,
            padding: 0,
            backgroundColor: "#FFF",
            WebkitFilter: "drop-shadow(0px 0px 5px #666)",
            filter: "drop-shadow(0px 0px 5px #666)"
        };
 
        return (
                <div style={cardStyle}>
                    <Square color={this.props.color}/>
                    <Label color={this.props.color}/>
                </div>
        );
    }
});
 
var Square = React.createClass({
    render: function () {
        var squareStyle = {
            height: 150,
            backgroundColor: this.props.color
        };
 
        return (
                <div style={squareStyle}>
 
                </div>
        );
    }
});
 
var Label = React.createClass({
    render: function () {
        var labelStyle = {
            fontFamily: "sans-serif",
            fontWeight: "bold",
            padding: 13,
            margin: 0
        };
 
        return (
                <p style={labelStyle}>{this.props.color}</p>
        );
    }
});
 
ReactDOM.render(
        <div>
            <Card color="#FFA737"/>
        </div>,
        destination
);

尽管 color 属性只被 Square 和 Label 组件所用,但是父组件 Card 负责传递该属性给它们。对于更深层次的嵌套,你会需要更多的中间组件负责传递属性。这就变得更糟糕。当你有多个属性想沿着多级组件传递时,你所做的打字(或者复制/粘贴)数量也会增加很多。有方法可以缓解,我们会在后面的教程中更详细地了解这些缓解措施。

传递属性

处理属性有令人沮丧的一面,在前一个教程中我们已经看到了一点。在只处理一层组件时,将属性从一个组件传递到另一个很简单。但是如果你想将一个属性在多层组件之间传递,事情就开始变得复杂了。

事情变得复杂从来不是一件好事情,所以在本教程中,我们来看看我们怎么做才能让在多层组件之间传递属性变得容易。

问题概述

React 强制一个命令链,在链中,属性必须从父组件向下流动到直接的子组件。也就是说,在传递一个属性时,你不能跳过子层。这还意味着你的子组件不能把一个属性传回到父组件。所有的通讯是从父到子单向的。

如果我们想发送三个属性又该怎么办?或者四个呢?

我们很快会看到这种方法既没有可扩展性,也没有可维护性。对于需要通讯的每个附加的属性,我们将不得不为它添加一个入口作为每个组件声明的一部分。如果在某个时间我们决定重新命名属性,就不得不确保该属性的每个实例也被重新命名。如果删除一个属性,我们需要从依赖该属性的每个组件中删除该属性。总的来说,这是我们在编写代码时,应该努力避免的一种情况。那么我们可以怎么做呢?

遇见扩展运算符

所有这些问题的解决方案在于一个 JavaScript 新概念:扩展运算符(Spread Operator)。如果没有上下文,要解释扩展运算符有点难度,所以这里我们先看一个示例,然后再看扩展运算符的定义。

printStuff(...items);

扩展运算符是在 items 数组前的 '…' 字符,使用 '…items' 等于分别调用 items[0], item[1], item[2]。

正确传递属性

var destination = document.querySelector("#container");
 
var Display = React.createClass({
    render: function () {
        return (
                <div>
                    <p>{this.props.color}</p>
                    <p>{this.props.num}</p>
                    <p>{this.props.size}</p>
                </div>
        );
    }
});
 
var Label = React.createClass({
    render: function () {
        return (
                <Display {...this.props}/>
        );
    }
});
 
var Shirt = React.createClass({
    render: function () {
        return (
                <div>
                    <Label {...this.props}/>
                </div>
        );
    }
});
 
ReactDOM.render(
        <div>
            <Shirt color="steelblue" num="3.145" size="medium"/>
        </div>,
        destination
);

总结

ES6/ES2015 委员会设计扩展运算符时,只设计把它用在数组上。它能在对象字面量(例如我们的 props 对象)上使用,归功于 React 扩展了标准。到目前为止,还没有浏览器支持在对象字面量上使用扩展运算符。我们的示例能够运行是因为有 Babel。除了将所有 JSX 转变为浏览器能理解的语言外, Babel 还会将前沿以及实验功能转变成跨浏览器友好的东西。

深入JSX

一、JSX背后发生了什么?

return React.createElement(
    "div",
    {style: cardStyle},
    React.createElement(Square, {color: this.props.color}),
    React.createElement(Label, {color: this.props.color})
);

由Babel完全在浏览器中执行 JSX => JS 的转换,被转变成JavaScript。

二、要记住的 JSX 怪癖

1. 只能返回一个根节点

如果是有多个根元素,就会破坏函数返回值以及 createElement的机制。

2. 不能指定 inline CSS

3. 保留关键字和 className

ReactDOM.render(
    <div className="slideIn">
        <p className="emphasis">Gabagool!</p>
        <Label/>
    </div>,
    document.querySelector("#container")
);

4. 注释

如果你指定一个注释作为一个标记的子节点,那么你必须用 {和 } 把注释包起来,以确保它被解析为一个表达式:

ReactDOM.render(
  <div class="slideIn">
    <p class="emphasis">Gabagool!</p>
    {/* I am a child comment */}
    <Label/>
  </div>,
  document.querySelector("#container")
);

如果完全在一个标记内部指定注释,那么就不需要用大括号把单行或者多行注释括起来:

ReactDOM.render(
  <div class="slideIn">
    <p class="emphasis">Gabagool!</p>
    <Label
      /* This comment
         goes across
         multiple lines */
         className="colorCard" // end of line
    />
  </div>,
  document.querySelector("#container")
);

5. 大小写、HTML 元素和组件

大小写很重要。如果大小写出错,React 将不会正确渲染内容。

要表示 HTML 元素,必须确保 HTML 标记是小写字母。当表示组件时,组件的名称必须是大写。

6. JSX 可以出现在任何地方

在很多情况下,JSX 并不是像我们前面看到的示例一样,整齐地排列在一个 render 或者 return 函数内。看看如下示例:

var swatchComponent = <Swatch color="#2F004F"></Swatch>;
 
ReactDOM.render(
  <div>
    {swatchComponent}
  </div>,
  document.querySelector("#container")
);

我们有一个 swatchComponent 变量,该变量被初始化为一行 JSX。当 swatchComponent 变量放在 render 函数内时,Swatch 组件就被初始化。这一切都是有效的,并且在将来当我们学习如何用 JavaScript 生成和操作 JSX 时,我们将会做更多这种事情。

处理状态

到目前为止,我们已经创建的组件都是无状态的(stateless)。它们有从它们的父组件传递进来的属性(亦称 props),一旦属性被设置了,就被当作是不可修变的(immutable)。对于很多交互场景来说,你肯定不想这样子。在一些用户交互发生,或者一些数据从服务器或者其它地方返回过来时,你想能改变组件的外观。

我们所要的是另一种超越属性的在组件上存储数据的方式,即存储可以修改的数据。我们所需要的是称为状态(state)的东西。

从数据到 UI

React 中的事件

组件生命周期

用 React Router 创建单页应用

用 React 常见一个简单的 Todo List 应用

在 React 中访问 DOM 元素

设置 React 开发环境