一、响应式基础
1.ref():声明基本类型,引用类型,函数需接收参数,并将其包裹在一个带有 .value
属性的对象中,在模板中使用 ref 时,我们不需要附加 .value,
当在模板中使用时,ref 会自动解包。
<template>
<button @click="count++">
{{ count }}
</button>
</template>
<script setUp>
import { ref } from 'vue'
const count = ref(0)
console.log(count.value)
</script>
2.reactive():声明对象、数组等复杂类型。
<template>
<button @click="state.count++">
{{ state.count }}
</button>
</tempalte>
<script setUp>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
console.log(state)
</script>
3. toRefs与toRef深度剖析
3.1 toRefs()函数用于将一个响应式对象的所有属性转换为单独的响应式引用。这个函数通常与解构赋值一起使用,以便我们可以轻松地访问响应式对象的每个属性。
<script setUp>
import { reactive, toRefs } from 'vue'
const state = reactive({
name: 'Commas',
age: 18
})
//现在解构这个对象,并保持每个属性的响应式,这时toRefs 就派上用场了
//这样,name 和 age 就成为了单独的响应式引用,我们可以直接使用它们,而不必担心失去响应式。
const { name, age } = toRefs(state);
</script>
3.2 toRef()函数用于为源响应式对象的某个属性创建一个响应式引用。与 toRefs
不同的是,toRef
只能用于单个属性。
<script setUp>
//假设我们有一个响应式对象 state,包含name和age两个属性,现在我们只想为name属性创建一个响应式引用
//这样name就成为了state.name的响应式引用,我们可以直接修改name.value,这个修改会反映state.nam上。
const name = toRef(state, 'name');
</script>
3.3 联系与区别
(1)toRefs
和 toRef
都用于创建响应式引用。
(2)toRefs
用于将整个响应式对象的所有属性转换为响应式引用,而 toRef
只用于单个属性。
(3)toRefs
通常与解构赋值一起使用,方便同时访问多个属性,而 toRef
用于为单个属性创建响应式引用。
二、计算属性 computed()
getter与setter的计算属性
get()获取数据
set()改变数据
<template>
<div class="computed">
姓: <input type="text" v-model="firstName" /> <br />
名: <input type="text" v-model="lastName" /><br />
姓名: <span>{{ fullName }}</span>
<br />
<button @click="changeName">更改姓名</button>
</template>
<script setUp>
import { computed, reactive } from "vue";
let firstName = ref("han");
let lastName = ref("召华");
//默认是 getter 方法
const fullName = computed(() => {
return firstName.value + "_" + lastName.value;
});
//有getter与setter的计算属性
let fullName = computed({
get() {
return (
firstName.value.slice(0, 1).toUpperCase() +
firstName.value.slice(1) +
"-" +
lastName.value
);
},
set(val) {
let [str1, str2] = val.split("-");
firstName.value = str1;
lastName.value = str2;
},
});
function changeName() {
fullName.value = "huang-aaa";
}
</script>
三、watch与watchEffect
3.1watch 监听函数
情况一:ref(监听不需要加.value)
情况二:reactive(默认深度监听且deep不可变)
情况三:getter函数监听 响应式对象的某个属性且是基本数据类型
情况四:监视多个数据使用数组情况 [() => obj.value.a.b,() => obj.value.a]
<template>
<h2>one:{{ one }} | two: {{ two.value }}</h2>
<button @click="one++">改变 one 数据</button>
<button @click="two.value++">改变 two 数据</button>
</template>
<script setup>
import { watch, ref ,reactive} from 'vue'
// 变量1
const one = ref(0)
// 变量2
const two = reactive({
value: 10
})
// 监听多个变量
// 第一个参数变为数组形式,每个下标对应一个要监听的变量
// 第二个参数的函数传参改为每项数组形式,每个数组对应新旧值的参数
watch([one, () => two.value], ([oneNew, twoNew], [oneOld, twoOld]) => {
console.log(`one: ${oneNew}(新) ——— ${oneOld}(旧)`);
console.log(`two: ${twoNew}(新) ——— ${twoOld}(旧)`);
});
</script>
3.2 watchEffect 监听函数
1.立即运行一个函数,同时响应式追踪其依赖,并在依赖更改时重新执行该函数
2.watch与watchEffect区别
2.1都能监听响应式数据变化,只不过见监听方式不同
2.2watch:要明确指出监视数据
2.3watchEffect:不用指出监视数据(函数使用哪些,就监视哪些属性)
<script setUp>
let obj = ref({
a: {
b: {
watchName: "韩召华",
watchAge: 28,
},
},
});
watchEffect(() => {
if (obj.value.a.b.watchAge > 23) {
console.log("watchEffect--------", "监听到了");
}
});
</script>
四、ref获取DOM的使用
1.用于修改HTML的DOM
2.作用在组件上时,获取的是组件实例对象,子组件需通过defineExpose暴露数据,父组件才能使用
const presonDom = ref();
console.log("DOM实例", presonDom);
五、props使用
子组件接收
1.defineProps 接收list + 限制类型
defineProps(['a','b'])接收
defineProps带默认值写法
const props = defineProps({
name: String,
age:{
type: Number,
default: 23
}
})
ts写法defineProp<{list?:Persons}>()
2.withDefaults 接收list + 限制类型 + 限制必要性 + 指定默认值
withDefaults(defineProps<{list?:Persons}>(),{list:()=>[{id:0,name:"张三",age:18}]})
六、生命周期
1.创建 setUP
2.挂载前 onBeforeMount(()=>{})
3.挂载后 onMounted(()=>{ console.log(子组件挂载优先于父组件) })
4.更新前 onBeforeUpdate(()=>{})
5.更新后 onUpdate(()=>{})
6.卸载前 onBeforeUnmount(()=>{})
7.卸载后 onUnmounted(()=>{})
七、hooks
1.use命名
2.return 可用对象,数组,方法暴露数据且包含属性和方法
3.使用如下
八、routes路由
1.RouterLink 跳转路由
>to的使用对象:name,path
>active-class 激活样式
2.RouterView 展示路由
3.params query props 路由传参
4.push推入,replace 替换(无路由记录,直接作用于RouterLink上)
5.编程式导航 useRouter
6.redirect 重定向
const router = useRouter();
router.push();
router.replace();
九、集中式状态数据管理 redux | vuex | pinia
1.npm i pinia
2.创建pinia(创建store文件-根据页面组件命名-用use命名store文件-defineStore定义store - 在使用组件引入该store文件 - 通过talkStore.value或者talkStore.$store.value)
3.pinia 修改的三种方式
3.1第一种方式修改 countstore.sum 直接修改值
3.2第二种方式修改 批量
countstore.$patch ({
sum: 100,
xxx: 123
})
3.3第三种方式 使用actions
//直接调用actions里的方法
countStore.increment('参数集');
4.storeToRefs响应式数据 只关注store中数据,不会对方法进行ref包裹
5.getters 计算属性
6.$subscribe 订阅 监听store中的数据变化
countStore.$subscribe((mutate,satat)=>{
console.log('监听',mutate,satat)
}
十、组件通信
1.props
父传子":data"
子传父 子defineProps(['sentData']) 通过事件传参
在父组件:sentData="getData"
getData( value ) {
console.log('接收子传数据',value);
}
2.自定义 defineEmits
子组件 const emit = defineEmits(['eimtData'])
emit('emitData',{name:'张三',age:18})
父组件 @emitData="getData"
getData( value ) {
console.log('接收子传数据',value);
}
3._mitt工具-总线
npm引入 mitt
const emitter =mitt()
emitter.emit() // 触发钩子
emitter.on() // 监听钩子
emitter.off() // 解绑钩子(一般在onUnmounted()里使用)
emitter.all() // 清空事件
4.v-model方式
5.$attrs 祖孙组件传值
如下图所示,A 和 B、B 和 C/D 都是父子关系,C 和 D 是兄弟关系,A 和 C/D 是隔代关系。
$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (包含class 和 style)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (包含class 和 style ),并且可以通过 v-bind=“$attrs” 传入内部组件。在Vue3.0版本中,$listeners / inheritAttrs已被移除掉,现在事件监听器是 $attrs 的一部分。
案例组件(父子孙)
同样,我们创建3个组件: 父组件:parent.vue,子组件:child.vue,孙组件:grandchild.vue
- 父组件:parent.vue
<template>
<div class="parent-root">
<child
class="child"
:name="name"
:age="age"
@grandchildByValue="grandchildByValue"
>
</child>
</div>
</template>
<script>
import { ref } from 'vue'
import child from '@/views/test/child.vue'
export default {
name: 'parent',
components: { child },
props: {},
setup() {
const name = ref('小明'),
age = ref(18),
grandchildByValue = value => {
console.log('孙组件传过来的值:', value)
}
return {
name,
age,
grandchildByValue
}
}
}
</script>
<style scoped lang="scss"></style>
- 子组件:child.vue
<template>
<div class="child-root">
<grandchild v-bind="$attrs"></grandchild>
</div>
</template>
<script>
import { ref } from 'vue'
import grandchild from '@/views/test/grandchild.vue'
export default {
name: 'child',
components: { grandchild },
props: {},
setup(context) {
console.log('attrs:', context.attrs)
}
}
</script>
<style scoped lang="scss"></style>
- 孙组件:grandchild.vue
<template>
<div class="grandchild-root">
<p>name:{{ name }}</p>
<p>age:{{ age }}</p>
<el-button type="primary" @click="fn">孙子传值爷爷</el-button>
</div>
</template>
<script>
export default {
name: 'grandchild',
components: {},
props: {
name: {
type: String,
default: ''
},
age: {
type: Number,
default: null
}
},
setup(context) {
const fn = () => {
context.emit('grandchildByValue', '爷爷,您好')
}
return {
fn
}
}
}
</script>
<style scoped lang="scss"></style>
6.$refs跟$parent
ref 需要结合defineExpose暴露数据使用
1、$refs
用法
父组件(模板代码:参数$refs
)
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
按钮对应的函数代码
这里,就可以同时获取到c1和c2
两个实例
function getAllChild(refs:{[key:string]:any}){
console.log(refs)
for (let key in refs){
refs[key].book += 3
}
}
两个子组件
暴露变量,这样父组件就可以操作该变量
// 把数据交给外部
defineExpose({book})
2、$parent
用法
子组件(模板代码:参数$parent
)
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
按钮对应的函数代码
function minusHouse(parent:any){
parent.house -= 1
}
父组件(暴露变量)
// 向外部提供数据
defineExpose({house})
7.provide_inject依赖注入 祖孙之间直接通信
我们可以使用provide和inject使顶层组件向底层组件提供数据和方法。
Provide
语法格式: provide('key',顶层组件中的数据或方法)
顶层组件示例代码如下:
<template>
顶层组件
<Middle />
</template>
<script setup>
import { provide, ref } from 'vue'
import Middle from './components/middle.vue'
const count = ref('这是顶层组件提供的数据')
provide('count-key', count) // 向底层组件提供数据
const sayHello = () => {
console.log('你好,我是顶层组件提供的方法')
}
provide('sayHello', sayHello) // 向底层组件提供方法
</script>
中间组件示例代码如下:
<template>
中间组件
<Bottom />
</template>
<script setup>
import Bottom from './bottom.vue'
</script>
inject
底层组件通过inject函数获取数据并通过变量接收,其语法格式如下:
const 变量名=inject('key')
底层组件示例代码如下:
<template>
底层组件
<button @click="sayHello">sayHello</button>
<div>来自顶层组件的数据:{{ countData }}</div>
</template>
<script setup>
import { inject } from 'vue'
const sayHello = inject('sayHello')
const countData = inject('count-key')
</script>
十一、shallowReactive和shallowRef
shallowReactive
(1)作用:与reactive
作用类似,但只处理对象最外层属性的响应式(浅响应式)
(2)使用场景:如果有一个对象数据,结构比较深(内嵌多层对象),但只需要最外层的属性进行响应式,则使用shallowReactive
shallowRef
(1)作用:与ref
作用类型,但只处理基本数据类型的响应式,不处理对象类的响应式
(2)使用场景:如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,则使用shallowRef
十二、readonly和shallowReadonly
(1)readonly:将包裹的对象变为只读,并且是深度只读
(2)shallowReadonly:浅层属性为只读,深层次属性可以修改
十三、toRaw和markRaw
toRaw将代理对象变成普通对象,数据发生变化,不会更新
markRaw标记的对象数据,从此以后再也不能成为代理对象了
两者区别:toRaw会将整个对象变成非响应式的,markRaw可以指定哪些属性值可以变化
<template>
<div>
<h2>toRaw与markRaw</h2>
<h3>state:{{state}}</h3>
<button @click="updateToRaw">测试toRaw</button>
<button @click="updateMarkRaw">测试markRaw</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent,reactive,ref,toRaw,markRaw } from 'vue'
interface UserInfo {
name:string,
age:number,
likes?:string[]
}
// 引入子集组件
import Child from './components/child.vue';
export default defineComponent({
components:{
Child
},
setup(){
const state = reactive<UserInfo>({
name:'小明',
age:12,
})
const updateToRaw = () => {
const user = toRaw(state)
user.name += '-----'
console.log('测试')
}
const updateMarkRaw = () => {
// state.likes = ['吃','喝']
// state.likes[0] += '-----'
const likes = ['吃','喝']
state.likes = markRaw(likes)
setInterval(() => {
console.log('定时器走起来')
if (state.likes) {
state.likes[0] += '----'
}
},1000)
console.log('测试')
}
return {
updateToRaw,
updateMarkRaw,
state
}
}
})
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>
十四、自定义customRef
(1)customRef 用于自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应,接受工厂函数
(2)两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象
使用:
import {customRef} from 'vue';
function useDebouncedRef(value) {
return customRef((track, trigger) => {
return {
get() {
track() 追踪当前数据
return value
},
set(newValue) {
value=newValue
trigger() 触发响应,即更新界面
},
}
})
}
通过customRef返回的ref对象,和正常ref对象一样,通过x.value修改或读取值
类型声明:
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}