React 简介

背景

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。

react 是什么?

React 是一个用于构建用户界面的 JavaScript 库。用于构建快速、高效的用户界面;它是一个轻量级库,遵循组件化设计模式、声明式编程范式以及函数式编程概念。它使用虚拟 DOM 来有效地操作 DOM;遵循从高阶组件到低阶组件的单向数据流。

在整个 Web 应用的 MVC 架构中,你可以将 React 看作为视图层,并且是一个高效的视图。React 提供了和以往不一样的方式来看待视图,它以组件开发为基础。对 React 应用而言,你需要分割你的页面,使其成为一个个的组件。也就是说,你的应用是由这些组件组合而成的。你可以通过分割组件的方式去开发复杂的页面或某个功能区块,并且组件是可以被复用的。这个过程大概类似于用乐高积木去拼装不同的物体。


特点

  • 采用组件化模式、声明式编码;提高开发效率和组件复用率
  • 使用虚拟 DOM 和 diffing 算法,减少与真实 DOM 的交互
  • 与已知的库或框架完好配合,相对灵活
  • 使用 JSX 语法糖
  • 单向数据流


Virtual DOM

为了跟踪模型层的变化,并且将其应用到 DOM 中(也就是渲染),我们需要注意两个重要的事情:

  • 数据是什么时候改变的
    React 提供了一个观察者模型用于替代传统的脏检查(dirty checking),也就是持续的检查模型的变化。这也就是解释了为什么 React 不需要计算哪些发生了改变的原因,因为它会立即知道。这个过程减少了计算量,使应用程序变得更平滑。
  • 哪一个(些)DOM 元素需要被更新
    React 在内存中构建了 DOM 的树形表示,并且计算出哪个 DOM 元素应该被改变。对浏览器而言,DOM 操纵是比较耗费性能的,因此我们更倾向于让其变得最小化。幸运的是,React 视图尽可能少的触及到 DOM 元素。给予对象表示而言, 更少的 DOM 操纵意味着计算会更快,因此 DOM 改变也被尽可能的减少。
    React 在底层实现了一个 diffing 算法,该算法使用 DOM 的树形表示法,当某个节点发生变化(标记为 dirty)时它会重新计算整个子树,你会注意到你的模型发生了改变,因为整个子树在之后会被重新渲染。

什么是虚拟 DOM?

在 react 中,简单来说虚拟 DOM 其实就是 JS 对象,将真实 DOM 抽象成一个 javascript 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function el(name, props, child) {
{
this.name = name;
this.props = props;
this.child = child;
}
}

var dom = new el("div", { id: "list" }, [
new el("span", { id: "span_one" }, ["第一个元素"]),
new el("span", { id: "span_two" }, ["第二个元素"]),
new el("span", { id: "span_three" }, ["第三个元素"]),
new el("span", { id: "span_four" }, ["第四个元素"]),
]);

这里的 dom 就是一个对象,里面包含了 name,props,child。
将对象转换成真实的 DOM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
el.prototype.render = function () {
var d = document.createElement(this.name);
for (key in this.props) {
d.setAttribute(key, this.props[key]);
}

this.child.forEach(function (child) {
if (child instanceof el) {
tnode = child.render();
} else {
tnode = document.createTextNode(child);
}
d.appendChild(tnode);
});
return d;
};

let ele = dom.render();
document.body.appendChild(ele);

原理

在 React 中,也有一个 render 函数来将虚拟 DOM 树转换成真实 DOM,并且,React 中有 state 转移的过程,所以每次 state 有变化之后,就会触发 render 函数,重新构造一个虚拟 DOM 树。对比新旧虚拟 DOM 树的差别,记录下差异,然后只针对差异部分对应的真实 DOM 进行操作。

  • 如何进行新旧虚拟 DOM 树的对比?
    这里采用的是 Diff 算法。Diff 算法比较复杂,主要的思路是这样的。
    首先,每一次生成的虚拟 DOM 树上的各个节点都对应唯一的一个 id,当第二次生成了新的 DOM 树时,对原来树上的每一个节点对比新树上对应节点,如果不同,就记录下来这个差异。同时,差异也分为很多种:
  1. 替换节点
  2. 删除、移动、增加节点
  3. 修改属性
  4. 对文本内容做修改

对每一类记录下差异后,针对不同的差异做不同的操作。

1
比如:替换节点就需要调原生JS的repaceChild()接口;对于修改属性,则要调setAttribute()接口等等。

JSX 语法

1
const element = <h1>Hello, Word!</h1>;

像这样的形式,被称为 JSX;是 javascript 的语法扩展。JSX 的基本语法和 XML 基本相同,都是使用成对标签的形式构成树状结构的数据。

为什么使用 JSX?

我们来看看 React 官方的回答:

简单来说,react 认为组件才是王道;而组件是和模板紧密关联的。组件模板和组件逻辑分离让问题复杂化了。
所以就有了 JSX 这种语法,就是为了把 HTML 模板直接嵌入到 JS 代码里面,这样就做到了模板和组件关联,但是 JS 不支持这种包含 HTML 的语法,所以需要通过工具将 JSX 编译输出成 JS 代码才能使用。

JSX 是可选的

因为 JSX 最终是输出成 JS 代码来表达的,所以我们可以直接用 React 提供的这些 DOM 构建方法来写模板,比如一个 JSX 写的一个链接:
<a href="https://react.docschina.org/">Hello!</a>
用 js 来写的的话就是:
React.createElement('a', {href: 'https://react.docschina.org/'}, 'Hello!')

JSX 语法糖允许前端开发者使用我们最熟悉的类 HTML 标签语法来创建虚拟 DOM 在降低学习成本的同时,也提升了研发效率与研发体验。

使用

标签类型

  • DOM 类型的标签(div,span 等), 使用时,标签首字母必须小写
  • React 组件类型的标签,使用时,组件名称的首字母必须大写

React 通过首字母的大小写来判断渲染的是 DOM 类型标签还是 React 组件类型标签。

JS 表达式

JSX 的本质是 javascript,在 JSX 中使用 JS 表达式需要用{}包裹起来。

  • 使用场景
  1. 通过表达式给标签属性赋值
    const dom = <Child data={ 1 + 1 } />
  2. 通过表达式定义子组件
1
2
3
4
5
6
7
const arr = ['li01','li02','li03']

const dom = {
<ul>
arr.map(item=>{<Item msg={item} key={item} />})
</ul>
}

标签属性

  1. 当 JSX 标签是 DOM 类型的标签时,对应的 DOM 标签支持的属性 JSX 也支持,部分属性名称有所变化,例如 class 写成 className,之所以改变是因为 React 对 DOM 标签支持的事件重新做了封装。事件属性名称采用驼峰格式。
  2. 当 JSX 标签是 React 组件类型时,可以自定义标签的属性名。

注释

JSX 的注释需要大括号”{}”将/**/包裹起来。


元素渲染

元素是构成 React 应用的最小砖块。

将一个元素渲染为 DOM

1
<div id="root"></div>
1
2
const dom = <h1>Hello, Word!</h1>;
ReactDOM.render(dom, document.getElementById("root"));

更新已渲染元素

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()。

1
2
3
4
5
6
7
8
9
10
11
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);

React 只更新它需要更新的部分

ReactDOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。

尽管每一秒都会新建一个描述整个 UI 树的元素,ReactDOM 只会更新实际改变了的内容。


组件

组件的定义

定义组件的两种方式:

  • 使用函数(函数组件)
  • 使用 ES6 class(类组件)

函数组件

1
2
3
function Funcomponent(props) {
return <h1>我是函数组件,{props.name}</h1>;
}

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

class 类组件

使用 class 定义组件满足的两个条件:

  1. class 继承自 React.Component。
  2. class 内部必须自定义 render 方法,render 方法返回代表该组件 UI 的 React 元素。
1
2
3
4
5
class Classcomponent extends React.Component {
render() {
return <h1>我是类组件,{this.props.name}</h1>;
}
}

组件渲染

1
const element = <Child name="child" />;

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

1
2
3
4
5
6
function Child(props) {
return <h1>Hello!{props.name}</h1>;
}

const element = <Child name="函数式组件" />;
ReactDOM.render(element, document.getElementById("root"));

来看看上述例子都做了什么?

  • 调用 ReactDOM.render()函数,并传入< Child name=”函数式组件” />作为参数。
  • React 调用 Child 组件,并将{name: ‘函数式组件’}作为 props 传入。
  • Child 组件将< h1 >Hello!函数式组件</ h1 >元素作为返回值。
  • React DOM 将 DOM 高效地更新为< h1 >Hello!函数式组件</ h1 >。

组合、嵌套组件

将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖。组件嵌套的方式就是将子组件写入到父组件的模板中去,且 React 没有 Vue 中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 从 react 的包当中引入了 React 和 React.js 的组件父类 Component
// 还引入了一个React.js里的一种特殊的组件 Fragment
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>Hello,React</h1>
)
}
}
class Content extends Component {
render () {
return (
<p>React.js是一个用于构建用户界面的JavaScript库</p>
)
}
}
/** 由于每个React组件只能有一个根节点,所以要渲染多个组件的时候,需要在最外层包一个容器,如果使用div, 会生成多余的一层dom
class App extends Component {
render () {
return (
<div>
<Title />
<Content />
</div>
)
}
}
**/
// 如果不想生成多余的一层dom可以使用React提供的Fragment组件在最外层进行包裹
class App extends Component {
render () {
return (
<Fragment>
<Title />
<Content />
</Fragment>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

Props(组件的数据挂载方式)

属性 props(只读性)

props 是由外部传进来的,也可以由组件内部设置初始化的 props;组件无论是使用函数式组件还是 class 组件,都不能修改自身的 props。可以通过父组件主动重新渲染的方式来传入新的 props。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component{
render(){
return (
<h1>Hello,{this.props.name}</h1>
)
}
}

const Conent = (props)=> {
return (
<p>{props.name}是一个用于构建用户界面的JavaScript库</p>
)
}

class App extends Component{
render(){
return(
<Fragment>
<Title name="React" />
<Conent name="React.js" />
</Fragment>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

设置组件的默认 props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component{
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render(){
return (
<h1>Hello,{this.props.name}</h1>
)
}
}

const Conent = (props)=> {
return (
<p>{props.name}是一个用于构建用户界面的JavaScript库</p>
)
}
// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
name: 'React.js'
}

class App extends Component{
render(){
return(
<Fragment>
{/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
<Title />
<Conent />
</Fragment>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

props.children

我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component{
render(){
return (
<h1>Hello,{this.props.children}</h1>
)
}
}

const Conent = (props)=> {
return (
<p>{props.children}</p>
)
}

class App extends Component{
render(){
return(
<Fragment>
<Title>React</Title>
<Conent><strong>React.js</strong>是一个用于构建用户界面的JavaScript库</Conent>
</Fragment>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

prop-types

React 其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React 提供了一种机制,让写组件的人可以给组件的 props 设定参数检查,需要安装和使用 prop-types:

1
npm i prop-types -S

State(状态)

组件的 state 是组件内部的状态,state 的变化最终将反映在组件 UI 的变化上。在构造方法 constructor 中通过 this.state 定义组件的初始状态,并调用 this.setState 方法改变组件的状态。

定义 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class App extends Component{
constructor(){
super()
this.state = {
name: 'React',
bool: true
}
}
render(){
return(
<div>
<h1>Welcome to {this.state.name} Word!</h1>
<button>{this.state.bool ? 'YES' : 'NO'}</button>
</div>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

this.props 和 this.state 是纯 js 对象,在 vue 中,data 属性是利用 Object.defineProperty 处理过的,更改 data 的数据的时候会触发数据的 getter 和 setter,但是 React 中没有做这样的处理,如果直接更改的话,react 是无法得知的,所以,需要使用特殊的更改状态的方法 setState。

setState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class App extends Component{
constructor(){
super()
this.state = {
name: 'React',
bool: true
}
}
// 添加点击事件
handleBtn = ()=>{
this.setState = ({
bool: !this.state.bool
})
}
render(){
return(
<div>
<h1>Welcome to {this.state.name} Word!</h1>
<button onClick={this.handleBtn}>{this.state.bool ? 'YES' : 'NO'}</button>
</div>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')

setState 有两个参数:

1
2
3
this.setState = {
bool: !this.state.bool,
};

注意的是这个方法接收两个参数,第一个是上一次的 state, 第二个是 props
setState 是异步的,所以想要获取到最新的 state,没有办法获取,就有了第二个参数,这是一个可选的回调函数

1
2
3
4
5
6
7
8
9
10
this.setState =
((preState, props) => {
return {
bool: !preState.bool,
};
},
() => {
console.log("callback", this.state.bool);
});
console.log("setState", this.state.bool);

Props 和 State 对比

都是纯 js 对象,都会触发 render 更新,都具有确定性(状态/属性相同,结果相同)

PropsState
能从父组件获取不能
可以由父组件修改不可以
能在内部设置默认值不能
不在组件内部修改要改
能设置子组件初始值不可以
可以修改子组件的值不可以
主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState 方法进行更新,setState 会导致组件的重新渲染。

React 组件是通过 props 和 state 两种数据驱动渲染出组件 UI。Props 是组件对外的接口,它是只读的,组件通过 props 接受外部传入的数据;state 是组件对内的接口,它是可变的,组件内部状态通过 state 来反映。

如果搞不清 state 和 props 的使用场景,记住一个简单的规则:尽量少地用 state,多用 props。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

状态提升

  • 如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
    具体操作可以查看 React 文档:状态提升

生命周期

  • 第一次渲染
    constructor → componentWillMount → render → componentDidMount
  • props 更新
    componentWillReceiveProps → shouldComponentUpdate(如果返回 true) → componentWillUpdate → render → componentDidUpdate
  • state 更新
    shouldComponentUpdate (如果返回 true)→ componentWillUpdate → render → componentDidUpdate
  • 组件卸载
    componentWillUnmount

React 旧版生命周期包含三个过程:

  1. 挂载过程
    constructor()
    componentWillMount()
    componentDidMount()
  2. 更新过程
    componentWillReceiveProps(nextProps)
    shouldComponentUpdate(nextProps,nextState)
    componentWillUpdate (nextProps,nextState)
    render()
    componentDidUpdate(prevProps,prevState)
  3. 卸载过程
    componentWillUnmount()

其具体作用分别为:

  • constructor()
    完成了 React 数据的初始化。
  • componentWillMount()
    组件已经完成初始化数据,但是还未渲染 DOM 时执行的逻辑,主要用于服务端渲染。
  • componentDidMount()
    组件第一次渲染完成时执行的逻辑,此时 DOM 节点已经生成了。
  • componentWillReceiveProps(nextProps)
    接收父组件新的 props 时,重新渲染组件执行的逻辑。
  • shouldComponentUpdate(nextProps, nextState)
    在 setState 以后,state 发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中 return false 可以阻止组件的更新,主要用于性能优化。
  • componentWillUpdate(nextProps, nextState)
    shouldComponentUpdate 返回 true 以后,组件进入重新渲染的流程时执行的逻辑。
  • render()
    页面渲染执行的逻辑,render 函数把 jsx 编译为函数并生成虚拟 dom,然后通过其 diff 算法比较更新前后的新旧 DOM 树,并渲染更改后的节点。
  • componentDidUpdate(prevProps, prevState)
    重新渲染后执行的逻辑。
  • componentWillUnmount()
    组件的卸载前执行的逻辑,比如进行“清除组件中所有的 setTimeout、setInterval 等计时器”或“移除所有组件中的监听器 removeEventListener”等操作。

所不同的是少了 componentWillMount, componentWillReceiveProps, componentWillUpdate 这三个鸡肋,其实这三个生命周期回调要直到 v17 才会被完全删除,不过现在都被冠以 UNSAFE_的前缀了。

多了两个是 getDerivedStateFromProps 和 getSnapshotBeforeUpdate,其中 getDerivedStateFromProps 在第一次渲染和更新的时候都会被调用,而 getSnapshotBeforeUpdate 只会在更新的时候被调用。

  • 使用 getDerivedStateFromProps(nextProps, prevState)的原因:

    旧的 React 中 componentWillReceiveProps 方法是用来判断前后两个 props 是否相同,如果不同,则将新的 props 更新到相应的 state 上去。在这个过程中我们实际上是可以访问到当前 props 的,这样我们可能会对 this.props 做一些奇奇怪怪的操作,很可能会破坏 state 数据的单一数据源,导致组件状态变得不可预测。

    而在 getDerivedStateFromProps 中禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去访问 this.props 并做其他一些让组件自身状态变得更加不可预测的事情。

  • 使用 getSnapshotBeforeUpdate(prevProps, prevState)的原因:

    在 React 开启异步渲染模式后,在执行函数时读到的 DOM 元素状态并不总是渲染时相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。

    而 getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。


事件处理

绑定事件

React 元素的事件处理和 DOM 元素很相似,都是采用 on+事件名称的方式绑定事件。但不同的是原生事件采用的是纯小写形式,如:onclick;而 React 的事件使用小驼峰的形式,如:onClick;使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

React 的事件不是原生事件,而是合成事件。

传统 HTML:

1
<button onclick="clickActive()">BUTTON</button>

React:

1
<button onClick="{clickActive}">BUTTON</button>

事件 handler 的写法

  • 直接在 render 里写行内的箭头函数(不推荐)
  • 在组件内使用箭头函数定义一个方法(推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 render 里直接使用 onClick={this.handleClick.bind(this)}(不推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 constructor 里 bind(this)(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };

// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState((state) => ({
isToggleOn: !state.isToggleOn,
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}

ReactDOM.render(<Toggle />, document.getElementById("root"));

Event 对象

和普通浏览器一样,事件 handler 会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React 中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有 event.stopPropagation、event.preventDefault 这种常用的方法

因此,在 React 中你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault。

1
<a href="#" onclick="console.log('click'); return false"></a>
1
2
3
4
5
6
7
8
function Active() {
function clickLink(e) {
e.preventDefault();
console.log("click");
}

return <a href="#" onClick={clickLink}></a>;
}

向事件处理程序传递参数

  1. 在 render 里调用方法的地方包一层箭头函数
  2. 在 render 里通过 this.handleEvent.bind(this, 参数)这样的方式来传递
  3. 通过 event 传递
1
2
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。

React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

条件表达式渲染(适用于两个组件二选一的渲染)

1
2
3
4
5
6
7
8
9
10
11
12
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}

&& 操作符渲染 (适用于一个组件有无的渲染)

1
2
3
4
5
6
7
8
9
function Mailbox(props) {
const messages = props.messages;
return (
<div>
<h1>Hello!</h1>
{messages.length > 0 && <h2>You have {messages.length} messages.</h2>}
</div>
);
}

利用变量输出组件渲染 (适用于有多个组件多种条件下的渲染)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render() {
const isLoggedIn = this.state.isLoggedIn;

const button = isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
);

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}

利用函数方法输出组件或者利用函数式组件进行渲染 (适用于多个子组件需要根据复杂的条件输出的情况)

函数方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
renderButton(){
const isLoggedIn = this.state.isLoggedIn;
if(isLoggedIn)
{
return (<LogoutButton onClick={this.handleLogoutClick} />);
}
else
{
return (<LoginButton onClick={this.handleLoginClick} />);
}
}

render() {
return (
<div>
<Greeting />
{this.renderButton()}
</div>
);
}

函数式组件

1
2
3
4
5
6
7
8
9
10
11
12
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}

ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById("root")
);

列表和 key

当列表数据为多组时,调用数据运行时为提示警告:“Each child in an array or iterator should have a unique ‘ key ’ prop ”说明如果没有给每条数据添加一个“key” 的话,当数据组数过多时,不同组数有相同的数据,调用就可能会出现错误。因此这里的“key”就相当于数据库里的主键,给每组数据调取都有一个参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.value}</li>;
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) => (
// 正确!key 应该在数组的上下文中被指定
<ListItem key={number.toString()} value={number} />
));
return <ul>{listItems}</ul>;
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById("root")
);
  • key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
  • 虽然列表元素的 key 不能重复,但这个唯一性仅限至于在当前列表中,而不是全局唯一。
  • 不推荐使用索引作为 key,因为一旦列表中的数据发生重排,数据的索引也会发生变化,不利于 React 的渲染优化。

表单

受控组件

什么受控组件?简单来说就是如果一个表单元素的值是由 React 来管理的,那它就是一个受控组件。

贴一段 React 官网的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import React, { Component } from "react";

class Inputs extends Component {
constructor(props) {
super(props);
this.state = {
name: "",
password: "",
};
}

handleChange(event) {
const e = event.target;
this.setState({
[e.name]: e.value,
});
}

handleSubmit(event) {
console.log("Login Success!");
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
         
<label>
          用户名:
<input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
/>
         
</label>
         <label>
          密码:
<input
type="password"
name="password"
value={this.state.password}
onChange={this.handleChange}
/>
         
</label>
         
<input type="submit" value="登录" />
</form>
);
}
}

用户名和密码两个表单元素的值是从组件的 state 中获取的,当用户更改表单元素的值时,onChange 事件会被触发,对应的 handleChange 处理函数会把变化同步到组件的 state,新的 state 又会触发表单元素重新渲染,从而实现表单元素状态的控制。


总结

本章主要介绍了 React 的主要特性及其用法。React 通过 JSX 语法声明界面 UI,将界面 UI 和它的逻辑封装在同一个 JS 文件中。通过虚拟 DOM 和 diffing 算法来减少与真实 DOM 的交互。组件是 React 的核心,根据组件的外部接口 props 和内部接口 state 完成自身的渲染。使用组件需要理解它的生命周期,借助不同的生命周期方法,组件可以实现复杂逻辑。渲染时,注意 key 的使用,事件处理时,注意事件名称和事件处理函数的写法。最后介绍表单元素的用法,使用方式分受控组价和非受控组件。