前一篇讲了 vue3的生命周期钩子的使用。
本节接着讲 vue3的数据通信。
provide/inject 依赖注入
App.vue
<script setup lang="ts">
import { ref, provide } from 'vue'
import List from './components/List.vue'
import User from './components/User.vue'
import Demo from './components/Demo.vue'
const count = ref<number>(1)
const msg = ref<string>('hello world')
interface DataObj {
name: string
readonly id?: number // 只读属性
age: number
gender: string
address?: string // 可选属性
[propName: string]: any // 任意的属性
sum?(a: number, b: number): number // 声明 可选方法 sum()返回值为 number
updateData(): any // 声明 updateData() 返回值类型为 any
}
const updateData = (): void => {
console.log('我是来自爷爷组件的方法')
}
const dataObj = ref<DataObj>({
name: 'zhangsan',
age: 20,
gender: 'male',
updateData,
})
const clickHandle = (): any => {
console.log(count.value)
count.value++
/**在js中需要通过 .value来访问变量,因为 count是 proxy代理的名称 */
}
// 通过 provide 向外暴露一个变量,然后在所有后代组件中都可以获取,实现跨组件通信
provide('msg', msg)
provide('dataObj', dataObj)
const getDataFromSon = (params: string): any => {
console.log('来自子组件的数据', params)
}
</script>
<template>
<!--在模板中直接使用变量-->
<h1>{{ count }}</h1>
<button @click="count++">{{ count }}</button>
<button @click="clickHandle">增加</button>
<List @getDataFromSon="getDataFromSon" :dataToSon="msg" />
<User />
<Demo />
</template>
<style scoped></style>
Info.vue 作为后代组件来消费根组件传递的数据
<script setup lang="ts">
import { ref, inject } from 'vue'
import Home from './Home.vue'
const txt = ref<string>('hello vue3')
const list = ref<string[]>()
// 通过 inject来消费顶层组件传递的数据
const message = inject<string>('msg')
const dataObj = inject<any>('dataObj')
</script>
<template>
<h1>{{ txt }} {{ message }}</h1>
<h2>{{ dataObj.name }}</h2>
<button @click="dataObj.updateData">调用爷爷组件的方法</button>
<Home :txt="txt" />
</template>
<style scoped></style>
父子组件通信
创建一个List.vue
<script setup lang="ts">
import { ref, onMounted, watch, computed, reactive } from 'vue'
import Info from './Info.vue'
// 申明一个接口
interface Book {
title: string
year?: number // 可选属性,有可能不存在
}
const book: Book = reactive({ title: 'Vue 3 指引' })
const txt = ref<string>('')
const list = ref<string[]>([])
const infoRef = ref<any>()
// 通过 defineEmits()方法来声明传递给父组件的方法,该方法接收一个数组,可以传递多个emit方法
// defineEmits()和 defineProps()都是在子组件中使用,声明的都是定义在子组件上的事件和属性
const emit = defineEmits(['getDataFromSon'])
const props = defineProps<{ dataToSon: string }>()
const saveText = (): any => {
if (txt) list.value.push(txt.value)
emit('getDataFromSon', txt.value)
console.log('来自父组件的数据:', props.dataToSon)
txt.value = ''
}
computed(() => {})
watch(
() => txt.value,
(txt, preText) => {
console.log(txt, preText)
}
)
onMounted(() => {
// 使用子组件暴露出来的属性和方法
console.log(infoRef.value.message)
console.log(infoRef.value.dataObj)
console.log('Dom 已经挂在完毕')
})
const callSonMethod = (): any => {
infoRef.value.callMe()
}
/**
* 在script标签中添加 setup 属性是 vue 官方推荐的默认写法,
* 这样在 script标签中定义的函数,变量,甚至是 imort 导入的,都可以在模板中直接使用
* 而setup()函数这种写法,需要将所有声明的变量,函数通过 renturn函数暴露处供模板使用
*/
</script>
<template>
<input
type="text"
v-model="txt"
@keyup.enter="saveText"
placeholder="请输入内容"
/>
<div class="list">
<ul>
<li v-for="item in list" :key="item">
{{ item }}
</li>
</ul>
</div>
<Info ref="infoRef"/>
</template>
<style scoped></style>
创建Info.vue, 父组件传递 tex 变量给子组件, 子组件通过触发getDataFromSon()向父组件传递数据
<script setup lang="ts">
import { ref, inject } from 'vue'
import Home from './Home.vue'
const txt = ref<string>('hello vue3.0')
const list = ref<[]>()
const message = inject<string>('msg')
const dataObj = inject<any>('dataObj')
const callMe = (): any => {
console.log('this method is use defineExpose to parent')
}
// 通过 defineExpose({}) 方法暴露子组件的一些属性和方法,供父组件使用
defineExpose({
message,
dataObj,
callMe,
})
</script>
<template>
<h1>{{ txt }} {{ message }}</h1>
<h2>{{ dataObj.name }}</h2>
<button @click="dataObj.updateData">调用爷爷组件的方法</button>
<Home :txt="txt" />
</template>
<style scoped></style>
setup() 这种写法
<script lang="ts">
import { ref, reactive, onMounted } from 'vue'
export default {
/**
* 组合式 API 我们可以讲所有的属性方法都写在 setup()方法中,然后 return 出去
* 不再像 选项式API 哪种,需要写 data, methods, created等等
*/
setup() {
// ref用来定义基础数据类型,在 js中访问的时候要通过.value来访问
const count = ref<number>(1)
const list = reactive<any>({
// reactive用来定义复杂数据类型,访问的不用.value
num: 1,
list: [],
})
const increment = () => {
count.value++
list.num += 1
}
/**
* 在 vue3中没有 beforeCreated 和 created钩子函数,
* setup 方法就相当于这 2 个钩子
*/
onMounted(() => {
console.log('Dom 已经过载完毕')
})
/**
* 通过 return 将属性和方法暴露出去,供模板访问
*/
return {
count,
list,
increment,
}
},
}
/**
* 注意:使用reactive()声明复杂数据类型,给对象赋值时,通过下面这种方式来声明(如果嫌.value麻烦的话)
* const obj = reactive({
* data: {
* name: '',id: '',}
* })
* 就是在对象外面给它包一个key值,赋值时:
* obj.data = {name: 'zhangsan', id: 100}
*
* 第二种:就是直接用 ref 来声明对象或者数据,赋值时:obj.value = {name: 'Alex', id: 001}
*/
</script>
<template>
<h2>这是 User 组件 {{ count }}</h2>
<h1>{{ list.num }}</h1>
<button @click="increment">click</button>
</template>
<style></style>
setup()
和 <script setup></script>
区别
- setup()函数中定义的变量或者函数,需要 return 暴露出去,模板才能调用
- 而当使用
<script setup>
的时候,任何在<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用 <script setup>
中的代码会在每次组件实例被创建的时候执行。
<script setup></script>
优势:
toRef()
和toRefs()
区别
const state = reactive({
foo: 1,
bar: 2
})
// 通过toRef()后 fooRef变量也是响应式的,改变foo的值,fooRef也改变
// 作用:将一个非响应式的变量转换为一个响应式变量
const fooRef = toRef(state, 'foo')
// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2
// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3
ref()
和shallowRef()
区别
ref()我们知道可以创建一个响应式变量
shallowRef(): ref() 的浅层作用形式。
那么,shallowRef()就是可以创建一个浅层次的响应式变量,
应用场景:如果你的数据不赋值,不变更或者修改,只是做展示用,那么可以用 shallowRef(),可以节省性能消耗
reactive()
和 shallowReactive()
reactive(): 返回一个对象的响应式代理,
响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性
shallowReactive():reactive() 的浅层作用形式。
和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了
若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++