单文件组件-新特性

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-09-24

单文件组件组合式API语法糖 实验性

docs

基本示例

<template>
  <button @click="inc">{{ count }}</button>
</template>

<script setup>
  import { ref } from 'vue'

  export const count = ref(0)
  export const inc = () => count.value++
</script>

会被编译成

<script>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const inc = () => count.value++

    return {
      count,
      inc,
    }
  },
}
</script>

使用setup()参数

<script setup="props, { emit }">
import { watchEffect } from 'vue'

watchEffect(() => console.log(props.msg))
emit('foo')
</script>

会被编译成

<script>
import { watchEffect } from 'vue'

// setup is exported as a named export so it can be imported and tested
export function setup(props, { emit }) {
  watchEffect(() => console.log(props.msg))
  emit('foo')
  return {}
}

export default {
  setup,
}
</script>

导出组件

<script setup>
export { default as Foo } from './Foo.vue'
export { default as Bar } from './Bar.vue'
export const ok = Math.random()
</script>

<template>
  <Foo/>
  <Bar/>
  <component :is="ok ? Foo : Bar"/>
</template>

使用Vue2 options 选项

<script setup="props">
import { computed } from 'vue'

export default {
  props: {
    msg: String,
  },
  inheritAttrs: false,
}

export const computedMsg = computed(() => props.msg + '!!!')
</script>

会被编译成:

<script>
import { computed } from 'vue'

const __default__ = {
  props: {
    msg: String,
  },
  inheritAttrs: false,
}

export function setup(props) {
  const computedMsg = computed(() => props.msg + '!!!')

  return {
    computedMsg,
  }
}

__default__.setup = setup
export default __default__
</script>

使用 TypeScript

<script setup="props" lang="ts">
import { computed } from 'vue'

// declare props using TypeScript syntax
// this will be auto compiled into runtime equivalent!
declare const props: {
  msg: string
}

export const computedMsg = computed(() => props.msg + '!!!')
</script>

会被编译成:

<script lang="ts">
import { computed, defineComponent } from 'vue'

export default defineComponent({
  props: ({
    msg: String
  } as unknown) as undefined,
  setup(props: {
    msg: string
  }) {
    const computedMsg = computed(() => props.msg + '!!!')

    return {
      computedMsg,
    }
  }
})
</script>

会额外引入 ts 所需要的 defineComponent,并且会将针对 props 的声明,转换为选项中对应的 props

与普通script一并使用

<script>
performGlobalSideEffect()

// this can be imported as `import { named } from './*.vue'`
// 外部可以引入该.vue文件暴露的 named 属性
export const named = 1
</script>

<script setup>
import { ref } from 'vue'

export const count = ref(0)
</script>

会被编译成:

<script>
import { ref } from 'vue'

performGlobalSideEffect()

export const named = 1

export function setup() {
  const count = ref(0)
  return {
    count
  }
}

export default { setup }
</script>

编译API

单文件组件(SFC, Single File Components) 的编译相关API是在 @vue/compiler-sfc 这个包里面的,它暴露一个 compileScript 方法来处理 <script setup> 的编译。

import { parse, compileScript } from '@vue/compiler-sfc'

const descriptor = parse(`...`)

if (descriptor.script || descriptor.scriptSetup) {
  const result = compileScript(descriptor) // returns SFCScriptBlock
  console.log(result.code)
  console.log(result.bindings)
}

关于 bindings,是指单文件编译时会输出的元数据。

比如:

<script setup="props">
export const foo = 1

export default {
  props: ['bar']
}
</script>

bindings 会是:

{
  foo: 'setup',
  bar: 'props'
}

这个对象可以被传递到模板编译器:

import { compile } from '@vue/compiler-dom'

compile(template, {
  bindingMetadata: bindings
})

使用限制

由于模块执行语义的差异,<script setup> 是需要依赖于单文件组件的上下文的,当引入外部的 .js.ts 文件,会给开发人员和IDE工具造成混淆,因此,<script setup> 是不能够使用src属性来引入外部文件的。

单文件组件状态驱动的CSS变量 实验性

docs

基本示例

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>

这个 <div> 会被编译成:

<div style="--color:red" class="text">hello</div>

<style>
.text {
  color: var(--color);
}
</style>

<!-- 这种方式是h5支持的,使用style属性声明原生css变量,在选中该元素的选择器的样式规则中可以直接使用 -->

<!-- 别的元素 无法使用该css变量  -->

使用scoped

当使用 <style scoped> 的时候,这个 div 会被编译成:

<div style="--6b53742-color:red" class="text">hello</div>

<style>
.text {
  color: var(--6b53742-color);
}
</style>

与原生css变量一起使用

在与原生css变量一起使用的时候没,需要加上 global: 前缀。

<style scoped vars="{ color }">
.text {
  color: var(--color);
  font-size: var(--global:fontSize);
}
</style>

会被编译成:

<div style="--6b53742-color:red" class="text">hello</div>

<style>
.text  {
  color: var(--6b53742-color);
  font-size: var(--fontSize);
}
</style>

单文件组件 <style scoped> 的变动

docs

单文件组件 <style scoped> 现在可以包含全局规则或只针对插槽内容的规则。

基本示例

<style scoped>
/* deep selectors */
::v-deep(.foo) {}
/* 简写 */
:deep(.foo) {}

/* targeting slot content */
::v-slotted(.foo) {}
/* 简写 */
:slotted(.foo) {}

/* one-off global rule */
::v-global(.foo) {}
/* 简写 */
:global(.foo) {}
</style>

在Vue2中,存在 >>>/deep/ 两种写法,由于一些原因,在Vue3中将不再使用这两种写法。

其中 >>> 的写法,在使用一些css预编译语言时会有影响,比如 sass,因为 >>> 毕竟不是一个 css 原生的连接符。

而后来改用了 /deep/,它的问题则是,这种写法原本就是一个 css 提案,甚至已经在 Chrome 上进行了实现,但后来这个提案被废弃了。这就可能会对一些开发人员造成混淆,他们可能会担心在Vue SFC中继续使用 /deep/ 这个被废弃的写法会使得他们的代码在浏览器中不再支持。虽然在Vue中这个写法只是编译时,在最终的 css 代码中不会出现。

但是为了避免这种混淆,Vue3 提供了 :deep() / :slotted() / :global() 来在 <style scoped> 中进行规则的书写。

需要注意的是,不再是作为连接符使用 ::v-deep .bar {} 而是使用参数的形式来使用 ::v-deep(.bar) {}

总结:

  • >>> and /deep/ support are deprecated.

  • ::v-deep usage as a combinator is deprecated.

编译结果如下:

<style>
  ::v-deep(.bar) {}
  /* 也可以省略v-前缀 */
  :deep(.bar) {}
  /* 会被编译成 */
  [v-data-xxxxxxx] .bar {}

  /* ----------------- */

  ::v-slotted(.foo) {}
  /* 会被编译成 */
  .foo[v-data-xxxxxxx-s] {}

  /* ----------------- */

  ::v-global(.foo) {}
  /* 会被编译成 */
  .foo {}
</style>