前言

提示:Vue3.2 版本开始才能使用语法糖!

<script setup>是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。在 Vue3.2 中只需要在 script 标签上加上setup属性,无需 return,template 便可直接使用。相比于普通的

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯TypeScript 声明props和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

一、基本语法

要使用这个语法,需要将setup attribute添加到 <script> 代码块上:

<script setup>
  console.log('hello script setup')
</script>

里面的代码会被编译成组件setup()函数的内容。这意味着与普通的<script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行。

二、设置name

新增加一个script标签,在这个标签中写入name属性,代码如下:

<template>
  <button>demo</button>
</template>

<script>
export default {
  name: "VButton",
};
</script>

<script setup>
</script>

<style scoped lang="less">
</style>

三、data的设置和使用

<template>
  <div class="home">
    <button>+1</button>
    {{ num }}
  </div>
</template>

<script setup>
  import { ref } from 'vue';
  const num = ref(0);
</script>

四、method使用

<template>
  <div class="home">
    <button @click="btnClick">+1</button>
    {{ num }}
  </div>
</template>

<script setup>
  import { ref } from 'vue';
  const num = ref(0);

  const btnClick = () => {
    num.value = num.value + 1
  }
</script>

五、store仓库的使用

<script setup>
import { useStore } from '@/store';
const store = useStore();
store.getters.userInfo
</script>

六、computed计算属性的使用

接受一个 getter 函数,并根据getter 的返回值返回一个不可变的响应式 ref 对象。

<script setup>
import { computed, reactive } from 'vue';
import { useStore } from '@/store';
const store = useStore();
const userInfo = computed(() => store.getters.userInfo);

let person = reactive({
  firstName:'小',
  lastName:'叮当'
})
const fullName = computed(() => {
  return person.firstName + '-' + person.lastName
})
</script>

七、路由userRoute和userRouter使用

<script setup>
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
route.query // 获取路由query
route.params // 获取路由params

router.push({ path: '/login', query: {} });
</script>

八、使用组件以及动态组件

<template>
  <!-- PascalCase,建议使用,有助于区分原生的自定义元素 -->
  <MyComponent />
  <!-- kebab-case -->
  <my-component />
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
  <FooBarChild />
 
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>

<script setup>
import MyComponent from './MyComponent.vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

// 有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入
import { FooBar as FooBarChild } from './components'

// 从单个文件中导入多个组件
import * as Form from './form-components'

</script>

九、使用指令

<template>
  <h1 v-my-directive>This is a Heading</h1>
  <h1 py-directive>This is a Heading</h1>
</template>

<script setup>
// 内部定义
const vMyDirective = {
  beforeMount: (el) => {
    // 在元素上做些操作
  }
}
// 外部导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范
import { myDirective as pyDirective } from  './MyDirective.js'
</script>

十、emit和defineEmits的使用

<script setup>
const emit = defineEmits(['change', 'delete'])
// setup code
emits('change', 'cancel');
emits('delete');
</script>

十一、props和defineProps的使用

<script setup>
const props = defineProps({
  foo: String,
});
console.log(props.foo);
</script>

十二、watch的使用

<script setup>
import { ref, watch, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
const num = ref(0);

// 监听一个数据源
watch(num, (num, prevNum) => {
  /* ... */
})

const fooRef = ref(1);
const barRef = ref(2);
// 监听多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
  console.log('fooRef或barRef变了')
})

// 监听路由参数
const route = useRoute();
watch(
  () => route.fullPath,
  () => {
    // code
  }
);
</script>

十三、watchEffect的使用

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

<script setup>
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)
</script>

十四、defineExpose的使用

使用<script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在<script setup> 中声明的绑定。

为了在<script setup> 组件中明确要暴露出去的属性,使用defineExpose 编译器宏:

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

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

当父组件通过模板 ref 的方式获取到当前组件的实例,获取到的实例会像这样{ a: number, b: number } (ref 会和在普通实例中一样被自动解包)

父组件代码如下:

<template>
  <button @click="btnClick">点击</button>
  <Content ref="content" />
</template>

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

// content组件ref
const content = ref('content')
// 点击设置
const btnClick = () => {
  console.log(content.value.b)
}

</script>

十五、provide 和 inject 的使用

不限层级

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

let name = ref('张三')
// 使用provide
provide('provideState', {
  name,
  changeName: () => {
    name.value = '李四'
  }
})
</script>

子组件或子孙组件

<script setup>
import { inject } from 'vue'
const changeState = inject('changeState')

changeState.changeName()
</script>

十六、useSlots 和 useAttrs的使用

<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

十七、await的使用

<script setup>
const handleLogin = async (phone: string, code: string) => {
  try {
    const { token } = await requestAuthLogin({
      phone,
      code,
    });
    console.log(token);
  } catch (e) {
    console.log(e);
  } finally {
  }
};
</script>

十八、style特性

单文件组件的<style> 标签可以通过 v-bind这一 CSS 函数将 CSS 的值关联到动态的组件状态上:

<script setup>
const theme = {
  color: 'red'
}
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>

实际的值会被编译成 hash 的 CSS 自定义 property,CSS 本身仍然是静态的。自定义 property 会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式更新。

十九、使用vue实例

<script setup>
import { getCurrentInstance } from 'vue';
// 获取当前组件实例
const instance = getCurrentInstance();
 
// 获取当前组件的上下文,下面两种方式都能获取到组件的上下文。
// 方式一,这种方式只能在开发环境下使用,生产环境下的ctx将访问不到
const { ctx }  = getCurrentInstance();

//  方式二,此方法在开发环境以及生产环境下都能放到组件上下文对象(推荐)
const { proxy }  = getCurrentInstance();
// ctx 中包含了组件中由ref和reactive创建的响应式数据对象,以及以下对象及方法;
proxy.$attrs
proxy.$data
proxy.$el
proxy.$emit
proxy.$forceUpdate
proxy.$nextTick
proxy.$options
proxy.$parent
proxy.$props
proxy.$refs
proxy.$root
proxy.$slots
proxy.$watch
</script>

二十、仅限 TypeScript 的功能

设置类型声明时的默认 props 值

<script setup>
interface IProps {
  foo: string
  bar?: number
}
const props = defineProps<IProps>()

const emit = defineEmits<{
  (event: 'change', id: number): void;
  (event: 'update', value: string): void;
  (event: 'close'): void;
}>()

</script>

仅限类型的 defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。为了解决这个问题,提供了withDefaults 编译器宏:

<script setup>
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

</script>

注意事项

  • setup执行的时机:在beforeCreate之前执行一次,this是undefined。
  • 由于模块执行语义的差异,<script setup>中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因而<script setup> 不能和 src attribute一起使用。