time: 2022-02-24 14:33:11
author: heyunjiang
setup 作为 vue3 的核心功能,内部定义的响应式数据是如何被组件使用的呢,一起来看看 setup 的被执行流程
先来看看 <script setup>
组件的编译结果(简单可以使用 vite 本地开发,通过浏览器查看加载的 vue 文件即可)
源代码
<script lang="ts" setup>
import { ref } from 'vue'
const filterString = ref('')
</script>
<template>
<el-input v-model="filterString" />
</template>
编译结果
import { ref } from "/dist2/node_modules/.vite/vue.js?v=ede3a932";
const _sfc_main = /* @__PURE__ */ _defineComponent({
setup(__props, { expose }) {
expose();
const filterString = ref("");
const __returned__ = { filterString };
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
return __returned__;
}
});
import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "/dist2/node_modules/.vite/vue.js?v=ede3a932";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_el_input = _resolveComponent("el-input");
return _openBlock(), _createBlock(_component_el_input, {
modelValue: $setup.filterString,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => $setup.filterString = $event)
}, null, 8, ["modelValue"]);
}
import _export_sfc from "/dist2/@id/plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__file", "/Users/80245690/Desktop/project/dataspherestudio/web/packages/taskAnalysis/src/App.vue"]]);
在 vue3渲染流程 文章中说明了 _defineComponent, _export_sfc 的具体说明。这里简要总结
总结归纳:
在 app.mount 时,会调用组件的渲染流程,其中包含了 createVnode, render,其中 render 是直接调用 patch 来处理 vnode 的渲染;
在 patch 内部,如果判断为组件,则会走 mountComponent,内部包含了 createComponentInstance、setupComponent、setupRenderEffect;
vue3 渲染流程可以查看另一篇文章 而 setup 则是在 setupComponent 时调用
setupComponent
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
可以看到 setupComponent 主要是处理组件的 props, slot children, setup 函数处理 setupStatefulComponent 调用 setup
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
instance.accessCache = Object.create(null)
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
setCurrentInstance(instance)
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
if (isPromise(setupResult)) {
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
在 setupComponent.setupStatefulComponent 中调用了 setup 函数,返回的对象被 handleSetupResult 处理
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {
if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
instance.ssrRender = setupResult
} else {
instance.render = setupResult as InternalRenderFunction
}
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance, isSSR)
}
总结归纳:
instance.setupState
通常我们定义在 setup 中的数据,比如 const filterString = ref('')
定义的 ref 变量,是直接在 template 中使用 v-model
读取;而 template 又会最终编译为组件配置的 render 函数。
那么 setupState 通常是在 render 函数中的 createElement 节点去使用这些变量值。那么 render 函数又是啥时候被调用的?
初步猜测,是在实际渲染 vnode 时,也就是在 setupRenderEffect 中调用
setupRenderEffect.componentUpdateFn 中一段代码
const subTree = (instance.subTree = renderComponentRoot(instance))
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
在 renderComponentRoot 中,执行了 render
result = normalizeVNode(
render!.call(
proxyToUse,
proxyToUse!,
renderCache,
props,
setupState,
data,
ctx
)
)
在 render 中传入了 props, setupState, data, ctx 对象,render 咋用这些数据的呢?
还是回到编译结果上来看
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_el_input = _resolveComponent("el-input");
return _openBlock(), _createBlock(_component_el_input, {
modelValue: $setup.filterString,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => $setup.filterString = $event)
}, null, 8, ["modelValue"]);
}
可以看到,编译结果直接使用了 $setup 对象,也就是 instance.setupState 对象。
全文总结
<script setup>
最终编译为组件配置属性 setup 函数setupComponent
初始化时执行的,函数返回值赋予了 instance.setupState思考:我们代码中是否可以直接使用 $setup 对象
答:实例上只存在 setupState,$setup 只是函数定义的参数名;
如果直接使用 setupState,其实访问的是 _ctx.setupState,也是不行的;
我们使用 getCurrentInstance 呢?也拿不到组件属性,为什么?