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 没有提供任何机制能做到局部变量的读写,如下示例:
let A0 = 1
A0 = 2
let A0 = 1
A0 = 2
但是 JavaScript 可以追踪对象属性的读写
在 JavaScript 中有两种劫持 property 访问的方式:getter / setters 和 Proxies。Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref
reactive() 的局限性:
- 当你将一个响应性对象的属性解构为一个局部变量时,响应性就会“断开连接”,因为对局部变量的访问不再触发 get / set 代理捕获。
- 从
reactive()
返回的代理尽管行为上表现得像原始对象,但我们通过使用===
运算符还是能够比较出它们的不同
reactive
reactive()
函数用于创建一个响应式对象或数组
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,避免依赖原始对象
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返回的对象都会同步影响
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
const name = ref('这是一个响应式变量')
// 通过变量名.value获取值
console.log('拿到的值:', name.value)
const name = ref('这是一个响应式变量')
// 通过变量名.value获取值
console.log('拿到的值:', name.value)
使用在template中无需.value
<div>{{num}}</div>
<div>{{num}}</div>
示例:
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
<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
<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
被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样
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 的值,反之亦然
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执行
const props = toRefs(props) // 遍历props
const { modelValue } = toRefs(props) // 搭配解构赋值
const props = toRefs(props) // 遍历props
const { modelValue } = toRefs(props) // 搭配解构赋值
适合搭配torefs使用,如果在return里直接放上reactive创建的数据,每次使用的时候都要reactive变量名.对象属性的方式获取,太麻烦,用toRefs包裹后使用拓展运算符可直接使用对象属性名
<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接收父组件传递的值
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的默认值
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
父组件给子组件传递方法,子组件触发父组件传递的方法
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
当所依赖的内容发生变化时,里面的代码才会重新执行,回调函数
用法一,接收一个对象
const addFive = computed(() => {
return count.value + 5
})
const addFive = computed(() => {
return count.value + 5
})
用法二,接受一个对象,get取值时调用的方法,set赋值时调用的方法
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元素
import { nextTick } from 'vue'
nextTick(()=>{
// 这里获取更新后的dom
})
import { nextTick } from 'vue'
nextTick(()=>{
// 这里获取更新后的dom
})
provide/inject
实现多组件之间通信
provide 注入数据,
let color = ref('green')
provide("color", color)
let color = ref('green')
provide("color", color)
inject提取数据
const color = inject('color')
const color = inject('color')
watch
第一个参数是一个回调函数,返回监听目标,第二个是监听的处理操作
const watchA = watch(() => obj.a, () => {
console.log('123')
})
const watchA = watch(() => obj.a, () => {
console.log('123')
})
监听对象的多个属性,使用数组
const watchA = watch(() => [obj.a,obj.b], () => {
console.log('123')
})
const watchA = watch(() => [obj.a,obj.b], () => {
console.log('123')
})
watchEffect
watchEffect在一个回调函数中进行处理
const watchA = watchEffect(() => {
console.log(obj.a);
})
const watchA = watchEffect(() => {
console.log(obj.a);
})
getCurrentInstance
获取vue实例
const { ctx } = getCurrentInstance()
const hello = () => {
ctx.$router.push({
path: "/hello"
})
}
const { ctx } = getCurrentInstance()
const hello = () => {
ctx.$router.push({
path: "/hello"
})
}
onMounted
onMounted(() => {
console.log(ctx.$route.params)
})
onMounted(() => {
console.log(ctx.$route.params)
})
proxy/ctx
ctx相当于this,只有开发时可以
proxy服务器端也可以,所以非要获取vue实例时使用proxy
readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
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
<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
<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
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
<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>
<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
属性来为它们定义注入后计算属性的名称
<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变量
<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变量,并在内联样式中使用
<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
编译器宏:
<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暴露给父组件调用的方法
const resize = () => {
ehcart.resize()
}
defineExpose({
resize
})
const resize = () => {
ehcart.resize()
}
defineExpose({
resize
})
父组件通过ref获取子组件实例
template
<echart ref="getChart" />
<echart ref="getChart" />
script
const getChart = ref<any>(null)
window.onresize = () => {
getChart.value.resize()
}
const getChart = ref<any>(null)
window.onresize = () => {
getChart.value.resize()
}
全局方法的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
函数
import { defineAsyncComponent } from "vue"
import { defineAsyncComponent } from "vue"
注意点
- 相对路径不能是当前目录,也就是不能是
./
,所以使用../views/
到父目录再到自己目录,需要多此一举一下 - 加上
/* @vite-ignore */
阻止rollup编译动态组件报错,这样才能正常使用,注释影响打包,秀得很
computed: {
dynamicComponent() {
return defineAsyncComponent(() => import(/* @vite-ignore */ `../views/${this.vueUrl}.vue`))
}
}
}
computed: {
dynamicComponent() {
return defineAsyncComponent(() => import(/* @vite-ignore */ `../views/${this.vueUrl}.vue`))
}
}
}
注意点
setup
在beforecreate
之前执行,组件没有创建,所以没有this
,打印this
为undefined