您现在的位置是:网站首页> 编程资料编程资料
vue3中的setup使用和原理解析_vue.js_
2023-05-24
248人已围观
简介 vue3中的setup使用和原理解析_vue.js_
1.前言
最近在做vue3相关的项目,用到了组合式api,对于vue3的语法的改进也是大为赞赏,用起来十分方便。对于已经熟悉vue2写法的同学也说,上手还是需要一定的学习成本,有可能目前停留在会写会用的阶段,但是setup带来哪些改变,以及ref,reactive这两api内部实现原理到底是什么,下面先来总结:
setup带来的改变:
1.解决了vue2的data和methods方法相距太远,无法组件之间复用
2.提供了script标签引入共同业务逻辑的代码块,顺序执行
3.script变成setup函数,默认暴露给模版
4.组件直接挂载,无需注册
5.自定义的指令也可以在模版中自动获得
6.this不再是这个活跃实例的引用
7.带来的大量全新api,比如defineProps,defineEmits,withDefault,toRef,toRefs
ref带来的改变:
Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式数据
Ref作TS的类型标注
reactive带来的改变:
可以使用 reactive() 函数创建一个响应式对象或数组
reactive可以隐式地从它的参数中推导类型
使用interface进行类型标注
需要了解vue2和vue3区别的可以查看我的这篇文章:
vue2和vue3的区别(由浅入深)_KinHKin(五年前端)的博客-CSDN博客_vue2开发好还是vue3开发好Vue2使⽤的是选项类型API(Options API),Vue3使⽤的是合成型API(Composition API)Vue3:数据和⽅法都定义在setup中,并统⼀进⾏return{}vue2和vue3比较还是有很多不一样的地方,比如setup语法糖的形式最为便捷而且更符合开发者习惯,未来vue3将会大面积使用这种规则,这样更加符合开发习惯和降低后续维护的成本,还有目前Vue3已经成为了Vue的默认版本,后续维护应该也会以Vue3为主。希望各位同学赶紧学起来吧https://www.jb51.net/article/174706.htm
2.setup
在 setup() 函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用构建工具来简化该操作。当使用单文件组件(SFC)时,我们可以使用 来大幅度地简化代码。
中的顶层的导入和变量声明可在同一组件的模板中直接使用。你可以理解为模板中的表达式和 中的代码处在同一个作用域中。
里面的代码会被编译成组件 setup() 函数的内容。这意味着与普通的 只在组件被首次引入的时候执行一次不同,中的代码会在每次组件实例被创建的时候执行。
官方解答:
更少的样板内容,更简洁的代码。能够使用纯 TypeScript 声明 props 和自定义事件。更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的语法,它具有更多优势:
setup执行是在创建实例之前就是beforeCreate执行,所以setup函数中的this还不是组件的实例,而是undefined,setup是同步的。
setup?: (this: void, props: Readonly
> & UnionToIntersection >>>, ctx: SetupContext ) => Promise | RawBindings | RenderFunction | void;)
在上面的代码中我们了解到了第一个参数props,还有第二个参数context。
props是接受父组件传递过来的所有的属性和方法;context是一个对象,这个对象不是响应式的,可以进行解构赋值。存在属性为attrs:instance.slots,slots: instance.slots,emit: instance.emit。
setup(props, { attrs, slots, emit, expose }) { ... } 或 setup(props, content) { const { attrs, slots, emit, expose } = content }这里要注意一下,attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。请注意,与 props 不同,attrs 和 slots 的 property 是非响应式的。如果你打算根据 attrs 或 slots 的更改应用副作用,那么应该在 onBeforeUpdate 生命周期钩子中执行此操作。
3.源码分析
在vue的3.2.3x版本中,处理setup函数源码文件位于:node_moudles/@vue/runtime-core/dist/runtime-core.cjs.js文件中。
setupStatefulComponent
下面开始解析一下setupStatefulComponent的执行过程:
function setupStatefulComponent(instance, isSSR) { var _a; const Component = instance.type; { if (Component.name) { validateComponentName(Component.name, instance.appContext.config); } if (Component.components) { const names = Object.keys(Component.components); for (let i = 0; i < names.length; i++) { validateComponentName(names[i], instance.appContext.config); } } if (Component.directives) { const names = Object.keys(Component.directives); for (let i = 0; i < names.length; i++) { validateDirectiveName(names[i]); } } if (Component.compilerOptions && isRuntimeOnly()) { warn(`"compilerOptions" is only supported when using a build of Vue that ` + `includes the runtime compiler. Since you are using a runtime-only ` + `build, the options should be passed via your build tool config instead.`); } } // 0. create render proxy property access cache instance.accessCache = Object.create(null); // 1. create public instance / render proxy // also mark it raw so it's never observed instance.proxy = reactivity.markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)); { exposePropsOnRenderContext(instance); } // 2. call setup() const { setup } = Component; if (setup) { const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null); setCurrentInstance(instance); reactivity.pauseTracking(); const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]); reactivity.resetTracking(); unsetCurrentInstance(); if (shared.isPromise(setupResult)) { setupResult.then(unsetCurrentInstance, unsetCurrentInstance); if (isSSR) { // return the promise so server-renderer can wait on it return setupResult .then((resolvedResult) => { handleSetupResult(instance, resolvedResult, isSSR); }) .catch(e => { handleError(e, instance, 0 /* ErrorCodes.SETUP_FUNCTION */); }); } else { // async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult; if (!instance.suspense) { const name = (_a = Component.name) !== null && _a !== void 0 ? _a : 'Anonymous'; warn(`Component <${name}>: setup function returned a promise, but no ` + ` boundary was found in the parent component tree. ` + `A component with async setup() must be nested in a ` + `in order to be rendered.`); } } } else { handleSetupResult(instance, setupResult, isSSR); } } else { finishComponentSetup(instance, isSSR); } } 函数接受两个参数,一个是组建实例,另一个是是否ssr渲染,接下来是验证过程,这里的文件是开发环境文件, DEV 环境,则会开始检测组件中的各种选项的命名,比如 name、components、directives 等,如果检测有问题,就会在开发环境报出警告。
检测完成之后,进行初始化,生成一个accessCached的属性对象,该属性用以缓存渲染器代理属性,以减少读取次数。然后在初始化一个代理的属性,instance.proxy = reactivity.markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));这个代理属性代理了组件的上下文,并且将它设置为观察原始值,这样这个代理对象将不会被追踪。
接下来便是setup的核心逻辑了,如果组件上有setup 函数,继续执行,如果不存在跳到尾部,执行finishComponentSetup(instance, isSSR),完成组件的初始化,否则就会进入 if (setup) 之后的分支条件中。是否执行setup生成上下文取决于setup.length > 1 ?createSetupContext(instance) : null。
来看一下setup执行上下文究竟有哪些东西:
function createSetupContext(instance) { const expose = exposed => { if (instance.exposed) { warn(`expose() should be called only once per setup().`); } instance.exposed = exposed || {}; }; let attrs; { // We use getters in dev in case libs like test-utils overwrite instance // properties (overwrites should not be done in prod) return Object.freeze({ get attrs() { return attrs || (attrs = createAttrsProxy(instance)); }, get slots() { return reactivity.shallowReadonly(instance.slots); }, get emit() { return (event, ...args) => instance.emit(event, ...args); }, expose }); } }expose解析:
可以在 setup() 中使用该 API 来清除地控制哪些内容会明确地公开暴露给组件使用者。
当你在封装组件时,如果嫌 ref 中暴露的内容过多,不妨用 expose 来约束一下输出。
import { ref } from 'vue' export default { setup(_, { expose }) { const count = ref(0) function increment() { count.value++ } // 仅仅暴露 increment 给父组件 expose({ increment }) return { increment, count } } }例如当你像上方代码一样使用 expose 时,父组件获取的 ref 对象里只会有 increment 属性,而 count 属性将不会暴露出去。
执行setup函数
在处理完 createSetupContext 的上下文后,组件会停止依赖收集,并且开始执行 setup 函数。
const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]);
Vue 会通过 callWithErrorHandling 调用 setup 函数,组件实例instance传入,这里我们可以看最后一行,是作为 args 参数传入的,与上文描述一样,props 会始终传入,若是 setup.length <= 1 , setupContext 则为 null。
调用玩setup之后,会重置收集的状态,reactivity.resetTracking(),接下来是判断setupResult的类型。
if (shared.isPromise(setupResult)) { setupResult.then(unsetCurrentInstance, unsetCurrentInstance); if (isSSR) { // return the promise so server-renderer can wait on it return setupResult .then((resolvedResult) => { handleSetupResult(instance, resolvedResult, isSSR); }) .catch(e => { handleError(e, instance, 0 /* ErrorCodes.SETUP_FUNCTION */); }); } else { // async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult; if (!instance.suspense) { const name = (_a = Component.name) !== null && _a !== void 0 ? _a : 'Anonymous'; warn(`Component <${name}>: setup function returned a promise, but no ` + ` boundary was found in the parent component tree. ` + `A component with async setup() must be nested in a ` + `in order to be rendered.`); } } } 如果 setup 函数的返回值是 promise 类型,并且是服务端渲染的,则会等待继续执行。否则就会报错,说当前版本的 Vue 并不支持 setup 返回 promise 对象。
如果不是 promise 类型返回值,则会通过 handleSetupResult 函数来处理返回结果。
else { handleSetupResult(instance, setupResult, isSSR); }function handleSetupResult(instance, setupResult, isSSR) { if (shared.isFunction(setupResult)) { // setup returned an inline render function if (instance.type.__ssrInlineRender) { // when the function's name is `ssrRender` (compiled by SFC inline mode), // set it as ssrRender instead. instance.ssrRender =
相关内容
- TypeScript获取二叉树的镜像实例_JavaScript_
- Vue生命周期与setup深入详解_vue.js_
- TypeScript数组实现栈与对象实现栈的区别详解_其它_
- Vuex的五大核心详细讲解_vue.js_
- 在vue中为什么不能用index作为key_javascript技巧_
- 前端算法之TypeScript包含min函数的栈实例详解_js其它_
- 微信小程序使用ucharts在小程序中加入横屏展示功能的全过程_javascript技巧_
- 超详细JavaScript深浅拷贝的实现教程_javascript技巧_
- Vue权限指令控制权限详解_vue.js_
- JS面试之对事件循环的理解_javascript技巧_
点击排行
本栏推荐
