插槽
用于父组件给子组件内 传递html内容
默认插槽
app.vue
<child>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</child>
child.vue
<div class="child">
我是子组件
<hr />
<!-- 将会接收 父组件内 child标签中间的dom结构 -->
<slot></slot>
</div>
具名插槽
<child>
<template slot="slotA">
<img src="@/assets/logo.png" alt="" />
</template>
<template slot="slotB">
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</child>
<div class="child">
<slot name="slotA"></slot>
<hr />
我是子组件
<hr />
<!-- 将会接收 父组件内 child标签中间的dom结构 -->
<slot name="slotB"></slot>
</div>
作用域插槽
适用于: 数据不在父组件内 在 子组件内 如果父组件想个性化定制子组件的内容
<div class="child">
我是子组件
<hr />
<!-- 将会接收 父组件内 child标签中间的dom结构 -->
<slot :listData="list"></slot>
</div>
list在child内
data() {
return {
list: [
{ id: 0, name: "zhuque" },
{ id: 1, name: "wanzi" },
{ id: 2, name: "yingtao" },
{ id: 3, name: "afei" },
],
};
父组件内
<child>
<template scope="abc">
<!-- abc会接收来自于child组件内 slot标签的 所有的属性值 { "listData": [ { "id": 0, "name": "zhuque" }, { "id": 1, "name": "wanzi" }, { "id": 2, "name": "yingtao" }, { "id": 3, "name": "afei" } ] }-->
<!-- {{ abc }} -->
<ul>
<li v-for="item in abc.listData" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</child>
<child>
<template scope="data">
<ol>
<li v-for="item in data.listData" :key="item.id">{{ item.name }}</li>
</ol>
</template>
</child>
通过 解构赋值
<child>
<template scope="{listData}">
<ul>
<li v-for="item in listData" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</child>
普通节点 不能直接用scope 得用slot-scope
<child>
<ol slot-scope="{ listData }">
<li v-for="item in listData" :key="item.id">{{ item.name }}</li>
</ol>
</child>
Vue3
确保 你的 vue-cli 是 4.5.0的版本以上
vue --verstion的方式查看 版本过低请更新版本
初始文件对比
// vue2
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
// vue3
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
// vue3
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
//比之前的 vm 身上要少一些东西 创建实例对象
app.use(store).use(router).mount('#app')
router/index.js的区别
vue2
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
]
// history模式 hash模式
const router = new VueRouter({
mode: 'history',
routes
})
export default router
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
]
// createRouter 创建 router实例对象
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
修改 路由模式
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes
})
store/index.js
vue2
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
vue3
import { createStore } from 'vuex'
// createStore 创建 store实例对象
export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
关于 版本
vue2
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
不能使用 4版本
vue3内
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
不能使用 3版本
初始 setup
vue3一个新的配置项 一个函数
vue2中 option配置项 data methods computed 等等 都会换个形式 写入 setup函数内
data中的数据
data() {
return {
msg: "你好",
};
},
setup函数内
setup() {
let msg = "你好";
return {
msg,//暴露给模板使用 不return 模板没法使用 msg数据
};
},
setup函数返回值
1.返回数据给模板使用 (返回一个对象 常用)
2.(很少用) return 渲染函数
<template>
<div id="nav">我是 App组件{{ msg }}</div> //h1就会出现在这
</template>
<script>
import { h } from "vue";
export default {
name: "App",
setup() {
return () => h("h1", "我是h1标签");
},
};
</script>
<style></style>
setup中的 this
undefined 不指向组件实例
原因: setup执行的时机 beforeCreate之前
所以在setup函数内 几乎不使用 this
也意味着 最好是不要和 option中的 data method 等配置 混用
method配置项 直接在 setup内定义函数即可
<template>
<div id="nav">我是 App组件{{ msg }}</div>
<button @click="changeMsg">修改 msg</button>
</template>
setup() {
let msg = "你好";
function changeMsg() {
// 修改msg
console.log(msg);
}
return {
msg,
changeMsg,// !一定要 return出去 要不然模板拿不到
};
},
数据响应式问题
当我们触发 btn时发现 msg已经发生了变化 但是视图没有更新 数据不具有响应式
<div id="nav">{{ msg }}</div>
<button @click="changeMsg">修改 msg</button>
setup() {
let msg = "你好";
function changeMsg() {
// 修改msg
msg += "!!!!";
console.log(msg);
}
return {
msg,
changeMsg,
};
},
ref 函数
//要用 必须导入
import { ref } from 'vue';
let msg = ref("你好");
ref 作用 : 定义一个响应式数据
let /const xxx = ref(数据源)
msg ==> RefImpl{} 包含了响应式数据的引用对象 // 简称ref对象
RefImpl{
value:(...) //对象代理
}
数据 存在 msg.value属性上了
<div id="nav">{{ msg }}</div>
<button @click="changeMsg">修改 msg</button>
setup() {
let msg = ref("你好");
console.log(msg);
function changeMsg() {
msg.value += "!!!!";
console.log(msg);
}
return {
msg,
changeMsg,
};
},
let msg = '你好'
1.如果想要数据具有响应式 ref函数包裹为RefImpl{}对象 let msg = ref('你好')
2.修改ref数据 msg.value 去修改
3.return的时候 直接return msg
4.模板里使用时 直接使用 msg
注意:
function changeMsg() {
// msg.value += "!!!!";
msg = "你好你好"; //切断了 msg 和 RefImpl{}的引用关系 会失去响应式
console.log(msg);
}
对对象类型的数据
能响应式监听到 对象属性的增删 (vue2不能做到的)
不需要再使用Vue.set vm.$set对对象进行操作了
对于数组的 下标操作 也可以响应式监听到变化了 (但是vue2就不行 )
<template>
<div id="nav">{{ msg }}</div>
<p>名字: {{ person.name }}</p>
<p>年龄: {{ person.age }}</p>
<p>爱好: {{ person.hobby }}</p>
<ul>
<li v-for="item in arr">{{ item }}</li>
</ul>
<button @click="changeMsg">修改 msg</button>
<button @click="changeAge">修改 person.age</button>
<button @click="changeArr">修改 arr</button>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
let msg = ref("你好");
let person = ref({
name: "zhuque",
age: 3,
});
let arr = ref(["我", "爱"]);
console.log(person);
function changeMsg() {
msg.value = "你好你好";
console.log(msg);
}
function changeAge() {
person.value.age++;
person.value.hobby = "打球";
delete person.value.name;
}
function changeArr() {
arr.value[2] = "你"; //对于数组的 下标操作 也可以响应式监听到变化了 (但是vue2就不行 )
}
return {
msg,
changeMsg,
person,
changeAge,
arr,
changeArr,
};
},
};
</script>
<style></style>
App.vue
<template>
<div id="nav">我是App组件{{ msg}}</div>
<p>
名字:{{ person.name }}
年龄:{{ person.age }}
爱好:{{ person.hobby }}
</p>
<ul>
<li v-for="item in arr" :key="item"> {{item}}</li>
</ul>
<button @click="changeMsg">修改msg</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeArr">修改arr</button>
</template>
<script>
import { ref } from "vue"
export default{
name:"App",
// data(){
// return{
// msg:"你好",
// };
// },
setup(){
let msg = ref("你好");
let person = ref({
name:"zz",
age:4
});
let arr = ref(['我','喜','欢','你'])
console.log(this) //undefined
function changeMsg(){
// 修改msg
msg.value+="!!!!"
console.log(msg)
}
function changeAge(){
// person.value.age++
// person.value.hobby="打球"
delete person.value.name
}
function changeArr(){
arr.value[4]="呀"
}
return{
msg,
changeMsg,
person,
changeAge,
arr,
changeArr
}
}
};
</script>
<style lang="scss">
</style>
ref处理对象
无论对象内嵌套多少对象 都是做了响应式处理的
ref 中 定义基础数据类型 通过 defineProperty实现的
ref中 定义对象数据类型 底层借助了 reactive 函数 把对象 包装为proxy代理对象
<template>
<p>名字: {{ person.name }}</p>
<p>年龄: {{ person.age }}</p>
<p>爱好: {{ person.hobby }}</p>
<ul>
<li v-for="item in person.tags" :key="item">{{ item.name }}</li>
</ul>
<button @click="changeTags">修改 person.tags[0]</button>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
let msg = ref("你好");
let person = ref({
name: "zhuque",
age: 3,
tags: [{ name: "美女" }, { name: "富婆" }],
});
function changeTags() {
person.value.tags[0].name = "小可爱";
}
return {
person,
changeTags,
};
},
};
</script>
<style></style>
setup函数内的 props参数
props的接收
app.vue
<child :data="msg"></child>
child.vue
<template>
<div class="child">
<h1>子组件:{{ data }}</h1>
</div>
</template>
<script>
export default {
name: "Child",
props: ["data"], //如果没有接收 setup函数内的props会是空对象
setup(props) {
console.log(props); // Proxy {data: '你好'}
},
};
</script>
setup函数内的 context参数
1. // context.attrs 等同于 之前 vue2 this.$attrs
// context.emit 等同于 之前的 vue2 this.$emit
child
emits: ["change"], // 写一下更好
setup(props, context) {
// console.log(props.data);
console.log(context);
// context.attrs 等同于 之前 vue2 this.$attrs
//change 触发 Child组件上的自定义 change事件
context.emit("change");
}
app
<child :data="msg" @change="fn"></child>
function fn() {
msg.value = "你好你好啊~~~~~";
console.log("触发了 change自定义事件");
}
由于 emit用的比较多 一般解构出来用
setup(props, { emit }) {
let childData = ref("子组件数据");
let num = ref(0);
//change 触发 Child组件上的自定义 change事件
emit("change");
return {
childData,
num,
};
},
vue3中 ref的变化
vue2
<child ref="childRef"></child>
获取的时候 this.$refs.childRef 去获取 child实例对象
vue3中
setup() {
const childRef = ref(null); //第一步
return{
childRef//第二步
}
}
第三步 : <child ref="childRef"></child>
在setup中 操作 childRef
function getChild() {
console.log(childRef.value); //直接访问 .value 即可
}
expose参数
vue2中 利用ref获取 子组件内的data数据
<child :data="msg" @change="fn" ref="childRef"></child>
methods: {
getChild() {
console.log(this.$refs.childRef); //能获取到 child组件的 vc对象 data上的所有的数据
},
}
child.vue
data() {
return {
childData1: "11111",
};
},
假设我们不想 暴露vc给别的组件 只希望暴露指定的数据
context.expose({
childData, // 通过ref 不再能获取到 组件的vc对象 而是只能获取 childData数据
});
return {
childData,
num,
};
reactive函数
作用: 定义一个对象类型的响应式数据 (基础数据类型 不能用它 得用ref 函数)
- const 代理对象 = reactive(源对象) 接收一个对象 或者 数组
- 定义的响应式数据 是’深层次’ 内部的数据都是响应式的
- 基于 es6的 proxy 实现的
let person = reactive({ //不能放基础数据类型
name: "zhuque",
age: 3,
tags: [{ name: "美女" }, { name: "富婆" }],
});
function changeAge() {
// person.age++;
// person.hobby = "打球";
person.tags[0].name = "小可爱";
}
和 ref 对比一下
在定义上 ref() //可以接收任意数据类型
reactive //只接受 对象类型
在setup内操作时
ref定义的数据
xxx.value.msg
reactive定义的数据
xxx.msg
return的时候 都是直接 return
模板内使用时 也是直接使用
vue3新版本中 建议 使用 ref
computed
<input type="text" v-model="firstname" />
<input type="text" v-model="lastname" />
<p>全名: {{ fullname }}</p>
import { ref, computed } from "vue";
setup() {
let firstname = ref("王");
let lastname = ref("大锤");
// computed
// 计算属性 依赖于其他的响应式数据 数据发生变化 计算属性会重新计算
let fullname = computed(() => {
//只有getter
return firstname.value + "-" + lastname.value;
});
return {
firstname,
lastname,
fullname,
};
},
如果 计算属性 需要被修改
对象写法
<input type="text" v-model="firstname" />
<input type="text" v-model="lastname" />
<p>全名: {{ fullname }}</p>
<input type="text" v-model="fullname" />
let fullname = computed({
get() {
//getter
return firstname.value + "-" + lastname.value;
},
set(newValue) {
firstname.value = newValue.split("-")[0];
lastname.value = newValue.split("-")[1];
},
});
//在setup中 使用 计算属性时
fullname //ComputedRefImpl{}
console.log(fullname.value);
computed多个情况 (了解)
当计算属性 添加到 一个对象内 这个对象是响应式时 person.value.fullname 就是 本身的值
<div id="nav">
<input type="text" v-model="person.firstname" />
<input type="text" v-model="person.lastname" />
<p>全名: {{ person.fullname }}</p>
<input type="text" v-model="person.fullname" />
</div>
setup() {
let person = ref({
firstname: "王",
lastname: "大锤",
});
// computed
// 计算属性 依赖于其他的响应式数据 数据发生变化 计算属性会重新计算
person.value.fullname = computed(() => {
return person.value.firstname + "-" + person.value.lastname;
});
console.log(person.value.fullname); // 王-大锤 而不是 ComputedRefImpl对象
//当计算属性 添加到 一个对象内 这个对象是非响应式数据时 person.fullname 就会被处理为 ComputedRefImpl对象
let person2 = {};
person2.fullname = computed(() => {
return person.value.firstname + "-" + person.value.lastname;
});
console.log(person2.fullname); //ComputedRefImpl对象
! 如果不知道 啥时候该.value 用前 打印看看样子
watch
<h1>msg: {{ msg }}</h1>
<input type="text" v-model="person.firstname" />
<input type="text" v-model="person.lastname" />
<p>全名: {{ fullname }}</p>
<button @click="msg += '!'">修改 msg</button>
// 监听ref基础值类型 (切记不能监听msg.value 监听字符串无意义)
watch(msg, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//监听 ref多个数据
watch([msg, num], (newValue, oldValue) => {
console.log(oldValue, newValue); //['你好', 2] (2) ['你好!', 2]
});
//监听 ref对象类型 得.value 获取不到旧值 深度监听 (默认开启)(当person内的任意属性发生变化 都能触发 watch回调运行 )
watch(person.value, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//监听 ref 数组类型 得.value 获取不到旧值
watch(arr.value, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
// 监听 reactive 对象类型 不需要.value 获取不到旧值
watch(person, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//只监听 person内某个属性 监听一个基础值 会报错
watch(person.value.firstname, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
以上的解决方案
//只监听 person内某个属性 箭头包一下 return
watch(
() => person.value.firstname,
(newValue, oldValue) => {
console.log(oldValue, newValue);
}
);
// 只监听 person内某个属性 监听一个对象 这样写 ok
watch(person.value.tags, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//一些其他的参数
watch(
person.value.tags,
(newValue, oldValue) => {
console.log(oldValue, newValue);
},
{
immediate: true,
}
);
watch
<h1>msg: {{ msg }}</h1>
<input type="text" v-model="person.firstname" />
<input type="text" v-model="person.lastname" />
<p>全名: {{ fullname }}</p>
<button @click="msg += '!'">修改 msg</button>
// 监听ref基础值类型 (切记不能监听msg.value 监听字符串无意义)
watch(msg, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//监听 ref多个数据
watch([msg, num], (newValue, oldValue) => {
console.log(oldValue, newValue); //['你好', 2] (2) ['你好!', 2]
});
//监听 ref对象类型 得.value 获取不到旧值 深度监听 (默认开启)(当person内的任意属性发生变化 都能触发 watch回调运行 )
watch(person.value, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//监听 ref 数组类型 得.value 获取不到旧值
watch(arr.value, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
// 监听 reactive 对象类型 不需要.value 获取不到旧值
watch(person, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//只监听 person内某个属性 监听一个基础值 会报错
watch(person.value.firstname, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
以上的解决方案
//只监听 person内某个属性 箭头包一下 return
watch(
() => person.value.firstname,
(newValue, oldValue) => {
console.log(oldValue, newValue);
}
);
// 只监听 person内某个属性 监听一个对象 这样写 ok
watch(person.value.tags, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
//另一个写法
watch(
() => person.value.tags,
(newValue, oldValue) => {
console.log(oldValue, newValue);
},
{
deep: true, //如果() => person.value.tags, 开启深度监视
}
);
//一些其他的参数
watch(
person.value.tags,
(newValue, oldValue) => {
console.log(oldValue, newValue);
},
{
immediate: true,
}
);
如果需要 获取旧的值
import _ from "lodash"; //不需要npm
watch(
() => _.cloneDeep(person.value.tags), //深拷贝对象
(newValue, oldValue) => {
console.log(oldValue, newValue);
}
);
watchEffect
响应式数据自动应用使用 watchEffect 函数 参数是一个回调函数 对函数内的 响应式的数据 进行追踪 数据发生了变化 就会触发运行该函数
import { watchEffect } from "vue";
watchEffect(() => {
//query.value 发生变化 就会触发watchEffect回调函数重新运行
let queryValue = query.value; //变化的量
console.log("watchEffect函数执行了 发起请求 请求api(queryValue)");
}); // 不管依赖的数据 发不发生变化 默认先执行一次
允许停止 监听
// 允许停止监听
const wName = watchEffect(() => {
let queryValue = query.value; //变化的量
console.log("watchEffect函数执行了 发起请求 请求api(queryValue)");
});
wName(); 停止 监听
生命周期
(vue2)option内api setup 内
beforeCreate 不存在 相当于setup()
created 不存在 相当于setup()
beforeMount onBeforeMount
mounted onMounted (请求数据 初始化的操作)
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount 销毁前执行
destroyed onUnmounted 销毁后执行
activated onActivated 组件激活时调用
deactivated onDeactivated 组件失活时调用
import { ref, onBeforeMount } from "vue"; //先导入 再使用 除了destroyed 其他就是 vue2基础上 加个 on
setup(){
onBeforeMount(() => {
console.log("onBeforeMount");
});
}
自定义hook
changeColor.js
import { ref } from 'vue'
// 导出一个函数
export const changeColor = () => {
const color = ref("red");
const changeColorApi = () => {
color.value = randomColor();
};
const randomColor = () => {
return `rgb(
${randomNum(0, 255)},
${randomNum(0, 255)},
${randomNum(0, 255)})`;
};
const randomNum = (num1, num2) => {
return Math.floor(Math.random() * (num2 + 1 - num1) + num1);
};
return {
//希望给app.vue用到的数据
changeColorApi,
color
}
}
app.vue内
<template>
<div id="nav">
<div class="box" :style="{ background: color }">切换颜色</div>
<button @click="changeColorApi">修改颜色</button>
</div>
</template>
<script>
import { onBeforeMount, onMounted, ref } from "vue";
import { changeColor } from "./changeColor.js";
export default {
name: "App",
setup() {
// hook 本质就是一个函数 单独封装了一些功能
const { color, changeColorApi } = changeColor();
return {
color,
changeColorApi,
};
},
};
</script>
<style></style>
1. 抽离功能 形成js文件 导出一个函数 需要暴露数据给 组件用 return数据
2. 组件要使用 导入 js文件 导入函数
3. 运行函数 得到返回值
好处: 结构清晰好维护 代码可复用
toRef
作用 : 用来为源响应式对象上的某个属性新创建一个ref。然后ref可以被传递,一直保持对其源属性响应式连接在一起
语法 const fullname = toRef(person,'fullname')
应用 : 如果希望 讲响应式对象中的某个属性 单独提供给外部使用时
如果该属性是 基础数据类型 与原属性丢失响应式 (fullname 和 person没有关系了 person发生变化 外部的 fullname不会发生变化 )
错误示范 :
let person = ref({
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
return {
person,
fullname:person.value.fullname, //丢失响应式连接 person发生变化 外部的 fullname不会发生变化
changeFullname,
};
正确操作 : 能保持和源属性响应式连接
const changeFullname = () => {
person.value.fullname = "王大大大大";
};
const fullname = toRef(person.value, "fullname");
console.log(fullname); // ObjectRefImpl
return {
person,
fullname,
changeFullname,
};
多个属性需要 在模板内直接使用
const fullname = toRef(person.value, "fullname");
const firstname = toRef(person.value, "firstname");
const lastname = toRef(person.value, "lastname");
return {
fullname,
changeFullname,
firstname,
lastname,
};
toRefs
const personObj = toRefs(person.value);
return {
changeFullname,
...personObj,
};
shallowReactive 与 shallowRef
shallowReactive : 只处理对象最外层属性的响应式
shallowRef 只处理 基础数据类型的响应式 不进行对象的响应式处理
用途:
如果有一个对象 结构比较深 变化只有外层属性变化 ==> shallowReactive
如果有一个对象 后续功能不会修改对象内的属性 而是生成新的对象来替换 shallowRef
let person = shallowReactive({
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
let arr = ref([]);
const changeTags = () => {
person.tags[0].name = "唱歌"; //没法监听到变化
};
const changeFullname = () => {
person.fullname = "王大大大大"; //可以监听到变化
};
let person = shallowRef({ //不响应对象
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
const changeFullname = () => {
// person.value.fullname = "王大大大大"; //没有变化
person.value = { //有变化
firstname: "王~~~~",
lastname: "大锤~~~~~",
fullname: "王大锤~~~~",
tags: [{ name: "跳舞~~~~~" }],
};
};
应用 : 动态组件中
<component :is="data.componentId"></component>
const bool = ref(true);
let data = shallowRef({ //使用ref 会得到警告 对组件响应式化浪费性能
componentId: bool.value ? Child : HelloWorld,
});
return {
data,
}
readonly 与shallowReadonly
- readonly 让一个数据(或者响应式数据) 变为只读的 (深只读 只能获取不允许修改)
- shallowReadonly让一个数据(或者响应式数据) 变为只读的 (浅只读 内层的属性对象可以修改)
- 不希望你的数据被修改时 ==> 第三方的插件
import { computed, readonly, ref, shallowRef } from "vue";
let person = readonly({ // proxy 不需要.value
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
操作person内的属性 会报警告
let person = shallowReadonly({ // proxy 不需要.value
firstname: "王",
lastname: "大锤",
fullname: "王大锤",//修改fullname 报警告
tags: [{ name: "跳舞" }], //修改tags内的数据 会引发变化 但是 视图不更新
});
toRaw 与 markRaw
-
toRaw :
作用: 将一个 由 reactive 生成的响应式对象 转为 普通对象
使用场景 : 用于读取响应式对象对应的普通对象 对这个普通对象的所有操作 不会引发页面更新
let person = reactive({
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
const personRaw = toRaw(person);
console.log(personRaw)
- markRaw:
- 作用: 标记一个对象 使其永远不会成为响应式对象
- 应用场景 有些值 不应该被设置为响应式的 例如复杂的第三方类库
- 当渲染具有不可变数据源的大列表时 , 跳过响应式转化 可以提高性能
let person = markRaw({
firstname: "王",
lastname: "大锤",
fullname: "王大锤",
tags: [{ name: "跳舞" }],
});
console.log(person);//普通对象
console.log(reactive(person)); //还是普通对象
customRef
作用: 自定义ref
<input type="text" v-model="query" />
<p>{{ query }}</p>
const useDebouncedRef = (value) => {
// 返回一个特定的 ref对象
return customRef((track, trigger) => {
// track 进行追踪数据变化
return {
//getter 和 setter
get() {
console.log("执行");
track(); //value获取到最新的依赖值
return value;
},
set(newValue) {
console.log("设置");
value = newValue;
setTimeout(trigger, 1000);
// trigger(); //触发get函数重新运行 触发Vue去更新界面
},
};
});
};
const query = useDebouncedRef("");
console.log(query); //CustomRefImpl
实现防抖效果
用户瞬间的操作 都会导致事件高频触发 如果响应复杂 高频触发 可能会导致响应更不上 页面卡顿 假死现象
实时检查输入 绑定了 keyup事件 请求服务端响应搜索结果 触发频率太高 导致大量的请求发出 响应速度更不上
解决方案: 限制一下 事件的触发频率
经典方案: 设定一个周期延迟的执行动作 如果期间事件又被触发 重新设定周期 知道周期结束我们才执行动作
<input type="text" v-model="query" />
<p>{{ query }}</p>
const useDebouncedRef = (value) => {
// 返回一个特定的 ref对象
return customRef((track, trigger) => {
// track 进行追踪数据变化
let timer = null;
return {
//getter 和 setter
get() {
track(); //value获取到最新的依赖值
return value;
},
// 1 500ms之后 2 发送两个请求
// 1 马上按 2 发送一个请求
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
console.log("发送请求");
value = newValue;
trigger(); //触发get函数重新运行 触发Vue去更新界面
}, 500);
},
};
});
};
const query = useDebouncedRef("");
console.log(query); //CustomRefImpl
响应式数据的判断
返回布尔值
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
祖 ==> 孙组件之间的通信
provide与inject
用之前的props也能实现 但是要先爷爷传给儿子 儿子再传给孙子 很麻烦
祖组件中 : 传递
setup() {
const msg = ref("我是爷爷给孙子的数据");
//要传递一个 msgData 里面装着 ref("我是爷爷给孙子的数据")这个数据
provide("msgData", msg);
},
孙组件中 : 接收
setup() {
const msgFromGrandFather = inject("msgData");
return {
msgFromGrandFather,
};
},
父传子 : props
子传父: 自定义事件
任意组件通信 全局事件总线 vuex
祖传孙 : provide与inject
其他的新增和修改
Fragment
vue3组件中 template 并不是只允许一个根节点
原因: Fragment 内部会将多个标签 包含在一个 Fragment 虚拟元素中
好处: 减少标签层级 减小内存的占用
Teleport 标签
是一个能够将我们组件html结构 移动到指定位置的技术 ( 为了更好的写css 尤其有定位时 )
注意:在安装组件之前,目标元素必须存在-即,目标不能由组件本身呈现,理想情况下应该位于整个Vue组件树之外 ( 要么body 要么 #app )文章来源:https://www.toymoban.com/news/detail-515757.html
<teleport to="body"> //节点会移动到 body的位置
<div class="sun">
<h1>我是孙子 {{ msgFromGrandFather }}</h1>
</div>
</teleport>
.sun {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
transform: translate(-50%, -50%);
background-color: blue;
}
修改
vue2.x
注册全局组件
Vue.component('组件名',组件对象)
注册全局指令
Vue.directive('指令名',指令对象)
Vue.mixin()
Vue.use()
vue 3.x
注册全局组件
app.component('组件名',组件对象)
注册全局指令
app.directive('指令名',指令对象)
app.mixin()
app.use()
vue2 中 vue3中
Vue.prototype app.config.globalProperties //Vue原型上添加属性
Vue.config.xxx app.config.xxxx
过渡类名的写法
vue2
v-enter,v-leave-to{}
v-leave,v-enter-to{}
vue3
在起点项中 加上 -from
v-enter-from,v-leave-to{}
v-leave-from,v-enter-to{}
css操作
绑定数据
以v-bind css函数 把css的值关联到组件状态文章来源地址https://www.toymoban.com/news/detail-515757.html
setup() {
const fontColor = ref("red");
return {
fontColor,
};
},
<style>
.grand-father {
color: v-bind(fontColor);
}
</style>
深度操作
提供了修改第三方Ui框架组件样式的 方案
<Child></Child>
app 想要 控制 child组件内的 h1的css
.child :deep(h1) {
background-color: yellow;
}
全局选择器
在组件内 修改 body html的样式 是不成功的
:global(body) {
background-color: red;
}
Vuex 的变化
使用 state内的数据
import { useStore } from "vuex";
const store = useStore();
const msg = computed(() => store.state.msg);
getters 内的同理
const msgGetter = computed(() => store.getters.msg); //保留响应性 computed操作一下
store.commit("FN"); // 触发mutation内的函数
store.dispatch("fn"); // 触发action内的函数
不用 map...系列了 直接用store对象操作一切
Router的变化
vue2中
this.$router.push() 进行路由跳转
this.$route获取路由信息
vue3
import { useRouter, useRoute } from "vue-router";
setup() {
const router = useRouter();
const route = useRoute();
// router.push('/')
console.log(route);
},
缓存路由组件写法修改
vue2中
<keep-alive>
<router-view></router-view>
</keep-alive>
升级为下面的写法
vue3中
<router-view v-slot="{ Component }"> slot标签传给 父组件的 数据
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
插槽
slot='{}'
v-slot vue3新增的指令
实现作用域插槽
<Sun>
<template v-slot="{ msg }">
<h2>{{ msg }}</h2>
<img src="@/assets/logo.png" alt="" />
</template>
</Sun>
<div class="sun">
<h1>我是孙子</h1>
<slot :msg="msg"></slot>
</div>
实现作用域以及 具名插槽
<Sun>
<template v-slot:slotA="{ msg }">
<h2>{{ msg }}</h2>
<img src="@/assets/logo.png" alt="" />
</template>
<template v-slot:slotB="{ msg1 }">
<h2>{{ msg1 }}</h2>
<img src="@/assets/logo.png" alt="" />
</template>
</Sun>
<slot :msg="msg" name="slotA"></slot>
<slot :msg1="msg1" name="slotB"></slot>
v-model 使用到 自定义组件上
语法糖的处理
child.vue
<template>
<div class="child">
<h1>我是儿子</h1>
<Sun v-if="bool" v-model="bool"></Sun>
<button @click="fn">点击展示 Sun组件</button>
</div>
</template>
<script>
import { ref } from "vue";
import Sun from "./Sun.vue";
export default {
name: "Child",
components: { Sun },
setup() {
const bool = ref(false);
const fn = () => {
//显示 Sun组件
bool.value = true;
};
return {
bool,
fn,
};
},
};
</script>
Sun.vue中
<template>
<div class="sun">
<h1>我是孙子</h1>
<button @click="closeFn">关闭按钮</button>
</div>
</template>
<script>
import { inject, ref } from "vue";
export default {
name: "Sun",
props: ["modelValue"],
setup(props, { emit }) {
const closeFn = () => {
emit("update:modelValue", false);
};
return {
closeFn,
};
},
};
</script>
<style scoped></style>
原理
<Sun
v-if="bool"
:modelValue="bool"
@update:modelValue="modelValue = $event"
></Sun>
script setup写法
defineProps
<Child :data="num"></Child>
//在这里定义的数据 函数 props可以直接在模板内使用 不需要 return
<script setup>
import { ref } from "vue";
import Sun from "./Sun.vue";
const props = defineProps(['data']) //里面接收数组写法 和 对象写法
console.log(props.data);
const bool = ref(false);
const fn = () => {
//显示 Sun组件
bool.value = true;
};
</script>
导入子组件
只需要导入 就可以直接使用
<Sun
v-if="bool"
v-model="bool"
:modelValue="bool"
@update:modelValue="modelValue = $event"
></Sun>
import Sun from "./Sun.vue";
defineEmits
const emit = defineEmits(["select"]);
const bool = ref(false);
const fn = () => {
//显示 Sun组件
emit("select");
bool.value = true;
};
父组件中
<Child :data="num" @select="fn"></Child>
const fn = () => {
console.log("App.vue");
};
defineExpose
跟之前不同的点是 <script setup> 默认关闭的 (之前的写法 可以通过 ref获取组件的实例对象 然后获取他的数据)
defineExpose({
bool, // 外部只能获取到 bool数据
});
到了这里,关于【day 12】插槽和vue3的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!