一. 创建Vue3.0工程
1. 使用vue-cli创建
vue create vue_test
2. 使用vite创建 (暂时了解)
-
vite —— 新一代前端构建工具 https://vitejs.cn
-
创建工程:
npm init vite-app <project-name> cd <project-name> npm install npm run dev
二. 常用Composition API(组合式API)
1. 学习setup
-
Vue3.0中一个新的配置项,值为一个函数。
-
组件中所有用到的数据、方法等,均配置在setup中。
-
setup函数的两种返回值:
-
若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。
<template> <h1>我是App组件</h1> <p>姓名: {{name}}</p> <p>年龄: {{age}}</p> <button @click="sayHello">vue3中的sayHello</button> </template> <script> export default { name: 'App', setup() { // 不是响应式的数据 let name = 'Tom'; let age = 2; function sayHello() { alert(`我是${name}, 今年${age}岁!`); } // 返回一个对象, 模板中可以直接使用 return { name, age, sayHello } } } </script>
-
若返回一个渲染函数:则可以自定义渲染内容。(了解)
<template> <h1>我是App组件</h1> </template> <script> // 需要引入h import {h} from 'vue'; export default { name: 'App', setup() { // 返回一个渲染函数, 会使模板里写的实效, 只显示下边的h1标签 return () => h('h1', '这是渲染函数') } } </script>
-
-
注意点:
-
尽量不要与Vue2配置混用 (2可以访问3的setup中的属性和方法;3不能访问2;如果有重名3优先)
<template> <h1>我是App组件</h1> <p>姓名: {{name}}</p> <p>年龄: {{age}}</p> <p>性别: {{sex}}</p> <p>a的值: {{a}}</p><!--显示的是200--> <button @click="sayHello">vue3中的sayHello</button> <br> <br> <button @click="sayWelcome">vue2中的sayWelcome</button> <br> <br> <button @click="test1">测试vue2能否读取vue3的数据</button> <br> <br> <button @click="test2">测试vue3能否读取vue2的数据</button> </template> <script> export default { name: 'App', data() { return { sex: '男', a: 100 } }, methods:{ sayWelcome() { alert('hello~ i am vue2') }, test1() { console.log(this.sex, this.name, this.age, this.sayHello); // 男 Tom 2 ƒ sayHello() this.sayHello(); } }, setup() { // 不是响应式的数据 let name = 'Tom'; let age = 2; let a = 200; function sayHello() { alert(`我是${name}, 今年${age}岁!`); } function test2() { console.log(this.sex, this.sayWelcome, this.name, this.age); // undefined undefined 'Tom' 2 } // 返回一个对象, 模板中可以直接使用 return { name, age, a, sayHello, test2 } } } </script>
-
setup最好不要是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。
-
2. ref函数
-
作用:定义一个响应式数据
-
语法:
const/let xxx = ref(initValue);
-
创建一个包含响应式数据的引用对象(reference对象)
-
js中操作数据:
xxx.value
-
模板中读取数据直接写:
<div> {{xxx}} </div>
-
-
接受数据:
-
基本类型数据:响应式靠
Object.defineProperty()
的get
与set
完成 -
引用类型数据:内部“求助”了Vue3中的一个新函数——
reactive
函数
-
<template>
<h1>我是App组件</h1>
<p>姓名: {{name}}</p>
<p>年龄: {{age}}</p>
<h3>工作: {{info.job}}</h3>
<h3>成就: {{info.achievement}}</h3>
<button @click="changeVal">修改值</button>
</template>
<script>
import {ref} from 'vue';
export default {
name: 'App',
setup() {
// ref函数实现响应式
let name = ref('Tom');
let age = ref(2);
let info = ref({
job: 'eat mouse',
achievement: 'zero mouse'
})
// 方法
function changeVal() {
console.log(name, age); // 包装在引用对象中RefImpl
name.value = 'Jerry';
age.value = 1;
console.log(info.value); // 包装在Proxy中
info.value.job = 'steal cake';
info.value.achievement = 'countless cakes'
}
return {
name,
age,
info,
changeVal
}
}
}
</script>
3. reactive函数
-
作用:定义一个对象类型的响应式数据(基本类型无法使用,用ref函数)
-
语法:
consst 代理对象 = reactive(源对象)
-
接受一个对象(数组)
-
返回一个代理对象(Proxy的实例对象,简称proxy对象)
-
-
其他
-
reactive定义的响应式数据是“深层次的”
-
内部基于ES6的
Proxy
实现,通过代理对象操作源对象内部数据进行操作
-
<template>
<h1>我是App组件</h1>
<p>姓名: {{animal.name}}</p>
<p>年龄: {{animal.age}}</p>
<h3>工作: {{animal.info.job}}</h3>
<h3>成就: {{animal.info.achievement}}</h3>
<h3>测试c: {{animal.info.a.b.c}}</h3>
<button @click="changeVal">修改值</button>
</template>
<script>
import {reactive} from 'vue';
export default {
name: 'App',
setup() {
// reactive实现对象的响应式
let animal = reactive({
name: 'Tom',
age: 2,
info: {
job: 'eat mouse',
achievement: 'zero mouse',
a: {
b: {
c: 400
}
}
},
hobby: ['play', 'eat', 'sleep']
})
// 方法
function changeVal() {
console.log(animal);
animal.name = 'Jerry';
animal.age = 1;
animal.info.job = 'steal cake';
animal.info.achievement = 'countless cakes';
animal.info.a.b.c = 666;
}
return {
animal,
changeVal
}
}
}
</script>
4. Vue3.0 中的响应式原理
(1)Vue2.0 中的响应式原理
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) -
数组类型:通过重写更新数组的一系列方法实现拦截(对数组的变更方法进行了包裹)
Object.defineProperty(data, 'count', { get() {}, set() {} })
-
-
存在问题:
-
新增属性、删除属性,界面不会自动更新。(解决:利用
this.$set() this.$delete()
或Vue.set() Vue.delete()
实现) -
直接通过下标改数组,界面不会自动更新。(解决:利用
this.$set()
或数组的方法XX.splice()
实现)
-
<template>
<div>
<h1>Vue2 响应式原理</h1>
<h2 v-show="cat.name">姓名: {{cat.name}}</h2>
<h2>年龄: {{cat.age}}</h2>
<h2 v-show="cat.sex">性别: {{cat.sex}}</h2>
<h2>爱好: {{hobby}}</h2>
<button @click="addValue">增加对象属性</button>
<button @click="deleteValue">删除对象属性</button>
<button @click="modifyValue">修改数组中的值</button>
</div>
</template>
<script>
import Vue from 'vue'
export default {
data() {
return {
cat: {
name: 'Tom',
age: 2,
},
hobby: ['eat', 'sleep']
}
},
methods: {
addValue() {
// this.cat.sex = 'boy'; // 无效
// this.$set(this.cat, 'sex', 'boy')
Vue.set(this.cat, 'sex', 'boy')
},
deleteValue() {
// delete this.cat.name // 无效
// this.$delete(this.cat, 'name')
Vue.delete(this.cat, 'name')
},
modifyValue() {
// this.hobby[1] = 'play' // 无效
// this.$set(this.hobby, 1, 'play')
this.hobby.splice(1, 1, 'play');
}
}
}
</script>
// vue2中的响应式
let person = {
name: 'Tom',
age: 20
}
let p = {};
let perProps = Object.keys(person);
// console.log(perProps); 拿到包含key值的数组
perProps.forEach(item => {
Object.defineProperty(p, item, {
get() {
console.log('获取了' + item);
return person[item]
},
set(val) {
console.log('修改了' + item);
person[item] = val
}
})
})
(2)Vue3.0 中的响应式
-
实现原理:
-
通过Proxy代理:拦截对象中任意属性的变化(读、增、改、删)
-
通过Reflect反射:对源对象的属性进行操作
-
let person = {
name: 'Tom',
age: 20
}
let p = new Proxy(person, {
get(target, propsName) {
console.log(`获取了p里的某个属性${propsName}`);
// return target[propsName]
return Reflect.get(target, propsName)
},
set(target, propsName, newVal) {
console.log(`修改或增加了p里的某个属性${propsName}`);
// target[propsName] = newVal
Reflect.set(target, propsName, newVal)
},
deleteProperty(target, propsName) {
console.log(`删除了p里的某个属性${propsName}`);
// return delete target[propsName]
return Reflect.deleteProperty(target, propsName)
}
})
5. reactive和ref对比
reactive | ref | |
---|---|---|
定义数据 | 引用类型数据 | 基本数据类型,也可以定义对象,内部自动通过reactive转为代理对象 |
实现原理 | Proxy实现响应式,Reflect操作源对象数据 | Object.defineProperty() 的get 和set 实现响应式 |
使用方式 | 操作数据不需要.value | 操作数据需要.value ,模板中不需要.value |
6. setup的参数
-
父组件
<template>
<Demo @hello="getHello" msg="信息" name="名字">
<span>Tom cat</span>
</Demo>
</template>
<script>
import Demo from './component/Demo.vue'
export default {
name: 'App',
components: {Demo},
setup() {
function getHello(val) {
alert(`app接受到了demo传的值: ${val}`)
}
return {
getHello
}
}
}
</script>
-
子组件
<template>
<button @click="sendValue">点击demo给app传值</button>
</template>
<script>
export default {
name: 'Demo',
props: ['msg', 'name'],
emits: ['hello'], // 不加也不报错
// beforeCreate() {
// console.log('----beforeCreate----');
// },
setup(props, context) {
// console.log('----setup----', this); // 比beforeCreate先执行, 且this为undefined
console.log(props); // Proxy {msg: '信息', name: '名字'} 从父组件传来的值
// console.log(context.attrs); // 当没有props接受传递的值, 就能拿到值
console.log(context.slots);
function sendValue() {
context.emit('hello', 666)
}
return {
sendValue
}
}
};
</script>
-
setup执行的时机——
beforeCreate
前执行一次, this是undefined
-
setup的参数:
-
props:值为对象,仅包含显性声明的 prop(无论是否传递值,没有传递的值为
undefined
) -
context:上下文对象
-
attrs:值为对象,组件外部传递且没有在props配置总声明的属性
-
slots:收到的插槽内容
-
emit:分发自定义事件的函数
-
-
7. 计算属性
<template>
姓: <input type="text" v-model="person.firstname">
<br>
名: <input type="text" v-model="person.lastname">
<br>
全名: <input type="text" v-model="person.fullname">
<br>
<h3>全名: {{person.fullname}}</h3>
</template>
<script>
import { reactive, computed } from 'vue';
export default {
name: 'App',
setup() {
let person = reactive({
firstname: '张',
lastname: '三'
})
// 计算属性 ---- 简写, 只能读取
// person.fullname = computed(() => {
// return person.firstname + person.lastname;
// })
// 计算属性 ---- 全写, 能读能改
person.fullname = computed({
get() {
return person.firstname + '-' + person.lastname
},
set(val) {
const disName = val.split('-');
person.firstname = disName[0];
person.lastname = disName[1];
}
})
return {
person
}
}
}
</script>
8. watch监视
-
小坑
-
监视reactive定义的响应式数据时,
oldValue
无法正确获取, 强制开启了深度监视(deep配置失效) -
监视reactive定义的响应式数据中某个属性时,deep配置有效
-
-
用
ref
定义的基本数据不需要.value
, 因为这样获取到的值是一个具体的值(字符串/数字等) -
用
ref
定义的对象, 要去监视需要.value
, 否则无法监控到对象里面的值得变化, 或者去配置深度监视{deep: true}
<template>
<p>求和: {{sum}}</p>
<button @click="sum++">点击加1</button>
<hr>
<p>科目: {{subject}}</p>
<button @click="subject += '!'">点击改变</button>
<hr>
<p>姓名: {{person.name}}</p>
<p>年龄: {{person.age}}</p>
<button @click="person.name += '~'">改姓名</button>
<button @click="person.age ++">改年龄</button>
<hr>
<h4>薪水: {{person.job.j.salary}}K</h4>
<button @click="person.job.j.salary ++">涨薪</button>
</template>
<script>
import { reactive, ref, watch } from 'vue';
export default {
name: 'App',
setup() {
let sum = ref(0);
let subject = ref('数学');
let person = reactive({
name: 'Tom',
age: 18,
job: {
j: {
salary: 20
}
}
})
// 监视1, 单个简单类型数据
/* watch(sum, (newVal, oldVal) => {
console.log('sum变了', newVal, oldVal);
}, {immediate: true}) */
// 监视2, 多个简单类型数据
// watch([sum, subject], (newVal, oldVal) => {
// console.log('sum 和 subject变了', newVal, oldVal);
// })
// 监视3, 监视reactive数据;
// watch(person, (newVal, oldVal) => {
// console.log('person改变了', newVal, oldVal); // oldVal失效
// })
// 监视4, 监视对象的某个属性(简单类型)
// watch(() => person.name, (newVal, oldVal) => {
// console.log('person里的名字变了', newVal, oldVal);
// })
// 监视5, 监视对象的多个属性(简单类型)
// watch([() => person.name, () => person.age], (newVal, oldVal) => {
// console.log('person里的name和age变了', newVal, oldVal);
// })
// 监视6, 监视对象里的属性(非简单类型)
watch(() => person.job, (newVal, oldVal) => {
console.log('person里的job变了', newVal.j.salary, oldVal.j.salary);
}, {deep: true})
watch(person, (newVal, oldVal) => {
console.log('person变了', newVal, oldVal);
}, {deep: true})
return {
sum,
subject,
person
}
}
}
</script>
-
watchEffect函数
-
不指明监视那个属性, 监视的回调中用到哪个属性, 就监视那个属性
-
类似computed
-
computed注重的计算出来的值, 必须写返回值
-
watchEffect更注重的是过程(回调函数的函数体), 不写返回值
-
-
<template>
<p>求和: {{sum}}</p>
<button @click="sum++">点击加1</button>
<hr>
<p>科目: {{subject}}</p>
<button @click="subject += '!'">点击改变</button>
<hr>
<p>姓名: {{person.name}}</p>
<p>年龄: {{person.age}}</p>
<button @click="person.name += '~'">改姓名</button>
<button @click="person.age ++">改年龄</button>
<hr>
<h4>薪水: {{person.job.j.salary}}K</h4>
<button @click="person.job.j.salary ++">涨薪</button>
</template>
<script>
import { ref, reactive, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
let sum = ref(0);
let subject = ref('数学');
let person = reactive({
name: 'Tom',
age: 18,
job: {
j: {
salary: 20
}
}
})
watchEffect(() => {
const s1 = sum.value;
const s2 = person.name;
const s3 = person.job.j.salary
console.log('有值变化了');
})
return {
sum,
subject,
person
}
}
}
</script>
9. 生命周期
-
Vue3中可以继续使用Vue2的生命周期钩子, 最后两个名字变更,
-
beforDestroy
变为beforeUnmount
, -
destroyed
变为unmounted
-
-
Vue3中提供组合API形式的生命周期钩子
-
beforeCreate
===>setup()
-
created
===>setup()
-
beforeMount
===>onBeforeMount
-
mounted
===>onMounted
-
beforeUpdate
===>onBeforeUpdate
-
updated
===>onUpdated
-
beforeUnmount
===>onBeforeUnmount
-
unmounted
===>onunMounted
-
10. 自定义hook函数
-
本质是一个函数, 把setup函数中使用的Composition API进行封装
-
优点: 复用代码, 让setup中的逻辑更清楚
-
类似Vue2中的mixin
// hooks文件夹里的自定义hook函数--savePoint.js
import { onBeforeUnmount, onMounted, reactive } from 'vue';
export default function() {
// 保存鼠标点击的坐标点的数据
let point = reactive({
x: 0,
y: 0
})
// 事件方法
function savePoint(event) {
console.log(event.pageX, event.pageY);
point.x = event.pageX;
point.y = event.pageY;
}
// 挂载完成, 注册事件
onMounted(() => {
window.addEventListener('click', savePoint)
})
// 卸载前, 取消注册的事件
onBeforeUnmount(() => {
window.removeEventListener('click', savePoint)
})
return point
}
<template>
<p>求和: {{sum}}</p>
<button @click="sum++">点击加1</button>
<hr>
<h2>获取鼠标坐标点: ({{point.x}}, {{point.y}})</h2>
</template>
<script>
import { ref } from 'vue';
import savePoint from '../hooks/savePoint'
export default {
name: 'App',
setup() {
let sum = ref(0);
const point = savePoint();
return {
sum,
point
}
},
}
</script>
<style>
</style>
11. toRef
-
作用: 创建一个ref对象, 其value值指向另一个对象中的某个属性
-
语法:
const name = toRef(person, 'name')
-
应用: 将响应式对象中的某个属性单独提供给外部使用(也变成响应式的)
-
toRefs
: 可以批量创建多个ref对象,toRefs(person)
<template>
<h5>{{person}}</h5>
<p>姓名: {{name}}</p>
<p>年龄: {{age}}</p>
<h4>薪水: {{job.j.salary}}K</h4>
<hr>
<button @click="name += '~'">改姓名</button>
<button @click="age ++">改年龄</button>
<button @click="job.j.salary ++">涨薪</button>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: 'App',
setup() {
let person = reactive({
name: 'Tom',
age: 18,
job: {
j: {
salary: 20
}
}
})
// toRef 单个变为ref对象
// let name = toRef(person, 'name');
// let age = toRef(person, 'age');
// let salary = toRef(person.job.j, 'salary');
// console.log(salary);
// toRefs 可将整个对象里的所有属性都变成ref对象
let p = toRefs(person);
console.log(p.job.value.j.salary);// 20
return {
person,
...p
}
}
}
</script>
<style>
</style>