进来聊聊!Vue 和 React 大杂烩!
相信应用层面的知识,大家都比较熟悉了,实际 React 用来实现业务对于熟悉 Vue 的开发人员来说也不是难事,今天我们简单的了解一下 React 和 Vue 。(瞎聊聊)
先来两张源码编译图对比一下:
由于每个步骤能涉及的东西太多,所以本篇就简单聊一下他们的区别以及他在我们项目中实际的应用场景中能够做什么(想到什么聊什么)。
Vue
new 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 的生命周期大家应该都熟记于心了,简单过一下:
beforeCreate (创建实例前的钩子,此时 data 里的数据还不能用。)
created (实例创建完成后的钩子,此时 data 已完成初始化可使用,但 Dom 尚未挂载。)
beforeMount (将编译完成的 HTML 挂载到对应虚拟 Dom,此时页面并无内容。)
mounted (Dom 已完成挂载,此时可以操作 Dom,此阶段也可以调用接口等操作。)
beforeUpdate (更新之前的钩子,当data变化时,会触发beforeUpdate方法。基本上没有什么用处。)
updated (更新之后的钩子,当数据变化导致地虚拟DOM重新渲染时会被调用,被调用时,组件DOM已经更新。建议不要在这个钩子函数中操作数据,可能陷入死循环。)
beforeDestory (实例销毁前的钩子,此时还可以使用 this,通常在这一步会进行清除计时器等操作)
destoryed (实例销毁完成的钩子,调用完成后,Vue实例的所有内容都会解绑定,移出全部事件监听器,同时销毁所有的子实例。)
React
大家可能会比较关心 React 会扯什么(猜的),毕竟 Vue 已经是家喻户晓,加上国内业务使用也是居多,生态圈及各类解决方案也是层出不穷。
ReactDOM.render
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。
React state
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 的主要是通过 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 系列中有提到过 ->传送门
组件的生命周期可分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
简单过一下生命周期:
componentWillMount 在渲染前调用,在客户端也在服务端。
componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用,可以在你确认不需要更新组件时使用。
componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
小结
本文只是涉及内容众多,难免会有遗漏或不周,还请看官轻喷
作者:饼干_
链接:https://juejin.cn/post/7016530148073668621