相信应用层面的知识,大家都比较熟悉了,实际 React 用来实现业务对于熟悉 Vue 的开发人员来说也不是难事,今天我们简单的了解一下 React 和 Vue 。(瞎聊聊)
先来两张源码编译图对比一下:
由于每个步骤能涉及的东西太多,所以本篇就简单聊一下他们的区别以及他在我们项目中实际的应用场景中能够做什么(想到什么聊什么)。
我们知道 Vue 和 React 都是通过替换调指定的 Dom 元素来渲染我们的组件,来看一下:
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
先说 Vue 的,new Vue 做了什么?相信读过源码的同学都会知道,他执行了一堆初始化操作 initLifecycle、initEvents、initRender、initInjections、initState、initProvide
。
具体包括以下操作:选项合并(用户选项、默认选项)、children、 refs、slots、 createElement等实例属性和方法初始化、自定义事件处理、数据响应式处理、生命周期钩子调用、可能的挂载。
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
var data = {
a: 1
}
var vm = new Vue({
data
})
vm.a = 1
data.a // 1
data.a = 2
vm.a // 2
Vue 通过劫持 get 和 set 来实现响应式原理,这也是与 React 最大区别所在,React 只能手动调用 this.setState
来将state
改变。
我在往期篇幅有具体谈过 Vue 的响应式原理: 深入浅出Vue响应式原理
当 data 中的数据实现了响应式之后,就开始在模板上做功夫了。
这里有一个很重要的东西叫虚拟 Dom。
所谓虚拟 DOM 就是用 js 来描述一个 DOM 节点,在 Vue 中通过 Vnode 类来描述各种真实 DOM 节点。
在视图渲染之前,把 template 先编译成虚拟 Dom 缓存下来,等数据发生变化需要重新渲染时,通过 diff 算法找出差异对比新旧节点(patch),之后把最终结果替换到真实 Dom 上,最终完成一次视图更新。
了解更多关于 diff 移步至:diff算法
关于编译原理要细聊就有点多了,大致总结一下:
模板字符串
转换成 AST语法树
(解析器)AST
进行静态节点标记,主要用来做虚拟 DOM 的渲染优化(优化器)element ASTs
生成 render
函数代码字符串(代码生成器)有兴趣请移步至: Vue 模板编译原理
在这些过程中,Vue 会暴露一些钩子函数供我们在适当时机去执行某些操作,这就是生命周期钩子函数。关于 Vue 的生命周期大家应该都熟记于心了,简单过一下:
大家可能会比较关心 React 会扯什么(猜的),毕竟 Vue 已经是家喻户晓,加上国内业务使用也是居多,生态圈及各类解决方案也是层出不穷。
ReactDOM.render 是 React 的最基本方法用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
import App from './App.jsx'
import ReactDOM from 'react-dom'
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
render 方法实际是调用了内部的 React.createElement 方法,进而执行 ReactMount._renderSubtreeIntoContainer
。
还有一个方法 ReactDOM.unmountComponentAtNode() 作用和 ReactDOM.render() 正好相反,他是清空一个渲染目标中的 React 部件或 HTML。
state 是 React 中很重要的东西,说到 state 就不得不提到 setState 这个方法,很多人认为 setState 是异步操作,其实并不是。之所以会有一种异步的表现方式是因为 React 本身的性能机制导致的。因为每次调用 setState 都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少 render 调用。
如图,setState 接受一个新状态并不会立即执行,而是存入 pending 队列中进行判断。
如果有阅读过源码的同学就会知道他在其中通过判断 isBatchingUpdates (是否是批量更新模式)来进行区分。
如果是,那就会将状态保存到 dirtyComponents (脏组件)。
如果否,那就遍历所有的脏组件,并调用 updateComponent 更新 pending 队列的 state 或 props。执行完后,将 isBatchingUpdates 设置为 true。
假如有如下代码:
for ( let i = 0; i < 100; i++ ) {
this.setState( { count: this.state.count + 1 } );
}
若 setState 是同步机制,那么这个组件会被 render 100次,这无疑对性能是毁灭性的。
当然 React 也想到了这个问题并做了处理:
React 会将 setState 的调用合并为一个执行,所以 setState 执行时我们并没有看到 state 马上更新,而是通过回调获取到更新后的数据(有点类似 Vue 中的 nextTick),也就是刚刚上图所叙。
对于首次渲染,React 的主要是通过 React.render 接收到的 VNode 转化为 Fiber 树,并根据树的层级关系构建出 Dom 树并渲染。
而二次渲染(更新),Fiber 树已经存在内存中了,所以 React 会计算 Fiber 树中的各个节点差异(diff),并将变化更新渲染。
实际上 Vue 和 React 的 diff 算法都是同层 diff,复杂度都为O(n),但是他们的不同在于 React 首位节点是固定不动的(除了删除),然后依次遍历对比。
Vue 的 diff 在 compile 阶段的 optimize 标记了 static 点,可以减少 diff 次数,而且是双向遍历方法,并且借鉴了开源库 snabbdom。(不论 Vue 还是 React 两者都是各有秋千)
再说回渲染, React 中也存在着和 Vue 一样的 VNode(虚拟 Dom)。
JSX 会被编译转换成 React.createElement 函数的调用,返回值就是 VNode,其作用和 Vue 中的 VNode 基本一致。
关于 Fiber 是一个比较抽象的概念比较难理解,可以理解为他是用来描述有关组件以及输入输出的信息的一个 JavaScript 对象。
了解更多 Fiber:Fiber传送门
小结一下:
React 渲染流程(浅看):
jsx --> createElement 函数 --> 这个函数帮助我们创建 ReactElement 对象(对象树) --> ReactDOM.render 函数 --> 映射到浏览器的真实DOM
在渲染过程中暴露出来的钩子就是生命周期钩子函数了,看图:
我在 Vue 转 React 系列中有提到过 ->传送门
组件的生命周期可分成三个状态:
简单过一下生命周期:
本文只是涉及内容众多,难免会有遗漏或不周,还请看官轻喷~
都看到这了,确定不点个赞(留言)再走吗?