IMAGINE'S BLOG IMAGINE'S BLOG
首页
  • 原生JS

    • JavaScript
  • 前端框架扩展

    • Vue
    • React
    • UI组件库
  • HTML
  • CSS
  • 浏览器
  • 分类
  • 标签
  • 归档
  • 技术文档
  • GitHub相关
  • Nodejs
关于
  • 网站
  • 友情链接
GitHub (opens new window)

peng

平平无奇的web前端开发一枚
首页
  • 原生JS

    • JavaScript
  • 前端框架扩展

    • Vue
    • React
    • UI组件库
  • HTML
  • CSS
  • 浏览器
  • 分类
  • 标签
  • 归档
  • 技术文档
  • GitHub相关
  • Nodejs
关于
  • 网站
  • 友情链接
GitHub (opens new window)
  • React(一):核心概念
    • React 简介
      • 背景
      • react 是什么?
    • 特点
    • Virtual DOM
      • 什么是虚拟 DOM?
      • 原理
    • JSX 语法
      • 为什么使用 JSX?
      • JSX 是可选的
      • 使用
      • 标签类型
      • JS 表达式
      • 标签属性
      • 注释
    • 元素渲染
      • 将一个元素渲染为 DOM
      • 更新已渲染元素
      • React 只更新它需要更新的部分
    • 组件
      • 组件的定义
      • 函数组件
      • class 类组件
      • 组件渲染
      • 组合、嵌套组件
      • Props(组件的数据挂载方式)
      • 属性 props(只读性)
      • 设置组件的默认 props
      • props.children
      • prop-types
    • State(状态)
      • 定义 state
      • setState
    • Props 和 State 对比
    • 生命周期
      • React 旧版生命周期
      • React 新版生命周期
    • 事件处理
      • 绑定事件
      • 事件 handler 的写法
      • Event 对象
      • 向事件处理程序传递参数
    • 条件渲染
      • 条件表达式渲染(适用于两个组件二选一的渲染)
      • && 操作符渲染 (适用于一个组件有无的渲染)
      • 利用变量输出组件渲染 (适用于有多个组件多种条件下的渲染)
      • 利用函数方法输出组件或者利用函数式组件进行渲染 (适用于多个子组件需要根据复杂的条件输出的情况)
      • 函数方式
      • 函数式组件
    • 列表和 key
    • 表单
      • 受控组件
    • 总结
  • React
peng
2022-09-02
目录

React(一):核心概念

# 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 对象。

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" }, ["第四个元素"]),
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 原理

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

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

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

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

# JSX 语法

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

像这样的形式,被称为 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. 通过表达式定义子组件
  const arr = ['li01','li02','li03']

  const dom = {
    <ul>
      arr.map(item=>{<Item msg={item} key={item} />})
    </ul>
  }
1
2
3
4
5
6
7

# 标签属性

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

# 注释

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


# 元素渲染

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

# 将一个元素渲染为 DOM

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

# 更新已渲染元素

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。更新 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);
1
2
3
4
5
6
7
8
9
10
11

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

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

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


# 组件

# 组件的定义

定义组件的两种方式:

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

# 函数组件

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

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

# class 类组件

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

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

# 组件渲染

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

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

function Child(props) {
  return <h1>Hello!{props.name}</h1>;
}

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

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

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

# 组合、嵌套组件

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

// 从 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')
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

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

# 属性 props(只读性)

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

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')
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

# 设置组件的默认 props

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')
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

# props.children

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

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')
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

# prop-types

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

npm i prop-types -S
1

# State(状态)

提示

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

# 定义 state

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')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

提示

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

# setState

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')
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

setState 有两个参数:

  • 参数是对象
this.setState = {
  bool: !this.state.bool,
};
1
2
3
  • 参数是方法

注意

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

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

# Props 和 State 对比

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

Props State
能从父组件获取 不能
可以由父组件修改 不可以
能在内部设置默认值 不能
不在组件内部修改 要改
能设置子组件初始值 不可以
可以修改子组件的值 不可以
主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 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 文档:状态提升 (opens new window)

# 生命周期

# 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”等操作。

# React 新版生命周期

所不同的是少了 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:

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

React:

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

# 事件 handler 的写法

  • 直接在 render 里写行内的箭头函数(不推荐)
  • 在组件内使用箭头函数定义一个方法(推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 render 里直接使用 onClick={this.handleClick.bind(this)}(不推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 constructor 里 bind(this)(推荐)
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"));
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

# Event 对象

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

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

<a href="#" onclick="console.log('click'); return false"></a>
1
function Active() {
  function clickLink(e) {
    e.preventDefault();
    console.log("click");
  }

  return <a href="#" onClick={clickLink}></a>;
}
1
2
3
4
5
6
7
8

# 向事件处理程序传递参数

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

# 条件渲染

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

笔记

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

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

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
10
11
12

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

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

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

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

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

# 函数方式

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
13
14
15
16
17
18
19
20

# 函数式组件

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  <Greeting isLoggedIn={false} />,
  document.getElementById("root")
);
1
2
3
4
5
6
7
8
9
10
11
12

# 列表和 key

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

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")
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

笔记

  • key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
  • 虽然列表元素的 key 不能重复,但这个唯一性仅限至于在当前列表中,而不是全局唯一。
  • 不推荐使用索引作为 key,因为一旦列表中的数据发生重排,数据的索引也会发生变化,不利于 React 的渲染优化。

# 表单

# 受控组件

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

贴一段 React 官网的解释:

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>
    );
  }
}
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
54

笔记

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

# 总结

提示

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

#React
上次更新: 2022/09/06, 16:01:35
最近更新
01
Axios 封装
09-06
02
MySQL数据库常用操作
09-06
03
解决element表格数据量过大导致页面渲染缓慢、卡顿问题
09-06
更多文章>
Theme by Vdoing | Copyright © 2020-2024 peng | IMAGINE
image | imgloc.com
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式