【Vue3】一文看懂vue3单文件组件的语法糖<script setup>

前言

提示: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的设置和使用

ref接受一个 inner value,然后返回一个响应式且可变的ref对象,这个ref对象需要使用.value来访问其inner value。

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

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

reactive 接受一个对象参数,然后返回一个具有响应式的原始值代理对象。

<template>
  <div>
    <button @click="increase">被点击了{{obj.count}}次</button>
  </div>
</template>
<script setup>
import { reactive } from 'vue';
const obj = reactive({ count: 0 })
const increase = () => {
  obj.count++
}
</script>

refreactive相结合,当一个ref被作为reactive 对象的属性时被存取或者变更时,它会自动的解包inner value,可以像正常的属性一样去使用它。

const count = ref(0)
const state = reactive({
  count
})
// 访问count
console.log(state.count) // 0
// 变更count
state.count = 1
console.log(count.value) // 1

注意,如果一个新的 ref 对象被分配给一个带有 ref 的属性,它将会替换旧的 ref

....
const otherCount = ref(2)
//旧的count,被otherCount替代,
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
四、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();
console.log(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>

另外,根据需要你也可以同时提供 get 和 set 函数来创建一个可写的 ref 对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})
plusOne.value = 1
console.log(count.value) // 0
七、路由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>

也可以关闭watch,watch 被创建时,会返回一个关闭函数,可以用该函数关闭一个 watch。

const count = ref(0)
const stop = watch(() => console.log(count.value))
stop() //关闭该观察者
setTimeout(() => {
  // 更改count的值,但是不会触发观察者。
  count.value++
}, 1000)

还有一种watch懒调用,观察者第一次被创建都会去触发一遍回调函数,假如需要当值被改变时才触发观察者,可以加入lazy配置。

const count = ref(0)
// 第一次执行不会打印
watch(count, value => console.log(value), { lazy: true })
setTimeout(() => {
  count.value++
  // -> logs 1
}, 1000)
十三、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> 使用 slotsattrs 的情况应该是很罕见的,因为可以在模板中通过 $slots 和 $attrs 来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlotsuseAttrs 两个辅助函数:

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

const slots = useSlots()
const attrs = useAttrs()
</script>
十七、isRef 和 toRefs的使用

isRef用来检测一个值是否是一个ref对象。当需要解构一个或许为ref对象时,尤其有用。

// 如果foo是ref对象,就解构,否则直接返回本身。
const unwrapped = isRef(foo) ? foo.value : foo

toRefs把一个reactive对象转换为一个普通对象,并且该普通对象上的每个属性指向的是 ref对象,这些 ref 对象由原对象相应的属性值创建。

const state = reactive({
  foo: 1,
  bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 为一个普通对象:
{
  foo: Ref<1>,
  bar: Ref<2>
}
*/
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
十八、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之前执行一次,thisundefined
  • 由于模块执行语义的差异,<script setup> 中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因而 <script setup> 不能和 src attribute 一起使用。
  • 10
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值