学习 React.js 比你想象的要简单

学习 React.js 比你想象的要简单

经过 Medium 中的一篇文章来学习 React.js 的基本原理

你有没有注意到在 React 的 logo 中隐藏着一个六角星?只是顺便提下...
去年我写了一本简短的关于学习 React.js 的书,有 100 页左右。今年,我要挑战本身 —— 将其总结成一篇文章,并向 Medium 投稿。javascript

这篇文章不是讲什么是 React 或者 你该怎样学习 React。这是在面向那些已经熟悉了 JavaScript 和 DOM API 的人的 React.js 基本原理介绍html

本文采用嵌入式 jsComplete 代码段,因此为了方便阅读,你须要一个合适的屏幕宽度。前端

下面全部的代码都仅供参考。它们也纯粹是为了表达概念而提供的例子。它们中的大多数有更好的实践方式。java

您能够编辑和执行下面的任何代码段。使用 Ctrl+Enter 执行代码。每一段的右下角有一个点击后能够在 jsComplete/repl 进行全屏模式编辑或运行代码的连接。node


1 React 所有都是组件化的

React 是围绕可重用组件的概念设计的。你定义小组件并将它们组合在一块儿造成更大的组件。react

不管大小,全部组件都是可重用的,甚至在不一样的项目中也是如此。android

React 组件最简单的形式,就是一个普通的 JavaScript 函数:ios

function Button (props) {
  // 这里返回一个 DOM 元素,例如:
  return <button type="submit">{props.label}</button>;
}
// 将按钮组件呈现给浏览器
ReactDOM.render(<Button label="Save" />, mountNode)复制代码

例 1:编辑上面的代码并按 Ctrl+Enter 键执行(译者注:译文暂时没有这个功能,请访问原文使用此功能,下同)git

括号中的 button 标签将稍后解释。如今不要担忧它们。ReactDOM 也将稍后解释,但若是你想测试这个例子和全部接下来的例子,上述 render 函数是必须的。(React 将要接管和控制的是 ReactDOM.render 的第 2 个参数即目标 DOM 元素)。在 jsComplete REPL 中,你可使用特殊的变量 mountNodegithub

例 1 的注意事项:

  • 组件名称首字母大写,Button。必需要这样作是由于咱们将处理 HTML 元素和 React 元素的混合。小写名称是为 HTML 元素保留的。事实上,将 React 组件命名为 “button” 而后你就会发现 ReactDOM 会忽略这个函数,仅仅是将其做为一个普通的空 HTML 按钮来渲染。
  • 每一个组件都接收一个属性列表,就像 HTML 元素同样。在 React 中,这个列表被称为属性。虽然你能够将一个函数随意命名。
  • 在上面 Button 函数组件的返回输出中,咱们奇怪地写了段看上去像 HTML 的代码。这实际上既不是 JavaScript 也不是 HTML,老实说,这甚至不是 React.js。然而它很是流行,以致于成为 React 应用程序中的默认值。这就是所谓的 JSX,这是一个JavaScript 的扩展。JSX 也是一个折中方案!继续尝试并在上面的函数中返回其余 HTML 元素,看看它们是如何被支持的(例如,返回一个文本输入元素)。

2 JSX 输出的是什么?

上面的例 1 能够用没有 JSX 的纯 React.js 编写,以下:

function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}

// 要使用 Button,你能够这么作
ReactDOM.render(
  React.createElement(Button, { label: "Save" }),
  mountNode
);复制代码

例 2:不使用 JSX 编写 React 组件

在 React 顶级 API 中,createElement 函数是主函数。这是你须要学习的 7 个 API 中的 1 个。React 的 API 就是这么小。

就像 DOM 自身有一个 document.createElement 函数来建立一个由标签名指定的元素同样,React 的 createElement 函数是一个高级函数,有和 document.createElement 一样的功能,但它也能够用于建立一个表示 React 组件的元素。当咱们使用上面例 2 中的按钮组件时,咱们使用的是后者。

不像 document.createElement,React 的 createElement 在接收第二个参数后,接收一个动态参数,它表示所建立元素的子元素。因此 createElement 实际上建立了一个

这里就是这样的一个例子:

const InputForm = React.createElement(
  "form",
  { target: "_blank", action: "https://google.com/search" },
  React.createElement("div", null, "Enter input and click Search"),
  React.createElement("input", { className: "big-input" }),
  React.createElement(Button, { label: "Search" })
);

// InputForm 使用 Button 组件,因此咱们须要这样作:
function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}

// 而后咱们能够经过 .render 方法直接使用 InputForm
ReactDOM.render(InputForm, mountNode);复制代码

例 3:React 建立元素的 API

上面例子中的一些事情值得注意:

  • InputForm 不是一个 React 组件;它仅仅是一个 React 元素。这就是为何咱们能够在 ReactDOM.render 中直接使用它而且能够在调用时不使用 <InputForm /> 的缘由。
  • React.createElement 函数在前两个参数后接收了多个参数。从第3个参数开始的参数列表构成了建立元素的子项列表。
  • 咱们能够嵌套 React.createElement 调用,由于它是 JavaScript。
  • 当这个元素不须要属性时,React.createElement 的第二个参数能够为空或者是一个空对象。
  • 咱们能够在 React 组件中混合 HTML 元素。你能够将 HTML 元素做为内置的 React 组件。
  • React 的 API 试图和 DOM API 同样,这就是为何咱们在 input 元素中使用 className 代替 class 的缘由。咱们都但愿若是 React 的 API 成为 DOM API 自己的一部分,由于,你知道,它要好得多。

上述的代码是当你引入 React 库的时候浏览器是怎样理解的。浏览器不会处理任何 JSX 业务。然而,咱们更喜欢看到和使用 HTML,而不是那些 createElement 调用(想象一下只使用 document.createElement 构建一个网站!)。这就是 JSX 存在的缘由。取代上述调用 React.createElement 的方式,咱们可使用一个很是简单相似于 HTML 的语法:

const InputForm =
  <form target="_blank" action="https://google.com/search">
    <div>Enter input and click Search</div>
    <input className="big-input" name="q" />
    <Button label="Search" />
  </form>;

// InputForm “仍然”使用 Button 组件,因此咱们也须要这样。
// JXS 或者普通的表单都会这样作
function Button (props) {
  // 这里返回一个 DOM 元素。例如:
  return <button type="submit">{props.label}</button>;
}

// 而后咱们能够直接经过 .render 使用 InputForm
ReactDOM.render(InputForm, mountNode);复制代码

例 4:为何在 React 中 JSX 受欢迎(和例 3 相比)

注意上面的几件事:

  • 这不是 HTML 代码。好比,咱们仍然可使用 className 代替 class
  • 咱们仍在考虑怎样让上述的 JavaScript 看起来像是 HTML。看一下我在最后是怎样添加的。

咱们在上面(例 4)中写的就是 JSX。然而,咱们要将编译后的版本(例 3)给浏览器。要作到这一点,咱们须要使用一个预处理器将 JSX 版本转换为 React.createElement 版本。

这就是 JSX。这是一种折中的方案,容许咱们用相似 HTML 的语法来编写咱们的 React 组件,这是一个很好的方法。

“Flux” 在头部做为韵脚来使用,但它也是一个很是受欢迎的 应用架构,由 Facebook 推广。最出名的是 Redux,Flux 和 React 很是合适。

JSX,能够单独使用,不只仅适用于 React。

3 你能够在 JavaScript 的任何地方使用 JSX

在 JSX 中,你能够在一对花括号内使用任何 JavaScript 表达式。

const RandomValue = () =>
  <div>
    { Math.floor(Math.random() * 100) }
  </div>;

// 使用:
ReactDOM.render(<RandomValue />, mountNode);复制代码

例 5:在 JSX 中使用 JavaScript 表达式

任何 JavaScript 表达式能够直接放在花括号中。这至关于在 JavaScript 中插入 ${} 模板

这是 JSX 内惟一的约束:只有表达式。例如,你不能使用 if 语句,但三元表达式是能够的。

JavaScript 变量也是表达式,因此当组件接受属性列表时(不包括 RandomValue 组件,props 是可选择的),你能够在花括号里使用这些属性。咱们在上述(例 1)的 Button 组件是这样使用的。

JavaScript 对象也是表达式。有些时候咱们在花括号中使用 JavaScript 对象,这看起来像是使用了两个花括号,可是在花括号中确实只有一个对象。其中一个用例就是将 CSS 样式对象传递给响应中的特殊样式属性:

const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;

// 使用
ReactDOM.render(
  <ErrorDisplay
    message="These aren't the droids you're looking for"
  />,
  mountNode
);复制代码

例 6:一个对象传递特殊的 React 样式参数

注意我解构的只是在属性参数以外的信息。这只是 JavaScript。还要注意上面的样式属性是一个特殊的属性(一样,它不是 HTML,它更接近 DOM API)。咱们使用一个对象做为样式属性的值而且这个对象定义样式就像咱们使用 JavaScript 那样(咱们能够这样作)。

你能够在 JSX 中使用 React 元素。由于这也是一个表达式(记住,一个 React 元素只是一个函数调用):

const MaybeError = ({errorMessage}) =>
  <div>
    {errorMessage && <ErrorDisplay message={errorMessage} />}
  </div>;

// MaybeError 组件使用 ErrorDisplay 组件
const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;

// 如今咱们使用 MaybeError 组件:
ReactDOM.render(
  <MaybeError
    errorMessage={Math.random() > 0.5 ? 'Not good' : ''}
  />,
  mountNode
);复制代码

例 7:一个 React 元素是一个能够经过 {} 使用的表达式

上述 MaybeError 组件只会在有 errorMessage 传入或者另外有一个空的 div 才会显示 ErrorDisplay 组件。React 认为 {true}{false}
{undefined}{null} 是有效元素,不呈现任何内容。

咱们也能够在 JSX 中使用全部的 JavaScript 的集合方法(mapreducefilterconcat 等)。由于他们返回的也是表达式:

const Doubler = ({value=[1, 2, 3]}) =>
  <div>
    {value.map(e => e * 2)}
  </div>;

// 使用下面内容 
ReactDOM.render(<Doubler />, mountNode);复制代码

例 8:在 {} 中使用数组

请注意我是如何给出上述 value 属性的默认值的,由于这所有都只是 JavaScript。注意我只是在 div 中输出一个数组表达式。React 是彻底能够的。它只会在文本节点中放置每个加倍的值。

4 你可使用 JavaScript 类写 React 组件

简单的函数组件很是适合简单的需求,可是有的时候咱们须要的更多。React 也支持经过使用 JavaScript 类来建立组件。这里 Button 组件(在例 1 中)就是使用类的语法编写的。

class Button extends React.Component {
  render() {
    return <button>{this.props.label}</button>;
  }
}

// 使用(相同的语法)
ReactDOM.render(<Button label="Save" />, mountNode);复制代码

例 9:使用 JavaScript 类建立组件

类的语法是很是简单的:定义一个扩展的 React.Component 类(另外一个你须要学习的 React 的顶级 API)。该类定义了一个单一的实例函数 —— render(),并使函数返回虚拟 DOM 对象。每一次咱们使用基于类的 Button 组件(例如,经过 <Button ... />),React 将从这个基于类的组件中实例化对象,并在 DOM 树中使用该对象。

这就是为何上面的例子中咱们能够在 JSX 中使用 this.props.label 渲染输出的缘由,由于每个组件都有一个特殊的称为 props实例 属性,这让全部的值传递给该组件时被实例化。

因为咱们有一个与组件的单个使用相关联的实例,因此咱们能够按照本身的意愿定制该实例。例如,咱们能够经过使用常规 JavaScript 构造函数来构造它:

class Button extends React.Component {
  constructor(props) {
    super(props);
    this.id = Date.now();
  }
  render() {
    return <button id={this.id}>{this.props.label}</button>;
  }
}

// 使用
ReactDOM.render(<Button label="Save" />, mountNode);复制代码

例 10:自定义组件实例

咱们也能够定义类的原型而且在任何咱们但愿的地方使用,包括在返回的 JSX 输出的内部:

class Button extends React.Component {
  clickCounter = 0;

  handleClick = () => {
    console.log(`Clicked: ${++this.clickCounter}`);
  };

  render() {
    return (
      <button id={this.id} onClick={this.handleClick}> {this.props.label} </button>
    );
  }
}

// 使用
ReactDOM.render(<Button label="Save" />, mountNode);复制代码

例 11:使用类的属性(经过单击保存按钮进行测试)

注意上述例 11 中的几件事情

  • handleClick 函数使用 JavaScript 新提出的 class-field syntax 语法。这仍然是 stage-2,可是这是访问组件安装实例(感谢箭头函数)最好的选择(由于不少缘由)。然而,你须要使用相似 Babel 的编译器解码为 stage-2(或者仅仅是类字段语法)来让上述代码工做。 jsComplete REPL 有预编译功能。
// 错误:
onClick={this.handleClick()}

// 正确:
onClick={this.handleClick}复制代码

5 React 中的事件:两个重要的区别

当处理 React 元素中的事件时,咱们与 DOM API 的处理方式有两个很是重要的区别:

  • 全部 React 元素属性(包括事件)都使用 camelCase 命名,而不是 lowercase。例如是 onClick 而不是 onclick
  • 咱们将实际的 JavaScript 函数引用传递给事件处理程序,而不是字符串。例如是 onClick={handleClick} 而不是 onClick="handleClick"

React 用本身的对象包装 DOM 对象事件以优化事件处理的性能,可是在事件处理程序内部,咱们仍然能够访问 DOM 对象上全部能够访问的方法。React 将通过包装的事件对象传递给每一个调用函数。例如,为了防止表单提交默认提交操做,你能够这样作:

class Form extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submitted');
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}> <button type="submit">Submit</button> </form>
    );
  }
}

// 使用
ReactDOM.render(<Form />, mountNode);复制代码

例 12:使用包装过的对象

6 每个 React 组件都有一个故事:第 1 部分

如下仅适用于类组件(扩展 React.Component)。函数组件有一个稍微不一样的故事。

  1. 首先,咱们定义了一个模板来建立组件中的元素。
  2. 而后,咱们在某处使用 React。例如,在 render 内部调用其余的组件,或者直接使用 ReactDOM.render
  3. 而后,React 实例化一个对象而后给它设置 props 而后咱们能够经过 this.props 访问。这些属性都是咱们在第 2 步传入的。
  4. 由于这些所有都是 JavaScript,constructor 方法将会被调用(若是定义的话)。这是咱们称之为的第一个:组件生命周期方法
  5. 接下来 React 计算渲染以后的输出方法(虚拟 DOM 节点)。
  6. 由于这是 React 第一次渲染元素,React 将会与浏览器连通(表明咱们使用 DOM API)来显示元素。这整个过程称为 mounting
  7. 接下来 React 调用另外一个生命周期函数,称为 componentDidMount。咱们能够这样使用这个方法,例如:在 DOM 上作一些咱们如今知道的在浏览器中存在的东西。在今生命周期方法以前,咱们使用的 DOM 都是虚拟的。
  8. 一些组件的故事到此结束,其余组件获得卸载浏览器 DOM 中的各类缘由。在后一种状况发生时,会调用另外一个生命周期的方法,componentWillUnmount
  9. 任何 mounted 的元素的状态均可能会改变。该元素的父级可能会从新渲染。不管哪一种状况,mounted 的元素均可能接收到不一样的属性集。React 的魔力就是这儿,咱们实际上须要的正是 React 的这一点!在这一点以前,说实话,咱们并不须要 React。
  10. 组价的故事还在继续,可是在此以前,咱们须要理解我所说的这种状态

7 React 组件能够具备私有状态

如下只适用于类组件。我有没有提到有人叫表象而已的部件 dumb

状态类是任何 React 类组件中的一个特殊字段。React 检测每个组件状态的变化,可是为了 React 更加有效,咱们必须经过 React 的另外一个 API 改变状态字段,这就是咱们要学习的另外一个 API —— this.setState

class CounterButton extends React.Component {
  state = {
    clickCounter: 0,
    currentTimestamp: new Date(),
  };

  handleClick = () => {
    this.setState((prevState) => {
     return { clickCounter: prevState.clickCounter + 1 };
    });
  };

  componentDidMount() {
   setInterval(() => {
     this.setState({ currentTimestamp: new Date() })
    }, 1000);
  }

  render() {
    return (
      <div> <button onClick={this.handleClick}>Click</button> <p>Clicked: {this.state.clickCounter}</p> <p>Time: {this.state.currentTimestamp.toLocaleString()}</p> </div>
    );
  }
}

// 使用
ReactDOM.render(<CounterButton />, mountNode);复制代码

例 13:setState 的 API

这多是最重要的一个例子由于这将是你彻底理解 React 基础知识的方式。这个例子以后,还有一些小事情须要学习,但从那时起主要是你和你的 JavaScript 技能。

让咱们来看一下例 13,从类开始,总共有两个,一个是一个初始化的有初始值为 0clickCounter 对象和一个从 new Date() 开始的 currentTimestamp

另外一个类是 handleClick 函数,在渲染方法中咱们给按钮元素传入 onClick 事件。经过使用 setStatehandleClick 方法修改了组件的实例状态。要注意到这一点。

另外一个咱们修改状态的地方是在一个内部的定时器,开始在内部的 componentDidMount 生命周期方法。它每秒钟调用一次而且执行另外一个函数调用 this.setState

在渲染方法中,咱们使用具备正常读语法的状态上的两个属性(没有专门的 API)。

如今,注意咱们更新状态使用两种不一样的方式:

  1. 经过传入一个函数而后返回一个对象。咱们在 handleClick 函数内部这样作。
  2. 经过传入一个正则对象,咱们在在区间回调中这样作。

这两种方式都是能够接受的,可是当你同时读写状态时,第一种方法是首选的(咱们这样作)。在区间回调中,咱们只向状态写入而不读它。当有问题时,老是使用第一个函数做为参数语法。伴随着竞争条件这更安全,由于 setstate 其实是一个异步方法。

咱们应该怎样更新状态呢?咱们返回一个有咱们想要更新的的值的对象。注意,在调用 setState 时,咱们所有都从状态中传入一个属性或者全都不。这彻底是能够的由于 setState 实际上 合并 了你经过它(返回值的函数参数)与现有的状态,因此,没有指定一个属性在调用 setState 时意味着咱们不但愿改变属性(但不删除它)。

8 React 将要反应

React 的名字是从状态改变的反应中得来的(虽然没有反应,但也是在一个时间表中)。这里有一个笑话,React 应该被命名为Schedule

然而,当任何组件的状态被更新时,咱们用肉眼观察到的是对该更新的反应,并自动反映了浏览器 DOM 中的更新(若是须要的话)。

将渲染函数的输入视为两种:

  • 经过父元素传入的属性
  • 以及能够随时更新的内部私有状态

当渲染函数的输入改变时,输出可能也会改变。

React 保存了渲染的历史记录,当它看到一个渲染与前一个不一样时,它将计算它们之间的差别,并将其有效地转换为在 DOM 中执行的实际 DOM 操做。

9 React 是你的代码

您能够将 React 看做是咱们用来与浏览器通讯的代理。以上面的当前时间戳显示为例。取代每一秒咱们都须要手动去浏览器调用 DOM API 操做来查找和更新 p#timestamp 元素,咱们仅仅改变组件的状态属性,React 作的工做表明咱们与浏览器的通讯。我相信这就是为何 React 这么受欢迎的真正缘由;咱们只是不喜欢和浏览器先生谈话(以及它所说的 DOM 语言的不少方言),而且 React 自愿传递给咱们,免费的!

10 每个 React 组件都有一个故事:第 2 部分

如今咱们知道了一个组件的状态,当该状态发生变化的时候,咱们来了解一下关于这个过程的最后几个概念。

  1. 当组件的状态被更新时,或者它的父进程决定更改它传递给组件的属性时,组件可能须要从新渲染。
  2. 若是后者发生,React 会调用另外一个生命周期方法:componentWillReceiveProps
  3. 若是状态对象或传递的属性改变了,React 有一个重要的决定要作:组件是否应该在 DOM 中更新?这就是为何它调用另外一个重要的生命周期方法 shouldComponentUpdate 的缘由 。此方法是一个实际问题,所以,若是须要自行定制或优化渲染过程,则必须经过返回 true 或 false 来回答这个问题。
  4. 若是没有自定义 shouldComponentUpdate,React 的默认事件在大多数状况下都能处理的很好。
  5. 首先,这个时候会调用另外一生命周期的方法:componentWillUpdate。而后,React 将计算新渲染过的输出,并将其与最后渲染的输出进行对比。
  6. 若是渲染过的输出和以前的相同,React 不进行处理(不须要和浏览器先生对话)。
  7. 若是有不一样的地方,React 将不一样传达给浏览器,像咱们以前看到的那样。
  8. 在任何状况下,一旦一个更新程序发生了,不管以何种方式(即便有相同的输出),React 会调用最后的生命周期方法:componentDidUpdate

生命周期方法是逃生舱口。若是你没有作什么特别的事情,你能够在没有它们的状况下建立完整的应用程序。它们很是方便地分析应用程序中正在发生的事情,并进一步优化 React 更新的性能。


信不信由你,经过上面所学的知识(或部分知识),你能够开始建立一些有趣的 React 应用程序。若是你渴望更多,看看个人 Pluralsight 的 React.js 入门课程

感谢阅读。若是您以为这篇文章有帮助,请点击下面的 💚。请关注个人更多关于 React.js 和 JavaScript 的文章


PluralsightLynda 建立了在线课程。我最新的文章在Advanced React.jsAdvanced Node.jsLearning Full-stack JavaScript中。我也作小组的在线和现场培训,覆盖初级到高级的 JavaScript、 Node.js、 React.js、GraphQL。若是你须要一个导师,请来找我 。若是你对此篇文章或者我写的其余任何文章有疑问,经过这个联系我,而且在 #questions 中提问。


感谢不少检验和改进这篇文章的读者,Łukasz Szewczak、Tim Broyles、 Kyle Holden、 Robert Axelse、 Bruce Lane、Irvin Waldman 和 Amie Wilt.

特别要感谢“惊人的” Amie,经验是一个实际的 Unicorn。谢谢你全部的帮助,Anime,真的很是感谢你。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索