一、响应式基础
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'// 变量1const one = ref(0)// 变量2const 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"><childclass="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=newValuetrigger() 触发响应,即更新界面},}})}通过customRef返回的ref对象,和正常ref对象一样,通过x.value修改或读取值类型声明:function customRef<T>(factory: CustomRefFactory<T>): Ref<T>type CustomRefFactory<T> = (track: () => void,trigger: () => void) => {get: () => Tset: (value: T) => void}