【D1N910】一线大厂React实践宝典 React 与 DOM(进度 4/9)
正常操作,正常分析,大家好,我是D1N910。
在大约两年前,我购买了 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。
因为我一直基本上是使用Vue来进行前端页面的开发,但是一直没有时间去实践看完。 两年没去看了,真的很惭愧,时间,唉,过得真快啊。
为了在这门课过期之前看完,所以我要抓紧时间学完了。
本系列的专栏记录了我学习 一线大厂React实践宝典 的笔记。
下面的内容版权归属是 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。
当然,如果我微不足道的笔记也算是我自己的版权,那么也是有我的小小一份。
你可以用来学习 React。
但是如果你用来商业化盈利,请先获得 腾讯课堂【NEXT】一线大厂 React 实践宝典 制作方的许可。
BTW,作为挖坑狂魔,如果这篇专栏没有更新,那么当我没说过。
这是一个系列的文章,如果你没有看过前面的内容,那么建议你先看一看


这是一个自己的约定,五一,冲冲冲!
现在,拿下 《React 组件》这个战壕,能达到今天进度的 1/3,疲惫不堪的战士哟,加油,加油!

虚拟DOM
小剧场
求知男:React很方便,视图更新不用书写操作 DOM 的代码,它是怎么做到的呢。
聪明男:这个就涉及到了虚拟 DOM 的概念了,我们对 React 组件状态的更新,可以通过虚拟 DOM 反映到实际的 DOM中。所以在 React 当中,我们只需要维护 React 组件的状态即可。
求知男:由面向 DOM 编程转向面向状态编程,虚拟 DOM 真是个好东西。
聪明男:Yep。虚拟 DOM 为了我们减少了很多复杂的 DOM 操作,而且还能够提高性能。
概念介绍
虚拟 DOM 是提高性能的核心部分。
React 把 DOM 抽象为了 JavaScript 的对象,我们称之为虚拟 DOM。
虚拟 DOM 本质是一个 JavaScript 对象
React 组件在发生变换时,都会自动同步到这个虚拟 DOM 中。
修改组件 -> 反映到虚拟 DOM -> 对比、批处理等 -> 同步到实际 DOM
为什么使用虚拟 DOM
(1)性能
频繁操作 DOM 很消耗页面性能的。
虚拟 DOM 优化了操作 DOM 的方式,减少了操作 DOM 的次数,提高了性能。
(2)开发
直接操作 DOM 的开发方式,开发体验不是很好;不利于项目的维护。对于每一种操作,都要写特殊的操作 DOM 的方式,对于日后需求变更,不利于维护。
虚拟 DOM 让开发人员避免直接操作 DOM。它提供了通用的方案,开发同学只需要维护状态即可。
虚拟 DOM 与 DOM 的性能对比
首先界面上分别用 react 和原生 JS 渲染出两条各有一千条数据的列表,点击 sort 按钮能够重排序,且最终能够在控制台中打印出执行时间
(用 console.time 和 console.timeEnd 实现执行时间的记录)

左侧 react 渲染的写法

右侧原生渲染的写法

分别执行后发现,看起来 React 的执行效率远远大于 原生JS 渲染的嘛

但其实原生 js 这边的排序方法里的渲染方法可以优化的。比如可以等全部执行完了以后再进行url的赋值


这时候看起来原生 JS 进行的 DOM 操作效率就要高一些了。

诶?那这样 React 的虚拟 DOM 有啥用哦。
首先这边原生 JS 更快,是因为少了虚拟 DOM 的一些操作。
React 的虚拟 DOM 并不意味着就一定比原生 JS 要来得更快,它是提供了一种通用的优化方法来帮助我们对 DOM 的进行操作。
其实我们能够注意到,我们方才对原生 JS 的优化,是需要每次都要专门人工去写特殊的优化。
如果我们某些时刻忘记了这些优化,就会无意间导致性能大大降低。
而且像刚刚我们做的原生 JS 的优化,把 innerHTML 专门抽离出来,只做一次更新,不是常见的操作。
一般这种数据更改,是类似上面的单独操作的,比如用户在一个购物车列表里单独对于某几个商品不断地点击添加按钮,这样,他们的每一次操作都会触发页面重绘。
这时候性能的差距就体现出来了。
就是前面的941ms的结果了。
频繁直接操作 DOM,消耗会很大。但是频繁操作虚拟 DOM,一个 JavaScript 对象,它的开销是非常低的。
React 为我们提高了一种非常好的途径去提高页面的性能。
虚拟 DOM 的工作流程
在传统 APP下:
传统 App -> (构建 / 修改) -> DOM
DOM-> (事件) -> DOM
在 React App 下:
React App -> (构建 / 修改) -> 虚拟 DOM -> (比较 / 批处理,构建 / 修改) -> DOM
DOM -> (事件) -> 虚拟 DOM -> (事件) -> React App
对于React App来说,它只面对虚拟 DOM 进行操作,只需要考虑组件本身数据、状态即可。
React 状态变更 ->
触发 Render 方法 ->
产生新的虚拟 DOM 树 ->
计算新旧 DOM 树的差异(Diff 算法)->
将差异更新到真实DOM中
Diff 算法
视频里没有讲 Diff 算法具体怎么跑。只是说 Diff 算法从虚拟 DOM 的根结点开始遍历,查看具体变化(新增、修改、删除节点),最后将这个变化反馈到真实 DOM 上。视频里强调的是 Diff 算法的作用(好处),并没有说 Diff 算法在 React 中具体怎么实现的。
总结
1、Diff 提供了一种通用的方式来执行各种操作;
2、Diff 找到了一种最小的改变方式,来避免直接改变实际 DOM
这里做个小计划,后面会安排查看 Diff 算法的具体实现。
本小节没有 【资料】Diff 算法,真的好遗憾。
但是后面的源码讲解里有这个部分的内容,期待!
本小节完毕,代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-10-virtual-dom

React 与 DOM
小剧场 - React 与 DOM 的联系
求知男:我们现在都是通过状态来控制视图,但在某些场景下,希望能够直接操作 DOM 节点,应该怎么做的?
聪明男:OK,你说的是什么场景呢?
求知男:例如我这里需要聚焦一个文本框,直接操作 DOM 会比较简单。
聪明男:u1s1,确实,当然 React 已经想到过这些问题了,下面我们就来学习这块内容吧。
Refs & DOM
Refs 的概念
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或者 React 元素。
父组件可以通过传递 Props 来操作子组件。组件本身可以通过 Refs 获取相关 DOM 节点的实例,通过 Refs 可以直接操作 DOM 来更新组件。
常见的场景举例
(1)处理焦点
比如页面出现 input 输入框的时候需要自动聚焦。
(2)文本选择
比如选择页面上某些元素的文本内容
(3)媒体控制
视频或者音频的播放器,使用 audio 、video 的 api。
(4)触发强制动画
使用实际的 dom 操作才可以进行动画。
Refs的使用:通过Refs访问 DOM 节点
方法1、使用 React.createRef & ref.current 来定义并获取 Refs
1、创建 Ref
在构建函数中,使用 React.createRef() 来创建 Ref

2、绑定 Ref
在 render 中,通过修改 ref 属性,绑定上了 Ref

3、访问 DOM 节点
在 componentDidMount 这个组件实际挂载到 dom 后执行的声明周期里,通过
this.myRef.current 使用 DOM 节点,然后可以调用 DOM 节点上的方法。

为了能够让效果明显点,我这边设置的是,页面渲染完毕后,让 input 自动获取焦点,且设置 value 值为成功。
构建后查看页面,发现方法成功了。

方法2、通过回调 Refs 获取 DOM 节点
1、初始化一个变量存储 Ref

2、传入回调函数直接绑定 DOM 实例
这里的 ref 实际上就是上一个方法的 this.myRef.current

3、访问 DOM 节点(本此不需要 current)

效果也一样,说明这种方法也成功了

【资料】Refs & 函数式组件
Refs
注:本扩展所讲解的 Refs 基于 React 16 以及以后的版本
Refs 提供了一种访问 React 组件渲染之后对应的 DOM 节点的方式。
一般情况下,我们在以下几种场景中可能会用到 refs:
管理焦点、文本选择或媒体播放
触发动画
与第三方 DOM 库继承或者交互
一般来说,我们要尽可能地减少直接使用 Refs 的情况,避免将 Refs 用于可以声明性地完成的任何操作。
创建 Refs
Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
* 参考上面的方法一
访问 Refs
如果一个 ref 已经被传递到了 render 函数的一个元素,那么一个该节点的 DOM 引用可以通过访问 current 属性来获得
const node = this.myRef.current
ref 的值根据节点的类型而有所不同:
当 ref 属性用于 HTML 元素时,在构造函数中使用 React.createRef() 创建的 ref 将接收底层 DOM 元素作为其当前属性
当 ref 属性用于自定义组件的时候,ref 对象将这个自定义组件的已经挂载的实例作为其 current 属性值
你不应该把 ref 属性用于函数式组件因为他们并没有实例
函数式组件
(* 我们之前有实践过)
React 函数式组件是相对于 React 基于 Class 类声明的组件的另一种写法,其具有以下的特性:
不需要声明类,可以避免大量的譬如 extends 或者 constructor 这样的代码。
不需要显式声明 this 关键字,在 ES6 的类声明中往往需要将函数的 this 关键字绑定到当前作用域,而因为函数式声明的特性,我们不需要再强制绑定。
易于理解与测试。
更佳的性能表现:因为函数式组件中并不需要进行生命周期的管理与状态管理,因此 React 并不需要进行某些特定的检查或者内存分配,从而保证了更好的性能表现。
下面一个基于 Class 类声明的组件:

如果变换成函数式组件写法,则:

另外,函数式组件也可以访问 context,代码如下。需要注意的是,
1、想要传递 context,需要定义childContextTypes
2、因为 babel 转换问题,这种静态的写法是 ES7 的内容,所以要在配置 .babelrc 配置 stage-1,这个配置之前我们是没有的,如果没有配置的话,可能会导致 babel 提示不能够转换 "static childContextTypes = {",⬅️ 这个问题我纠结了 20min 才解决了,我还是太菜了。

本小节完毕,相关代码
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-11-autofocus

React 事件语法
传统事件
可以使用 onclick 绑定一个点击方法
<button onclick="showLog()">按钮</button>
* 使用小写写法
* 接受参数是字符串
React 事件
<button onClick={this.showLog}>按钮</button>
* 使用驼峰写法
* 接受参数是一个函数
因为 React 用的是 JSX 语法,会被编译成 React.createElement ,然后被 React 接管的。为了识别点击事件,在虚拟 DOM 中和实际 DOM 的点击事件绑定起来,就需要这种写法。
React 事件函数中的 this
需要在函数中传入 this 的原因,是因为我们的写法是ES6。
我们的函数一般是作为类的方法声明的。在这样的声明中,默认不会传递 this。
所以我们需要主动传递 this ,通过某种方法使得我们的事件函数的 this 主动绑定到当前的组件。否则我们直接在函数中使用 this,会提示 undefined。
首先,函数式组件没有 this,所以不需要额外绑定。
这里我们讨论的是类定义组件。
类定义组件绑定 this 的写法
1、bind 方法
(1)新增一个事件方法

(2)构建函数中为事件方法绑定this

(3)事件绑定


* 也可以直接在事件绑定的时候绑定 this

2、箭头函数
(1)新增一个事件方法

(2)添加箭头绑定方法

传递参数
1、bind 方式
bind 方式下要传递参数,bind只能写到事件属性中,写法是 this.事件函数.bind(this, ...传递参数)。
事件函数接收参数的时候,这里触发事件的 event 永远会隐式作为最后一个参数传入。
例子

2、箭头函数 方式
这里的event需要显示传入,当然这里的位置排列就是我们自定义了~

【资料】React 合成事件
上面我们已经学会了如何在 React 中绑定事件。
以我们刚刚使用的 onClick 为例子,这个 onClick 实际上是 React 的合成事件。React 并不是将 click 事件绑在该 div 的真实 DOM 上,而是类似事件代理的机制,在 document 处监听所有支持的事件,当事件发生并冒泡至 document 处时,React 将事件内容封装并交由真正的处理函数运行。
相关流程如下:
div -> 事件冒泡 -> document -> synthetic event -> event -> handler
其中因为 event 对象是复用的,事件处理函数执行完后,属性会被清空,所以 event 的属性无法被异步访问。

React 原生事件与合成事件
如果我们想在 React 中使用原生的 dom 事件,我们可以通过 ref 的方式拿到对应组件生成的 dom 元素,然后在对应的 dom 元素上绑定事件,不过一般,不推荐这种做法。
以下是一个共同使用原生事件与合成事件的 Demo。

能看出先后执行顺序,验证了 React 其实是在事件冒泡到 document 时才开始做处理的。
如果阻止事件冒泡,那么事件是不会到达 React 的合成事件回调函数中的。

本小节完毕,相关代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-12-handle-event

事件系统的运用
我想实现一个登录框(小剧场)
A:想实现一个表单组件,有什么思路吗?
B:可以用受控组件或者非受控组件完成
A:这两种有什么区别吗?
B:受控组件通过状态控制表单,用户的操作只会修改状态值,如何去修改表单视图由我们自己控制。
A:Get,那么非受控组件就是原始的表单形式吧?
B:没错,这种形式的表单可以通过 Refs 来获取到,如果我们需要获得一些值或者操作,直接修改 Dom 节点即可。
A:如何选择呢?
B:在一般情况下,我们在使用表单的时候呢,只需要使用非受控组件即可。如果我们需要状态同步或者组件更新,则用受控组件好一些。
受控组件
表单是 React 中很重要的领域。
受控组件的概念
输入的值由 React 控制的,就叫做受控组件
用户输入 = (表单变化)=> newState
我们的输入会被事件函数托管,产生表单变换,然后设置新的状态去更新我们的表单值。
用户输入,表单值发生变化 -> 触发 onchange 事件函数 -> 调用 setState 更新状态 -> 触发 render 重新渲染 -> 表单数据更新成功
我们称符合这样更新数据流程的表单组件为受控组件。
受控组件如何使用
1、创建一个表单

特点:value 和 state 相关联;onChange 绑定了事件函数。
2、添加 onChange 事件函数

通过 e.targe.value 可以获取最新的表单值。

如果我们不设置 onChange 方法,那么就是只读的,要设置为readyOnly

常用表单组件
textarea 标签
HMTL 写法 和 React 写法差不多

select 标签
HTML 写法
是用 selected 属性表示被选中
<select>
<option value="cat" selected>cat</option>
<option value="dog">dog</option>
<option value="horse">horse</option>
</select>
React 写法
用 value 判断被选中

checkbox
React 写法
用 checked 判断是否选中

radio
React 写法
用 checked 判断是否选中

通用的修改受控组件的办法
利用 name 来进行通用的受控组件的数据变换,需要注意的是,在 checkbox 类型的情况下,value 值对应的是 checked 来判断复选框是否选中。

最后效果如下

非受控组件
React 表单元素 + Refs = 非受控组件
在需要的时候,通过 Refs 来获取到输入值。
非受控组件 -> 用户输入表单值发生变化 -> 通过 Refs 访问表单节点,获取最新表单数据。
使用方法:
1、创建一个表单

2、获取表单值


非受控组件的默认值
在受控组件中,在state里可以设置默认值。
在非受控组件中有两种方式
(1)text input / select 表单 / textarea 表单
使用 defaultValue

(2)checkbox / radio
使用 defaultChecked

为什么不能使用 value 绑定默认值?
React 中,表单元素上的 value 属性将会覆盖 DOM 中的值。value 属性需要搭配 onChange 属性一起使用,否则会导致用户的输入无法更新到表单上。
在表单 UI 反馈简单的场景中,我们可以使用非受控组件。
即时验证的场景,例如验证表单是否是一个邮箱、手机号码等,这种场景需要即时反馈输入错误的 ui,使用受控组件是更好的方式。
本小节结束,代码在此:
https://github.com/D1N910/learn-react/tree/master/Chapter3/demo-13-form

Part 3: React 与 DOM 学习完毕
总体学习进度(4/9)
我的妈妈妈妈妈妈妈妈妈妈妈
本来以为现在这个点钟 21:13 分
不是看到了 5 就是看到 6了,但是没想到才刚看完!
估计是脑子不太清醒吧。
我也太摸鱼了 QAQ
本次的课后项目写一下吧
要求自己写一个
可以对邮箱进行验证、对详细信息进行字数限制、昵称和标题不能为空等限制
的表单验证组件
上面的内容都是学习自 腾讯课堂 【NEXT】学院 一线大厂React实践宝典
个人学习笔记
继续加油加油(无力)
from 蛋糕