引导语
上一个小节讲解了如何快速创建一个 React 应用,本小节我们开始巩固下 React 的基础知识。相信大家对 React JSX 都有一定的了解,但 JSX 的优点不仅仅是简化了写代码的过程,还需要思考为什么要使用 JSX 以及如何将 JSX 渲染到页面里。即使不了解也不用担心,接下来我们带大家一起深入学习 JSX 和 React 中是如何进行元素渲染的。
JSX 出现的原因
React 的一大亮点就是虚拟 DOM:可以在内存中创建虚拟 DOM 元素,由虚拟 DOM 来确保只对界面上真正变化的部分进行实际的 DOM 操作。和真实 DOM 一样,虚拟 DOM 也可以通过 JavaScript 来创建:
const ele = React.createElement('div', null, 'hello, world')
虽然通过以上的方式,就可以构建成 DOM 树,但这种代码可读性比较差,于是就有了 JSX 。JSX 是 JavaScript 的语法扩展,使用 JSX ,就可以采用我们熟悉的 HTML 标签形式来创建虚拟 DOM,也可以说 JSX 是 React.createElement
的一个语法糖。
JSX 语法
JSX 标签类型
// HTML 类型标签
const ele = <div>hello, world</div>
// react 组件类型标签
const component = <HelloWrold />
在 JSX 语法中,有两种标签类型:
- HTML 类型的标签:这种标签名需小写字母开头;
- 组件类型的标签(在之后的小节会详细介绍组件):这种标签必须以大写字母开头。
React 通过标签首字母的大小写来区分渲染的是标签类型。
React 中的所有标签,都必须有闭合标签
/>
JSX 中使用 JavaScript 表达式
在 JSX 中,也可以使用 JavaScript 表达式,只需要使用 {}
将表达式包裹起来就行。通常给标签属性传值或通过表达式定义子组件时会用到。例如下面代码:
// 属性传值
const addUser = {id: 1, name: '添加好友'}
<PanelItem item={addUser} />
// 通过表达式定义子组件
const teams = [
{id: 1, name: '创建高级群'},
{id: 2, name: '搜索高级群'}
]
<ul>
{teams.map(item =>
<PanelItem
item={item}
key={item.id}/>
)}
</ul>
JSX 中使用 JavaScript 表达式时,不能使用多行 JavaScript 语句。
JSX 编译之 Babel
JSX 并不是标准的 JavaScript,它需要编译器 Babel 将 JSX 代码编译成标准的 JavaScript 语言。
Babel 会把 JSX 转译成一个名为 React.createElement()
的函数调用,可以理解为 JSX 是使用一些规则来编写 React.createElement(type, config, children)
的快捷方式,可以将 JSX 视为编写 React.createElement()
声明的更简短且更自然的方法。
上文中JSX语法小节的 HellowWorld
组件,会被 Babel 编译成如下代码:
// HTML 类型标签
var ele = React.createElement("div", null, "hello, world")
// react 组件类型标签
var component = React.createElement(HelloWrold, null)
可以使用 Babel 官网提供的链接 https://www.babeljs.cn/repl 在线转译 JSX 代码。
JSX 有何优点
- 使用熟悉的语法定义 HTML 元素,提供更加语义化的标签,使用 JSX 编写模板更简单快速;
- 更加直观:JSX 让小组件更加简单、明了、直观;
- 抽象了 React 元素的创建过程,使得编写组件变得更加简单;
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化;
- JSX 是类型安全的,在编译过程中就能发现错误;
- 防注入攻击,所有的内容在渲染之前都被转换成了字符串,可以有效地防止
XSS(跨站脚本)
攻击。
元素渲染
在上文介绍的 JSX 中我们了解到如何创建 React 元素,但创建的 React 元素与浏览器的 DOM 元素不同,它是一个虚拟的 DOM 元素,在浏览器中我们需要把虚拟 DOM 转换成真实可用的浏览器 DOM,此过程就是元素渲染的过程。接下来,我们先来讨论下如何创建元素。
元素创建
React 元素是 React 应用中最小的可渲染单位,用于描述屏幕上输出的内容。我们可以使用 JSX 语法创建元素。
本篇主要讲解以下 3 种创建 React 元素的方式:
1. JSX 语法方式创建
const element = <h1>Hello, world</h1>;
使用 JSX 语法时我们必须引入 React 库。
JSX 的执行更快,而且编译时能及时发现错误,推荐大家使用此方式创建 React 元素。
2. React.createElement() 方式创建元素
// [] 中括号里的是选填项
React.createElement(
type, // 元素类型,可以是标签名字符串,也可以是 React 组件或者 React fragment, 比如:'div' 'span' 'React.Fragment'
[props], // 属性,对象形式,比如:{className: 'title'}
[...children] // 子节点,比如:react元素、dom元素或文本
)
这种方法其实在实际 React 开发中几乎不会使用,因为可以直接使用 JSX 方法(JSX 章节已详细讲解了 JSX 的优点,大家可以回顾下)。
3. React.cloneElement() 方式创建元素
// [] 中括号里的是选填项
React.cloneElement(
element, // 元素类型,必须是 React 元素
[props], // 属性,对象形式,比如:{className: 'title'}
[...children] // 子节点,比如:react元素、dom元素或文本
)
注意:createElement 的第一个参数必须是字符串或 React 组件,而在 cloneElement 里第一个参数应该是 ReactElement。
渲染方式
为了将 React 元素呈现到浏览器 DOM 中,我们需要有一个容器或者说根 DOM 元素来承载。一般在模板文件 index.html 使用一个 id 为 root 的 DOM 元素作为根元素。
//index.html
<div id="root"></div>
如何向根元素渲染一个简单的 React 元素呢?我们使用 ReactDOM.render()
方法渲染元素:
ReactDOM.render(
element, // 要渲染的元素
container, // 元素要渲染的容器
[callback] // 回调函数,可选的
)
ReactDOM.render()
方法有 3 个参数,它的第三个参数是一个回调函数,可以用来做渲染后的回调。
ReactDOM.render()
方法的返回值,其实是根元素的 React 实例。
我们需要在 index.js 文件中添加以下内容:
import React from 'react'
import ReactDOM from 'react-dom'
const myElement = <div>hello, world!</div>
ReactDOM.render(myElement, document.getElementById("root"))
React 16 之前的版本 DOM 不识别除了 HTML 和 SVG 支持的以外属性,而在 React 16 版本中将会把全部的属性传递给 DOM 元素。这个新特性可以让我们摆脱可用的 React DOM 属性白名单。
更新元素
React 元素是不可变的,一旦创建了元素,就无法更新其子代或者属性。我们已经创建并渲染了第一个 React 元素,但 React 并不是为了静态页面而存在的,为此,我们需要更新元素。因此,我们必须多次使用 render
方法来更新元素。对于大多数 React 应用而言,ReactDOM.render()
通常只会调用一次。React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会更新需要更新的部分。
读到此处,你是否好奇为什么 render
通常只调用一次?
在 React 中,render
执行得到的并不是真正的 DOM 节点,得到的仅仅是轻量级的 JavaScript 对象,我们称之为虚拟 DOM。
虚拟 DOM 具有批处理特性以及高效的 diff 算法(虚拟 DOM 和 diff 算法后续章节会有详细讲解),使得我们可以随时更新整个页面而无需担心性能问题。
虚拟 DOM 确保只对界面上真正变化的部分进行实际的 DOM 操作。
import React from 'react';
import './App.scss';
class App extends React.Component {
state = {
standingTime: 0
}
timeChange () {
setInterval(() => {
const current = this.state.standingTime + 1
this.setState({ standingTime: current })
}, 1000)
}
convertTheTime (time) {
if (time < 60) {
return `${time}秒`
} else if (time < 60 * 60) {
return `${Math.floor(time/60)}分${time%60}秒`
} else {
return `你停留时间超过一小时,请刷新重新计时`
}
}
componentDidMount () {
this.timeChange()
}
render() {
return (
<div className="App">
<div className="App-content">
<p>
你在本页面停留时间:
<b>
{ this.convertTheTime(this.state.standingTime) }
</b>
</p>
</div>
</div>
)
}
}
export default App;
提示:
和 DOM 元素不同的是,React 元素是纯对象,创建的代价低。
ReactDOM.render()
渲染传入容器节点的元素,首次调用时,将替换其中所有的 DOM 元素,以后的调用使用 React 的 虚拟 DOM diff 算法(虚拟 DOM diff 算法后续章节会有详细解读)进行有效的更新。ReactDOM.render()
不会修改容器节点(仅修改容器内的子节点)。
此外,元素和组件的概念是不一样的,组件是由元素组成的。
小结
本节内容主要讲解了 JSX 的语法及编译方式,随后在 JSX 的优点上做了深入的探究,之所以使用 JSX,不仅因为 JSX 速度更快,而且还能够在编译时及时发现错误。本节还阐述了元素渲染的方式及原理,render
时会对更新前后的虚拟 DOM 树做对比,只更新需要更新的部分。后续章节还会带大家探索渲染原理相关更多的奥秘,敬请期待。下一小节我们会继续学习 React 的基础知识点,为后续的章节夯实基础。