vue3.0新增特性之组合式API(setup)

2022-03-18 13:10:01

新特性

setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口
在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data 、computed 或 methods 被解析之前,所以它们无法在 setup 中被获取。

使用示例

import{ fetchUserRepositories}from'@/api/repositories'import{ ref}from'vue'exportdefault{components:{ RepositoriesFilters, RepositoriesSortBy, RepositoriesList},props:{user:{type: String,required:true}},setup(props){const repositories=ref([])// 创建一个响应式的repositories变量constgetUserRepositories=async()=>{
      repositories.value=awaitfetchUserRepositories(props.user)}return{
      repositories,
      getUserRepositories}},data(){return{filters:{...},// 3searchQuery:''// 2}},computed:{filteredRepositories(){...},// 3repositoriesMatchingSearchQuery(){...},// 2},watch:{user:'getUserRepositories'// 1},methods:{updateFilters(){...},// 3},mounted(){this.getUserRepositories()// 1}}

源码分析

源码位置: vue/runtime-core/src/component.ts

  • 判断是否是一个有状态组件
  • 初始化props
  • 初始化插槽
  • 设置有状态的组件实例
  • 返回组件实例

setupComponent

exportfunctionsetupComponent(instance: ComponentInternalInstance,
  isSSR=false){
  isInSSRComponentSetup= isSSRconst{ props, children}= instance.vnode// 判断是否是一个有状态的组件const isStateful=isStatefulComponent(instance)// 初始化propsinitProps(instance, props, isStateful, isSSR)// 初始化插槽initSlots(instance, children)//设置有状态的组件实例const setupResult= isStateful?setupStatefulComponent(instance, isSSR):undefined
  isInSSRComponentSetup=falsereturn setupResult}

setupStatefulComponent

  • 创建渲染代理的属性访问缓存

如果是 DEV 环境,则会开始检测组件中的各种选项的命名,比如 name、components、directives 等,如果检测有问题,就会在开发环境报出警告。

  • 创建渲染上下文的代理

会在实例上创建一个 accessCache 的属性,该属性用以缓存渲染器代理属性,以减少读取次数。之后会在组件实例上初始化一个代理属性,这个代理属性代理了组件的上下文,并且将它设置为观察原始值,这样这个代理对象将不会被追踪

  • 判断处理setup函数

从组件中取出 setup 函数,这里判断是否存在 setup 函数,如果不存在,则直接跳转到底部逻辑,执行 finishComponentSetup,完成组件初始化。否则就会进入 if (setup) 之后的分支条件中

  • 如果setup函数带有参数,则创建一个setupContext

生成 setup 的上下文对象
在 function 函数对象上调用 length 时,返回值是这个函数的形参数量。ru: setup(props) => setup.length === 1

  • 执行setup函数,获取结果

callWithErrorHandling函数获取setupResult结果
resetTracking重置依赖收集状态

  • 处理setup执行结果

判断 setupResult 的返回值类型
如果不是 promise 类型返回值,则会通过 handleSetupResult 函数来处理返回结果

functionsetupStatefulComponent(instance: ComponentInternalInstance,isSSR: boolean){const Component= instance.typeas ComponentOptionsif(__DEV__){/* 检测组件名称、指令、编译选项等等,有错误则报警 */// ...}// 0. 创建一个渲染代理的属性的访问缓存
  instance.accessCache= Object.create(null)// 1. 创建一个公共的示例或渲染器代理// 它将被标记为 raw,所以它不会被追踪
  instance.proxy=markRaw(newProxy(instance.ctx, PublicInstanceProxyHandlers))if(__DEV__){exposePropsOnRenderContext(instance)}// 2. 调用setup()const{ setup}= Componentif(setup){const setupContext=(instance.setupContext=
      setup.length>1?createSetupContext(instance):null)setCurrentInstance(instance)pauseTracking()// 执行setup函数const setupResult=callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,[__DEV__?shallowReadonly(instance.props): instance.props, setupContext])// 重置依赖收集resetTracking()unsetCurrentInstance()if(isPromise(setupResult)){
      setupResult.then(unsetCurrentInstance, unsetCurrentInstance).../}else{// 捕获 Setup 执行结果handleSetupResult(instance, setupResult, isSSR)}}else{// 完成组件初始化finishComponentSetup(instance, isSSR)}}

createSetupContext

setup 上下文(setupContext)中就如文档中描述的一样,有 attrs、slots、emit 这三种熟悉的属性,另外还有expose属性

exportfunctioncreateSetupContext(instance: ComponentInternalInstance): SetupContext{constexpose: SetupContext['expose']=exposed=>{if(__DEV__&& instance.exposed){warn(`expose() should be called only once per setup().`)}
    instance.exposed= exposed||{}// 暴露组件api给组件使用者}letattrs: Dataif(__DEV__){/* DEV 逻辑忽略,对上下文选项设置 getter */}else{return{getattrs(){return attrs||(attrs=createAttrsProxy(instance))},slots: instance.slots,emit: instance.emit,
      expose}}}
  • expose

用该 API 来清除地控制哪些内容会明确地公开暴露给组件使用者

import{ ref}from'vue'exportdefault{setup(_,{ expose}){const count=ref(0)functionincrement(){
      count.value++}// 仅仅暴露 increment 给父组件expose({
      increment})return{ increment, count}}}

像上方代码一样使用 expose 时,父组件获取的 ref 对象里只会有 increment 属性,而 count 属性将不会暴露出去

handleSetupResult

exportfunctionhandleSetupResult(instance: ComponentInternalInstance,setupResult: unknown,isSSR: boolean){if(isFunction(setupResult)){// setup 返回了一个行内渲染函数if(__NODE_JS__&&(instance.typeas ComponentOptions).__ssrInlineRender){// 当这个函数的名字是 ssrRender (通过 SFC 的行内模式编译)// 将函数作为服务端渲染函数
      instance.ssrRender= setupResult}else{// 否则将函数作为渲染函数
      instance.render= setupResultas InternalRenderFunction}}elseif(isObject(setupResult)){// 将返回对象转换为响应式对象,并设置为实例的 setupState 属性
    instance.setupState=proxyRefs(setupResult)}finishComponentSetup(instance, isSSR)}

finishComponentSetup

完成组件设置

exportfunctionfinishComponentSetup(instance: ComponentInternalInstance,isSSR: boolean,
  skipOptions?: boolean){const Component= instance.typeas ComponentOptions// 模板 / 渲染函数的规范行为// 1、渲染函数可能已经存在,通过 setup 返回// 2、除此之外尝试使用 `Component.render` 当做渲染函数// 3、如果这个函数没有渲染函数,设置 `instance.render` 为空函数,以便它能从 mixins/extend 中获得渲染函数if(__NODE_JS__&& isSSR){
    instance.render=(instance.render||
      Component.render||NOOP)as InternalRenderFunction}elseif(!instance.render){// 可以在 setup() 中设置if(compile&&!Component.render){const template= Component.templateif(template){const{ isCustomElement, compilerOptions}= instance.appContext.configconst{
          delimiters,compilerOptions: componentCompilerOptions}= ComponentconstfinalCompilerOptions: CompilerOptions=extend(extend({
              isCustomElement,
              delimiters},
            compilerOptions),
          componentCompilerOptions)
        Component.render=compile(template, finalCompilerOptions)}}

    instance.render=(Component.render||NOOP)as InternalRenderFunction// 对于使用 `with` 块的运行时编译的渲染函数,这个渲染代理需要不一样的 `has` handler 陷阱,它有更好的// 性能表现并且只允许白名单内的 globals 属性通过。if(instance.render._rc){
      instance.withProxy=newProxy(
        instance.ctx,
        RuntimeCompiledPublicInstanceProxyHandlers)}}}

在vue2中也有代理模式:

props求值后的数据存储在this._props中
data定义的数据存储在this._data中
在vue3中,为了维护方便,把组件中不通用状态的数据存储到不同的属性中,比如:存储到setupState、ctx、data、props中。在执行组件渲染函数的时候,直接访问渲染上下文instance.ctx中的属性,做一层proxy对渲染上下文instance.ctx属性的访问和修改,代理到setupState、ctx、data、props中数据的访问和修改。

  • 作者:qq_42179237
  • 原文链接:https://blog.csdn.net/qq_42179237/article/details/124462221
    更新时间:2022-03-18 13:10:01