Skip to content
索引

composition api

vue3推出了composition API(组合API),setup()是组合API的入口函数,可以直接在里面定义变量和方法(数据和业务逻辑),通过对象的形式返回暴露出去

现在更推荐使用<script setup> 语法糖

新的option,所有组合api函数都在此使用,只在初始化时执行一次

把data, method, computed, watch以及生命周期都放到setup函数中来使用

深入响应式系统

https://cn.vuejs.org/guide/extras/reactivity-in-depth.html#what-is-reactivity

Vue 中的响应性是如何工作的

原生 JavaScript 没有提供任何机制能做到局部变量的读写,如下示例:

js
let A0 = 1
A0 = 2
let A0 = 1
A0 = 2

但是 JavaScript 可以追踪对象属性的读写

在 JavaScript 中有两种劫持 property 访问的方式:getter / settersProxies。Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref

reactive() 的局限性:

  • 当你将一个响应性对象的属性解构为一个局部变量时,响应性就会“断开连接”,因为对局部变量的访问不再触发 get / set 代理捕获。
  • reactive() 返回的代理尽管行为上表现得像原始对象,但我们通过使用 === 运算符还是能够比较出它们的不同

reactive

reactive() 函数用于创建一个响应式对象或数组

ts
import { reactive } from 'vue'

const state = reactive({ count: 0 })
import { reactive } from 'vue'

const state = reactive({ count: 0 })

响应式对象其实是 JavaScript Proxy,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作

返回一个对象的响应式代理,reactive不支持基础类型的使用,只支持对象

响应式转换是“深层”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象

js
const a = { count: 0 }
const obj = reactive(a)
console.log(obj == a) // false
const a = { count: 0 }
const obj = reactive(a)
console.log(obj == a) // false

使用reactive包裹一个对象,修改原对象还是修改reactive返回的对象都会同步影响

js
const a = { count: 0 }
const obj = reactive(a)
a.count = 1
console.log(a) // {count:1}
console.log(obj) // Proxy{count:1}

const a = { count: 0 }
const obj = reactive(a)
obj.count = 2
console.log(a) // {count:2}
console.log(obj) // Proxy{count:2}
const a = { count: 0 }
const obj = reactive(a)
a.count = 1
console.log(a) // {count:1}
console.log(obj) // Proxy{count:1}

const a = { count: 0 }
const obj = reactive(a)
obj.count = 2
console.log(a) // {count:2}
console.log(obj) // Proxy{count:2}

ref

定义单个响应变量

定义一个包含响应式数据的引用对象,修改通过变量名.value的方式修改,但展现时直接使用变量名即可

ref底层其实还是reactive,vue会自动把ref中传入的值转换成reactive

csharp
const name = ref('这是一个响应式变量')
// 通过变量名.value获取值
console.log('拿到的值:', name.value)
const name = ref('这是一个响应式变量')
// 通过变量名.value获取值
console.log('拿到的值:', name.value)

使用在template中无需.value

vue
<div>{{num}}</div>
<div>{{num}}</div>

示例:

js
let num = ref(0)
function changeref () {
    num.value++
}
let num = ref(0)
function changeref () {
    num.value++
}

ref为什么要用.value取值?为什么返回一个对象?

https://blog.csdn.net/weixin_46182770/article/details/124506669

因为ref本质上是通过get、set实现的,通过get、set函数对value进行获取以及修改

获取单个dom

ref还可以用于获取dom

用法:在template中通过ref绑定一个值,在setup中通过ref定义一个同名的变量即可获取到相应dom

记得也是需要通过ref变量.value的方式获取dom,且应该在onMounted生命周期中调用,不然dom还未渲染,获取到的会是undefined

vue
<template>
  <div ref="hello">world</div>
</template>
<script lang="ts" setup>
const hello = ref('hello')
</script>
<template>
  <div ref="hello">world</div>
</template>
<script lang="ts" setup>
const hello = ref('hello')
</script>

获取多个dom

vue

<template>
  <div v-for="(item, index) in list" :key="index" :ref="hello">
	  {{item}}
</div>
</template>
<script lang="ts" setup>
const world = []
const hello = (el) => {
		world.value.push(el)
	}
</script>

<template>
  <div v-for="(item, index) in list" :key="index" :ref="hello">
	  {{item}}
</div>
</template>
<script lang="ts" setup>
const world = []
const hello = (el) => {
		world.value.push(el)
	}
</script>

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样

ts
const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

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

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

toRef

基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然

ts
const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3
const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3

toRefs

作用:用于批量设置多个数据为响应式数据。(toRef一次仅能设置一个数据),toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行

js
const  props = toRefs(props) // 遍历props
const { modelValue } = toRefs(props) // 搭配解构赋值
const  props = toRefs(props) // 遍历props
const { modelValue } = toRefs(props) // 搭配解构赋值

适合搭配torefs使用,如果在return里直接放上reactive创建的数据,每次使用的时候都要reactive变量名.对象属性的方式获取,太麻烦,用toRefs包裹后使用拓展运算符可直接使用对象属性名

vue
<template>
    //不使用toRefs需要data.name的方式调用
    {{name}}
    <el-button @click="name=name.slice(1)"></el-button>
</template>

<script>
import { reactive, toRefs } from 'vue'
export default {
    setup () {
        const data = reactive({
            name: '帅的没人要',
            sex: '性别分不出',
            arr: []
        })
        //如果return data需要通过data.name这种方式使用数据,return ...data数据是非响应式的,只有通过使用toRefs(data)包裹既简化又是响应式
        const refData = toRefs(data)
        return {
            ...refData,
        }
    }
}
</script>

<style>
</style>
<template>
    //不使用toRefs需要data.name的方式调用
    {{name}}
    <el-button @click="name=name.slice(1)"></el-button>
</template>

<script>
import { reactive, toRefs } from 'vue'
export default {
    setup () {
        const data = reactive({
            name: '帅的没人要',
            sex: '性别分不出',
            arr: []
        })
        //如果return data需要通过data.name这种方式使用数据,return ...data数据是非响应式的,只有通过使用toRefs(data)包裹既简化又是响应式
        const refData = toRefs(data)
        return {
            ...refData,
        }
    }
}
</script>

<style>
</style>

defineProps

子组件通过props接收父组件传递的值

typescript
interface Form {
    age: number
    name:string
}

const props = defineProps<{
    msg: string
    form?: Form
}>()
interface Form {
    age: number
    name:string
}

const props = defineProps<{
    msg: string
    form?: Form
}>()

withDefaults

设置props的默认值

typescript
import { defineProps, withDefaults } from "vue"
const props = withDefaults(
  defineProps<{
    modelValue: boolean
    text: string
  }>(),
  {
    modelValue: false,
    text: "这是一行默认文本"
  }
)
import { defineProps, withDefaults } from "vue"
const props = withDefaults(
  defineProps<{
    modelValue: boolean
    text: string
  }>(),
  {
    modelValue: false,
    text: "这是一行默认文本"
  }
)

emits

父组件给子组件传递方法,子组件触发父组件传递的方法

typescript
import { defineEmits } from 'vue'
const emits = defineEmits<{
  (e: "update:modelValue", b: boolean): void
  (e: "confirm"): void
}>()
function cancel() {
  emits("update:modelValue", false)
}
function confirm() {
  emits("update:modelValue", false)
  emits("confirm")
}
import { defineEmits } from 'vue'
const emits = defineEmits<{
  (e: "update:modelValue", b: boolean): void
  (e: "confirm"): void
}>()
function cancel() {
  emits("update:modelValue", false)
}
function confirm() {
  emits("update:modelValue", false)
  emits("confirm")
}

computed

当所依赖的内容发生变化时,里面的代码才会重新执行,回调函数

用法一,接收一个对象

typescript
  const addFive = computed(() => {
                   return count.value + 5
               })
  const addFive = computed(() => {
                   return count.value + 5
               })

用法二,接受一个对象,get取值时调用的方法,set赋值时调用的方法

js
let addFive = computed({
                    get: () => {
                       return count.value + 5
                    },
                    set: (param) => {
                        return count.value = param
                    }
                })
let addFive = computed({
                    get: () => {
                       return count.value + 5
                    },
                    set: (param) => {
                        return count.value = param
                    }
                })

nextTick

nextTick用于获取更新数据后的dom

应用场景:修改了控制dom的js变量,但vue框架还没来得及处理相应dom

举例:有一个dom元素使用了v-if,将v-if绑定的变量从false变为true,此时立刻获取这个v-if绑定的dom元素,可能会存在获取到null的情况,使用$nextTick可以保证正确获取到dom元素

js
import {  nextTick } from 'vue'
nextTick(()=>{
	// 这里获取更新后的dom
})
import {  nextTick } from 'vue'
nextTick(()=>{
	// 这里获取更新后的dom
})

provide/inject

实现多组件之间通信

provide 注入数据,

js
 let color = ref('green')
 provide("color", color)
 let color = ref('green')
 provide("color", color)

inject提取数据

js
const color = inject('color')
const color = inject('color')

watch

第一个参数是一个回调函数,返回监听目标,第二个是监听的处理操作

js
  const watchA = watch(() => obj.a, () => {
            console.log('123')
        })
  const watchA = watch(() => obj.a, () => {
            console.log('123')
        })

监听对象的多个属性,使用数组

js
  const watchA = watch(() => [obj.a,obj.b], () => {
            console.log('123')
        })
  const watchA = watch(() => [obj.a,obj.b], () => {
            console.log('123')
        })

watchEffect

watchEffect在一个回调函数中进行处理

js
  const watchA = watchEffect(() => {
            console.log(obj.a);
        })
  const watchA = watchEffect(() => {
            console.log(obj.a);
        })

getCurrentInstance

获取vue实例

js
const { ctx } = getCurrentInstance()
  const hello = () => {
            ctx.$router.push({
                path: "/hello"
            })
        }
const { ctx } = getCurrentInstance()
  const hello = () => {
            ctx.$router.push({
                path: "/hello"
            })
        }

onMounted

js
 onMounted(() => {
            console.log(ctx.$route.params)
        })
 onMounted(() => {
            console.log(ctx.$route.params)
        })

proxy/ctx

ctx相当于this,只有开发时可以

proxy服务器端也可以,所以非要获取vue实例时使用proxy

readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

js
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!

vue-router

useRouter,useRoute

typescript
<script lang="ts" setup>
import { useRouter,useRoute } from "vue-router"
const router = useRouter()
const route = useRoute()
function goSearch() {
  router.push("/home" + "1111" + "?k=" + "222")
  // 相当于下方
  // router.push({ name: "home", params: { code: 111 }, query: { k: 222 } })
}
</script>
<script lang="ts" setup>
import { useRouter,useRoute } from "vue-router"
const router = useRouter()
const route = useRoute()
function goSearch() {
  router.push("/home" + "1111" + "?k=" + "222")
  // 相当于下方
  // router.push({ name: "home", params: { code: 111 }, query: { k: 222 } })
}
</script>

router-view使用transition

vue
<router-view v-slot="{ Component }">
    <transition name="fade-transform">
      <component :is="Component" />
    </transition>
</router-view> 
<router-view v-slot="{ Component }">
    <transition name="fade-transform">
      <component :is="Component" />
    </transition>
</router-view> 

生命周期

  • onMounted
  • onUpdated
  • onUnmounted
  • onBeforeMount
  • onBeforeUpdate
  • onBeforeUnmount
  • onErrorCaptured
  • onRenderTracked
  • onRenderTriggered
  • onActivated
  • onDeactivated
  • onServerPrefetch

css module

css
<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>
<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>
vue
<template>
  <div>
    <p :class="{ [$style.red]: isRed }">
      Am I red?
    </p>
    <p :class="[$style.red, $style.bold]">
      Red and bold
    </p>
  </div>
</template>
<template>
  <div>
    <p :class="{ [$style.red]: isRed }">
      Am I red?
    </p>
    <p :class="[$style.red, $style.bold]">
      Red and bold
    </p>
  </div>
</template>
js
<script>
export default {
  created () {
    console.log(this.$style.red)
    // -> "red_1VyoJ-uZ"
    // 一个基于文件名和类名生成的标识符
  }
}
</script>
<script>
export default {
  created () {
    console.log(this.$style.red)
    // -> "red_1VyoJ-uZ"
    // 一个基于文件名和类名生成的标识符
  }
}
</script>

.vue 中可以定义不止一个 <style>,为了避免被覆盖,可以通过设置 module 属性来为它们定义注入后计算属性的名称

css
<style module="a">
  /* 注入标识符 a */
</style>

<style module="b">
  /* 注入标识符 b */
</style>
<style module="a">
  /* 注入标识符 a */
</style>

<style module="b">
  /* 注入标识符 b */
</style>

v-bind(var)

支持v-bind绑定一个css变量

vue
<template>
  <div class="hello">world</div>
</template>
<script lang="ts" setup>
const pink = "pink"
</script>
<style lang="scss">
.world {
  color: v-bind(pink);
}
</style>
<template>
  <div class="hello">world</div>
</template>
<script lang="ts" setup>
const pink = "pink"
</script>
<style lang="scss">
.world {
  color: v-bind(pink);
}
</style>

最终会被编译为var变量,并在内联样式中使用

css
<template>
  <div class="hello">world</div>
</template>
<script lang="ts" setup>
const pink = "pink"
</script>
<style lang="scss">
.world {
  color: v-bind(pink);
}
</style>
<template>
  <div class="hello">world</div>
</template>
<script lang="ts" setup>
const pink = "pink"
</script>
<style lang="scss">
.world {
  color: v-bind(pink);
}
</style>

defineExpose

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

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

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

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

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

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

父组件调用子组件方法

子组件通过defineExpose暴露给父组件调用的方法

ts
const resize = () => {
    ehcart.resize()
}
defineExpose({
  resize
})
const resize = () => {
    ehcart.resize()
}
defineExpose({
  resize
})

父组件通过ref获取子组件实例

template

vue
  <echart ref="getChart" />
  <echart ref="getChart" />

script

ts
const getChart = ref<any>(null)
window.onresize = () => {
    getChart.value.resize()
  }
const getChart = ref<any>(null)
window.onresize = () => {
    getChart.value.resize()
  }

全局方法的TS支持

ts
// vue全局方法设置类型
type state = {
  loading: boolean
  requests: any[]
  isShowHeader: boolean
  rightTabIndex: number
  date: string
}
type $store = {
  state: state
}
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    /** 动态请求图片路径 */
    requireImg: (path: string) => string
    $store: $store
  }
}
const app = createApp(App)
app.config.globalProperties.requireImg = requireImg
// vue全局方法设置类型
type state = {
  loading: boolean
  requests: any[]
  isShowHeader: boolean
  rightTabIndex: number
  date: string
}
type $store = {
  state: state
}
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    /** 动态请求图片路径 */
    requireImg: (path: string) => string
    $store: $store
  }
}
const app = createApp(App)
app.config.globalProperties.requireImg = requireImg

异步组件

需要使用defineAsyncComponent函数

js
import { defineAsyncComponent } from "vue"
import { defineAsyncComponent } from "vue"

注意点

  • 相对路径不能是当前目录,也就是不能是./,所以使用../views/到父目录再到自己目录,需要多此一举一下
  • 加上/* @vite-ignore */阻止rollup编译动态组件报错,这样才能正常使用,注释影响打包,秀得很
js
computed: {
    dynamicComponent() {
        return defineAsyncComponent(() => import(/* @vite-ignore */ `../views/${this.vueUrl}.vue`))
      }
    }
  }
computed: {
    dynamicComponent() {
        return defineAsyncComponent(() => import(/* @vite-ignore */ `../views/${this.vueUrl}.vue`))
      }
    }
  }

注意点

setupbeforecreate之前执行,组件没有创建,所以没有this,打印thisundefined

TypeScript 与组合式 API

https://cn.vuejs.org/guide/typescript/composition-api.html

Released under the MIT License.