3.1 子传父emit
非语法糖写法
子组件
<template> <h1>{{ name }}</h1> <h1>{{ school }}</h1> <button @click="test">按钮</button> </template> <!-- setup取代了export default --> <script> export default { props: ['name', 'school'], emits: ['hello'] //派发hello,否则会警告 setup(props, context) { let test = () => { context.emit('hello', '我是子组件的数据') } return { test } } } </script> <style scoped> .read-the-docs { color: #888; } </style>
父组件
<template> <div> <HelloWorld name="白泽" school="观音大士学府" @hello="helloworld"></HelloWorld> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { setup() { let helloworld = (val) => { console.log('val=>', val); } return { helloworld } }, components: { HelloWorld } } </script> <style scoped> </style>
语法糖写法
子组件
<template> <button @click="test">按钮</button> </template> <!-- setup取代了export default --> <script setup> let emit = defineEmits(['hello']) //将子传父的方法名解构出来的 let test = () => { emit('hello', [11, 22, 33]) } </script> <style scoped> .read-the-docs { color: #888; } </style>
父组件
<template> <div> <HelloWorld @hello="helloworld"></HelloWorld> </div> </template> <script setup> import HelloWorld from './components/HelloWorld.vue' let helloworld = (val) => { console.log('val=>', val); } </script> <style scoped> </style>
3.2 defineExpose的使用
子组件
<template> <h1>{{name}}</h1> <h1>{{info}}</h1> </template> <!-- setup取代了export default --> <script setup> import { ref, reactive } from 'vue' let name = ref('我是子组件') let info = reactive({ age: 12, hobby: '敲代码' }) // 将响应式数据暴露出去 defineExpose({ name, info }) </script> <style scoped> .read-the-docs { color: #888; } </style>
父组件
<template> <div> <!-- 这里的ref是获取子组件传过来的数据 --> <HelloWorld ref="hello"></HelloWorld> <button @click="getData">接收子组件的数据</button> </div> </template> <script setup> import HelloWorld from './components/HelloWorld.vue' import { ref } from 'vue' let hello = ref() let getData = () => { console.log('val', hello); //RefImpl console.log('name', hello.value.name); //我是子组件 console.log('info', hello.value.info); //Proxy(Object) {age: 12, hobby: '敲代码'} } </script> <style scoped> </style>
3.3 Vue3响应式原理
proxy代理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var proxy = new Proxy({
add: function(value) {
return value + 10
},
info: '我是proxy'
}, {
get: function(target, key, poperty) {
// console.log('target', target); // target {info: '我是proxy', add: ƒ}
// console.log('key', key); //key name
// console.log('poperty', poperty); // proxy实例本身,该参数可选
// console.log('我是钩子函数'); // 我是钩子函数 先打印这里,相当于之前学的钩子函数
return target[key]
},
set: function(target, key, value, receiver) {
return target[key] = value
}
})
console.log('proxy.info=>', proxy.info); // proxy.info=> 我是proxy
proxy.info = 'proxy' //target[key] = value
console.log('proxy.info=>', proxy.info); // proxy.info=> proxy
</script>
</body>
</html>
Vue3响应式原理
proxy和Reflect结合产生的,产生更好的兼容性,可以很好的捕获错误
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
let obj = {
name: 'vue3',
arr: [11, 22, 33],
obj: {
obj1: {
num: 99
}
}
}
function proxyData(value) {
let proxy = new Proxy(value, {
get: function(target, key) {
let val = target[key]
if (typeof val == 'object') {
proxyData(val)
}
return val
},
set: function(target, key, value) {
return target[key] = value
},
deleteProperty: function(target, key) {
delete target[key]
return true
}
})
return proxy
}
let pro = proxyData(obj)
pro.age = 12 //新增属性及属性值
console.log('pro', pro); // Proxy(Object) {name: 'vue3', arr: Array(3), obj: {…}, age: 12}
delete pro.name //删除
console.log('pro', pro); //Proxy(Object) {arr: Array(3), obj: {…}, age: 12}
pro.arr.push(44) //数组方法
console.log('pro.arr', pro.arr); // arr:(4) [11, 22, 33, 44]
pro.arr[0] = 666 //通过数组下标修改值
console.log('pro.arr', pro.arr); // arr:(4) [666, 22, 33, 44]
</script>
</body>
</html>
Reflect
产生更好的兼容性,可以很好的捕获错误
<!-- <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
let obj = {zhangsan:100,lisi:200}
Object.defineProperty(obj,'wangwu',{
get(){
return 300
}
})
Object.defineProperty(obj,'wangwu',{
get(){
return 400
}
})
</script>
</body>
</html> -->
<!-- <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
let obj = {zhangsan:100,lisi:200}
Reflect.defineProperty(obj,'wangwu',{
get(){
return 300
}
})
Reflect.defineProperty(obj,'wangwu',{
get(){
return 400
}
})
</script>
</body>
</html> -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
let obj = {
zhangsan: 100,
lisi: 200
}
const a = Reflect.defineProperty(obj, 'wangwu', {
get() {
return 300
}
})
const b = Reflect.defineProperty(obj, 'wangwu', {
get() {
return 400
}
})
console.log(a);
console.log(b);
// proxy和Reflect结合产生的
</script>
</body>
</html>
Vue2的响应式原理
Object.defineProperty() 可以监测到属性的获取、修改,但是新增、删除监测不到
对于数组:Object.defineProperty()不能监听数组的变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据响应式原理</title>
</head>
<body>
<div id="app">
Hello Vue
</div>
<script>
//模拟Vue实例中的data选项
let data = {
msg: 'Hello Vue',
arr: [1, 2, 3],
count: 0
}
//模拟Vue的实例
let vm = {};
function defineProperties(data) {
//循环给每个属性使用Object.defineProperty()
Object.keys(data).forEach(key => {
//数据劫持,当访问或者设置vm中的成员的时候,做一些干预操作
Object.defineProperty(vm, key, {
//可枚举(即可被遍历)
enumerable: true,
//可配置(可以使用delete删除,可以通过defineProperty重新定义)
configurable: true,
//当获取值时执行
get() {
console.log('getter:', data[key]);
return data[key];
},
//当设置、更新msg变量时执行
set(newValue) {
console.log("setter:", newValue);
if (data[key] === newValue) {
return; //前后数据相同,则不用做操作DOM的多余操作
}
data[key] = newValue;
// 将id=app的标签元素的内容改成newValue
document.querySelector("#app").textContent = newValue;
}
})
});
}
//执行该函数,使每个属性添加响应式
defineProperties(data);
//测试setter
vm.msg = "Hello 响应式原理";
vm.arr[1] = 4
//测试getter
console.log(vm);
</script>
</body>
</html>
Proxy-Vue3的响应式原理案例对比
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div></div>
<script>
const arr = [1, 2, 3];
const handler = {
get(target, key) {
console.log(`修改了数组下标为 ${key} 的元素`);
return target[key];
},
set(target, key, value) {
console.log(`set修改了数组下标为 ${key} 的元素,新值为 ${value}`);
target[key] = value;
return true;
}
};
const proxyArr = new Proxy(arr, handler);
proxyArr[1] = 4; // 输出 "修改了数组下标为 1 的元素,新值为 4"
//触发了set 我们认为就是产生页面响应性变化
</script>
</body>
</html>
Property-Vue2的响应式原理案例对比
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据响应式原理</title>
</head>
<body>
<div id="app">
Hello Vue
</div>
<script>
let arr = [1, 2, 3]
let obj = {}
Object.defineProperty(obj, 'arr', {
get() {
console.log('get arr')
return arr
},
set(newVal) {
console.log('set', newVal)
arr = newVal
}
})
obj.arr[1] = 4 // 只会打印 get arr, 不会打印 set
//没有触发set 我们认为就是没有产生页面响应性变化
</script>
</body>
</html>
3.4 插槽
具名插槽
作用域插槽:父组件有权访问子组件的数据并修改
子组件
<template> <!-- 具名插槽 --> <!-- <slot name="left">11</slot> <slot name="center">22</slot> <slot name="right">3344</slot> --> <!-- 作用域插槽 --> <slot :info="info"></slot> </template> <!-- setup取代了export default --> <script setup> import { ref, reactive } from 'vue' // 作用域插槽 let info = reactive({ age: 12, hobby: '敲代码' }) </script> <style scoped> .read-the-docs { color: #888; } </style>
父组件
<template> <div> <!-- 具名插槽 --> <!-- <HelloWorld> --> <!-- 写法1: --> <!-- <template #left></template> <template #center></template> <template #right></template> --> <!-- 写法2: --> <!-- <template v-slot:left></template> <template v-slot:center></template> <template v-slot:right></template> --> <!--错误 <span slot="left"></span> <span slot="center"></span> <span slot="right">我是right</span> 这种不要写了 --> <!-- </HelloWorld> --> <!-- 作用域插槽 --> <HelloWorld v-slot="zyycc">{{zyycc}}</HelloWorld> </div> </template> <script setup> import HelloWorld from './components/HelloWorld.vue' import { ref } from 'vue' </script> <style scoped> </style>