找回密码
 会员注册
查看: 33|回复: 0

奇葩说框架之vue组件及逻辑复用

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-9-19 22:46:57 | 显示全部楼层 |阅读模式
开篇身为前端工程师,势必听过这个问题:“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
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-25 13:21 , Processed in 0.796967 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表