|
开篇身为前端工程师,势必听过这个问题:“Vue与React有什么区别?”的确,Vue和React都是目前最流行、生态最好的框架,整体的功能也非常相似。对此,我们组织了一次diff主题分享,采用奇葩说的模式,针对同一功能讨论下vue与react的异同。本月主题将围绕以下角度带大家更深刻的认识vue和react框架:组件及逻辑复用,diff算法,编译构建及运行,渲染。vue与react都推崇组件式的开发理念,那么我们第一篇从组件开始,话不多说,直接进入本篇的正题:vue组件及逻辑复用的源码解析。正文关于vue的组件及逻辑复用,本篇将从三个功能点介绍:组件复用使用V-slot,通过源码分析其工作原理逻辑复用Mixin,源码解析之四种合并策略更好的组件组合式API:Composition源码解析-掌握Vue的全貌为了更深入的理解vue的设计思想,首先来整体了解下vue的全貌:❝2013年,在Google工作的尤雨溪,受到Angular的启发,开发出了一款轻量框架,最初命名为Seed。❝2015.10.26,1.0.0Evangelion是Vue历史上的第一个里程碑。同年,vue-router、vuex、vue-cli相继发布,标志着Vue从一个视图层库发展为一个渐进式框架。❝2016.10.01,2.0.0是第二个重要的里程碑,它吸收了React的虚拟Dom方案,还支持服务端渲染。自从Vue2.0发布之后,Vue就成了前端领域的热门话题。vue2源码结构:vue2源码结构vue2逻辑关系:vue2逻辑关系❝2019.02.05,Vue发布了2.6.0,这是一个承前启后的版本,在它之后,将推出3.0.0。❝2019.12.05,在万众期待中,尤雨溪公布了Vue3源代码,目前Vue3处于Alpha版本。vue3源码结构vue3源码结构vue3逻辑关系vue3逻辑关系至此,我们对源码结构及逻辑关系有了一个概念,接下来根据源码进行分析。组件复用使用V-slot组件的本质是一个AST树,Vue组件复用使用V-slot,其中包含了匿名插槽、具名插槽、作用域插槽,那么是他们在Vue生态中是如何运作的?我把它总结为以下三个步骤:首先,进行initRender初始化然后,进行rederSlot挂载最后,进行template编译v-slot工作原理接下来通过源码来展开分析下:以上源码结构梳理,我们得知vue2中源码核心代码在src-core中,进入源码,我们先看下init做了什么?// 初始化生命周期、初始化事件、初始化render函数等Vue.prototype._init = function(){ ... vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ...}初始化:在initRender中,我们看到了$slots与$scopedSlots挂载到了vdom上,并对slot中的children进行遍历,并组合成slots对象并返回。// initRender 初始化initRender (vm: Component) { ... vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject ...}// resolveSlots 遍历,并组合成 slots 对象并返回function resolveSlots ( children: ?Array, context: ?Component): { [key: string]: Array } { const slots = {} for (let i = 0, l = children.length; i Array) | Array), props: ?Object, bindObject: ?Object): ?Array { ... const target = props & props.slot if (target) { return this.$createElement('template', { slot: target }, nodes) } else { return nodes } ... }3、编译:将nodes进行编译并渲染到页面,这块在后面的编译渲染章节会着重讲解,这里先不展开。在Vue中v-slot可进行组件复用,对于逻辑的复用,我们就要用到mixin了。Mixins四种合并策略mixins定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改,以达到逻辑复用。那么它是什么时候开始合并的?又是怎么合并的?通过源码解读,绘制了一张图,大家先睹为快。接下来,我们带着以上两个问题,进入源码src-core中,我们看到在入口文件中第一行执行了initGlobalAPI(Vue),而在此函数中处理并挂载了一些全局对象及方法到Vue上。export function initGlobalAPI (Vue: GlobalAPI) { ... initUse(Vue) initMixin(Vue) initExtend(Vue) ...以上我们可以看到initMixin(Vue),第一个问题迎刃而解,minxin在这里进行了初始化:export function initMixin (Vue: GlobalAPI) { Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin) return this }}接下来minxin内部是如何进行合并的?我们进入mergeOptions中:优先判断有没有mixin里面挂mixin的情况有的话递归进行合并,最终形成一层嵌套关系。优先遍历parent的key,再遍历child中的key调用对应的strats[XXX]方法进行合并export function mergeOptions ( parent: Object, child: Object, vm?: Component): Object { ... // 递归并合并为一层嵌套关系 if (child.mixins) { for (let i = 0, l = child.mixins.length; i 组件mixin的mixin->组件mixin->组件optionsLIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook})// mergeHook 逻辑const res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal return resASSET_TYPES中包括component、directive、filter,它们通过原型链进行叠加。ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets})// mergeAssets 逻辑var res = Object.create(parentVal || null); if (childVal) { for (var key in childVal) { res[key] = childVal[key]; } } return resstrats.props=strats.methods=strats.inject=strats.computed直接替换。 if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret众所周知,随着项目结构的复杂,引用大量mixin会带来一些使用缺陷,比如:隐式依赖、命名冲突、侵入式等问题。因此,在vue3中出现了一个更好的组合式API,使组件及逻辑复用更加清晰,可读性更优。更好的组合式API:Composition初探根据个人理解,我认为vue3相比与vue2最大的变化应该是构造函数、响应式以及提出了CompositionApi概念。我们先从构造函数说起,vue2中需要newVue()来创建根实例并进行数据和方法的初始化;而vue3中则直接使用createApp来创建对象,去掉了Vue构造函数,避免非必要功能集成,减小体积。// vue2const app = new Vue(options);app.$mount("#app");Vue.use();// vue3const app = createApp(app);app.$mount("#app");app.use();Vue3使用Proxy代理取代Vue2的Object.defineProperty劫持,且响应式模块拆出,可单独使用,即reactivity,这里后边章节会介绍到,这里暂不展开。CompositionApi主要做了如下变更:CompositionApi使得组件化更加内聚,将零散分布的逻辑组合在一起,也可以将单独的功能逻辑拆分成单独的文件。在写法上更贴近于react的纯函数思想。这里重点讲一下setup,这个是vue3中新添加的函数,是vue3组件实例化的入口,在这个函数中是无法使用data和methods的,因此也不能使用this,这与它的调用时机有关系。而setup接收两个参数props和context来接收外部组件传入的值。那么setup是何时被注册的呢?执行createApp单元测试,让我们进入源码packages-runtime-core中//1- 初始化:通过ensureRenderer来 创建app对象const createApp = ((...args) => { const app = ensureRenderer().createApp(...args)}// 2 - ensureRenderer 调用 createRenderer 渲染函数function ensureRenderer() { return renderer || (renderer = createRenderer(rendererOptions))}// 3- createRenderer 调用 baseCreateRendererfunction createRenderer(options: RendererOptions) { return baseCreateRenderer(options)}// 4- baseCreateRenderer在这个方法中将vnode进行diff和patch操作并挂载到 container 上,最终返回 render hydrate createApp三个函数function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions): any { ... return { render, hydrate, createApp: createAppAPI(render, hydrate) }...//5- `patch`中出现了processComponent函数,继而调用了mountComponent,进入了setupComponent中,先解析属性,再解析slots,之后调用了setup函数。function setupComponent() { const propsOptions = Comp.props resolveProps(instance, initialVNode.props, propsOptions) resolveSlots(instance, initialVNode.children) setupStatefulComponent(instance, parentSuspense)}//6- 最终的执行结果handleSetupResult,如果是函数,则赋值给实例对象render,如果是对象,则创建响应式。export function handleSetupResult( instance: ComponentInternalInstance, setupResult: unknown, parentSuspense: SuspenseBoundary | null) { if (isFunction(setupResult)) { instance.render = setupResult as RenderFunction } else if (isObject(setupResult)) { instance.renderContext = reactive(setupResult) } finishComponentSetup(instance, parentSuspense)}最后,来看一下Vue2与Vue3在使用上的对比:// vue2// mixins.jsexport default { data() { return { x:0, y:0 } }, methods: { update(e) => { this.x = e.pageX this.y = e.pageY } }, mounted() { window.addEventListener('mousemove', update) }, unmounted() { window.removeEventListener('mousemove', update) },}...// 组件使用:xyz是哪来的?import appMixin from './mixins.js'export default { data() { return { x:0, y:0, z:10 } }}template: `{{ x }} {{ y }} {{ z }}`// vue3function useMouse() { const x = ref(0) const y = ref(0) const update = e => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y }}...// 组件使用const Component = { setup() { // 在组件中使用该函数 const { x, y } = useMouse() // 与其它函数配合使用 const { z } = useOtherLogic() return { x, y, z } }, template: `{{ x }} {{ y }} {{ z }}`}为了直观的感受下写法上的变化,直接上图ptions与omponsitiondiff结语回顾一下,本篇我们介绍了Vue的组件复用v-slot到逻辑复用mixins,再到vue3中的compositonAPI;也通过进入源码的分析,了解到Vue2向Vue3的设计思想发生的转变,而Vue和React越来越像,那是因为技术的底层逻辑都是相通的,框架的迭代升级也是为了更好的为开发者服务。我曾经看过这样一个比喻,觉得很有意思,分享给大家:❝Vue是自动挡;而React是手动挡自我解读下:vue的封装思想,使得开发者上手会更快一点;react则更接近底层,需要多操作几步。它们都是我们的代步工具,使我们脱离原始的步行,节奏更快,效率更高。剧透:下一篇将会介绍下React的逻辑复用,大家敬请期待。参考文章Vue发展历史简介:https://blog.csdn.net/h64257772/article/details/104467483【Vue源码】mixin源码解析:https://zhuanlan.zhihu.com/p/95838174Vue3.0新特性以及使用经验总结:https://juejin.cn/post/6940454764421316644#heading-6
|
|