vue3官网
尚硅谷视频
黑马视频
1.vue3
import { createApp } from 'vue' // 引入createApp方法
import App from './App.vue' // 引入App根组件
createApp(App).mount('#app') // 创建一个 Vue 实例,并将其挂载到 id 为 "app" 的 DOM 元素上
2.vue2
import Vue from 'vue' // 引入vue
import App from './App.vue' // 引入App根组件
// 创建一个 Vue 实例,并在创建时指定根组件的渲染函数,然后将该实例挂载到 id 为 "app" 的 DOM 元素上
new Vue({
render: h => h(App),
}).$mount('#app')
3.render函数
render: function (createElement) {
return createElement(App);
}
↓ 对象的增强写法
render (createElement) {
return createElement(App);
}
↓ createElement 简写成 h
render (h){
return h(App);
}
↓ 箭头函数
render: h => h(App);
createElement 函数是 Vue.js 的渲染函数中常用的一个函数,用于创建虚拟 DOM 元素,要有return返回值。
/ createElement 函数的第一个参数:标签名或组件选项,可以是 HTML 标签名字符串,表示创建普通的 HTML 元素,也可以是组件的选项对象,表示创建一个组件实例。/
import App from './App'
new Vue({
el: '#app',
render: h => h(App)
})
↓ 等价与
import App from './App'
new Vue({
el: '#app',
template: ' ',
components: {
App
}
})
上面两种的效果是一样的,可以看出 h(App)函数 的作用是:使用App作为这个Vue实例的template,同时一并了注册App组件;
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<div> hello vue3 </div>
</template>
Vue3 引入组件后,可以再模板中直接使用
<template>
<ReactiveDemo />
<ComputedDemo />
</template>
<script setup>
import ReactiveDemo from '@/components/ReactiveDemo.vue'
import ComputedDemo from '@/components/ComputedDemo.vue'
</script>
1.vue2 选项式API(Options API)
<script>
export default {
// data() 返回的属性将会成为响应式的状态,并且暴露在this上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
,它们可以在模板中作为事件监听器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
mounted() {
console.log('通常项目中初始化在这里调接口')
}
}
</script>
2.vue3 组合式API(Composition API)
<script>
import { ref, computed, onMounted } from 'vue'
export default {
setup () {
// 定义变量
const count = ref(1)
// 定义计算属性
const doubleCount = computed(() => {
return count.value * 2
})
// 定义方法
const changeCount = () => {
count.value = 100
}
// 生命周期函数
onMounted(() => {
console.log(count.value, 'onMounted')
})
// 统一将变量和方法等暴露出去
return {
count, doubleCount, changeCount
}
},
}
</script>
中的导入和顶层变量/函数都能够在模板中直接使用;<template>
<div id="app">
<p>名字:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>
<button @click="sayHello">弹框</button>
</p>
</div>
</template>
<script>
export default {
name: "App",
// setup是一个函数,有返回值
setup() {
// 定义数据
let name = "张三";
let age = 18;
// 定义方法
function sayHello() {
alert(`我是${name},我今年${age},你好啊!`); // 使用不需要用this,因为this是undefined,函数作用域;
}
/ setup函数的返回值是一个对象, 返回值可以直接在模板中使用,不需要点;/
return { name, age, sayHello }
}
};
</script>
setup是一个函数,有返回值,返回值是一个对象可以直接在模板中使用;
在setup函数中定义的东西,一定要return出去,才能被使用;
setup 中不能使用 this;
在beforcreatd前执行,
所以this是undefined,所以vue3中没有this;1.解构了 props 对象,解构出的变量将会丢失响应性,推荐通过 props.xxx 的形式来使用其中的 props。
export default {
props: {
title: String
},
setup(props) {
// 通过 props.xxx 的形式来使用其中的 props。
console.log(props.title)
}
}
2.必须解构 props 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么可以使用 toRefs() 和 toRef() 这两个工具函数。
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
contex上下文对象:
包含了attrs,slots,emit,expose;
emit可以触发自定义事件的执行从而完成子传父通信;
该上下文对象是非响应式的,可以安全地解构;
attrs:一个对象,父组件传递给过来的非 props 属性对象,即未在props中接收的属性。
slots:一个函数,用于访问组件的插槽内容。
emit:一个用于触发自定义事件的函数。
expose:一个用于暴露组件的公共 API 的函数。
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit),子传父通信;
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
contex上下文对象:
attrs:一个对象,父组件传递给过来的非 props 属性对象,即未在props中接收的属性。
slots:一个函数,用于访问组件的插槽内容。
emit:一个用于触发自定义事件的函数。
expose:一个用于暴露组件的公共 API 的函数。
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit),子传父通信;
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
expose
setup() {
// 定义要暴露的属性和方法
const myValue = ref(42);
const myMethod = () => {
console.log("Hello from myMethod!");
};
// 让组件实例处于`关闭状态`,即不向父组件暴露任何东西
expose()
// 让组件实例处于`开启状态`,可以有选择性的向父组件暴露,本组件的属性或方法
expose({
myMethod
});
}
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`计数器初始值为 ${count.value}。`)
})
</script>
- setup语法糖,不用在return变量和函数了,在模板中可以直接使用;
- script标签添加setup, 省略 export default{ };
作用:定义响应式的数据; (多用于基本数据类型)
const xxx = ref (数据)
模板中使用不需要 .value;
js操作需要 .value;
接收的数据可以是:基本类型、也可以是对象类型。
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的数据劫持。
对象类型的数据:内部通过reactive函数实现的。
<template>
<div id="app">
<p>名字:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>工作: {{job.type}}</p> //模板中使用不需要.value
<p>
<button @click="changeInfo">改变</button>
</p>
</div>
</template>
<script>
import { ref } from "vue"; // 引入ref函数
export default {
name: "App",
setup() {
// 定义基本数据类型
let name = ref("张三");
let age = ref(18);
// 定义引用数据类型
let job = ref({ type: "前端工程师" });
// 定义修改ref定义的响应式数据的方法
function changeInfo() {
// 基本数据类型
name.value = "李四";
age.value = 28;
// 引用数据类型
job.value.type = "后端工程师";
}
return { name, age, job, changeInfo};
},
};
</script>
定义一个对象或数组类型的响应式数据;
(基本类型不要用它,要用ref函数);语法:const xxx = reactive(源对象);
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
js和html中不需要 .value
<template>
<div id="app">
<p>名字:{{ person.name }}</p>
<p>工作: {{ person.job.type }}</p>
<p>
<button @click="changeInfo">改变</button>
</p>
</div>
</template>
<script>
import { reactive } from "vue"; // 引入reactive函数
export default {
name: "App",
setup() {
// 通过reactive函数定义对象或数组;
let person = reactive({
name: "张三",
job: {
type: "前端工程师",
},
});
// 定义方法
function changeInfo() {
// 修改不需要.value
person.name = "赵四";
person.job.type = "后端工程师";
}
return { person, changeInfo }
},
};
</script>
set方法的参数是要设置的新属性值;
<!-- 只读取计算属性 -->
<template>
<div id="app">
<p>姓 : <input type="text" v-model="person.xing" /> </p>
<p>名 : <input type="text" v-model="person.ming" /> </p>
<p>姓名: {{ fullName }}</p> // 使用计算属性
<p>姓名: {{ person.fullName }}</p> // 计算属性直接追加到person对象上使用
</div>
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "App",
setup() {
// reactive函数;
let person = reactive({
xing: "",
ming: "",
});
// 计算属性 fullName, computed函数的参数可以是一个函数, 依靠返回值
let fullName = computed(() => {
return person.xing + person.ming;
});
/直接把计算属性追加到person响应式对象上,直接返回person对象 person.fullName = computed(() => person.xing + person.ming ) /
// 返回值
return {
person,
fullName, // 返回计算属性
};
},
};
</script>
修改计算属性
<!-- 获取和修改计算属性 -->
<p>姓名: <input type="text" v-model="fullName" /></p>
import { reactive, computed } from "vue"; //引入computed函数
// 完整的计算属性, 获取和修改, get+set
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){ // value是input输入框的新值
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
watch(参数1,参数2,参数3);
一个箭头函数,依靠返回值,精确监视对象某个属性
;监视reactive定义的响应式数据,1. 不能获取oldValue,oldValue和newValue一致;2. 强制开启了deep:true,设置deep:false无效;
/ 情况一:监视ref定义的一个响应式数据 /
watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue, oldValue)
}, { immediate:true })
/ 情况二:监视多个ref定义的响应式数据 /
监视的数据都放到一个数组中;newVal和oldVal返回的也是数组形式。
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/ 情况三:监视reactive定义的响应式数据 /
1. 无法正确获得oldValue;2. oldValue和newValue一致;3.
强制开启了深度监视deep:true;4. 设置deep:false也无效;
watch(person, (newValue, oldValue)=>{
console.log('person变化了', newValue, oldValue) // newValue === oldValue; true
}, { immediate:true, deep:false }) //此处的deep配置不再奏效
/ 情况四:监视reactive定义的响应式数据中的某一个属性 /
1. 箭头函数,依靠返回值;
2. 可以获取新值,旧值; 3. 设置deep有效;
watch(() => person.job, (newValue,oldValue) => {
console.log('person的job变化了', newValue, oldValue)
}, { immediate:true, deep:true })
/ 情况五:监视reactive定义的响应式数据中的多个属性 /
1. 放到一个数组中, 箭头函数依靠返回值;
2. 可以获取新值,旧值; 3. 设置deep有效; 4.newVal和oldVal返回的也是数组形式。
watch([ () => person.job, () => person.name ], (newValue,oldValue) => {
console.log('person的job变化了', newValue,oldVal ue)
}, { immediate:true,deep:true })
/ 特殊情况:监视的是reactive定义的对象中的某个对象,即对象嵌套的比较深 /
1. 开启deep:true才能监测到变化;2. 可以设置deep:false; 3. 无法获取oldVal,newVal和oldValue一致;
const person = reactive({
name: '张三',
age: 18,
obj: {
salaray: {
money: 10000
}
}
})
watch(() => person.job, (newValue,oldValue) => {
console.log('person的job变化了', newValue, oldValue) // newValue === oldValue; true
}, { deep:true }) // 设置deep:tue或false有效;
watchEffect ( 监视的回调函数 )
watch:既要指明监视的属性,也要指明监视的回调。
watchEffect:不用指明监视哪个属性,监视的回调函数中用到哪个属性,那就监视哪个属性。
watchEffect有点类似于computed:
但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value // 用到了sum,就监视person;
const x2 = person.age // 用到了person,就监视person;
console.log('watchEffect配置的回调执行了')
})
父子生命周期执行顺序:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
Vue3生命周期
beforeCreate -> setup()
created -> setup() 开始创建组件之前,在beforeCreate和created之前执行,创建的是data和method。
beforeMount -> onBeforeMount() 组件挂载到节点上之前执行的函数。
mounted -> onMounted() 组件挂载完成后执行的函数。
beforeUpdate -> onBeforeUpdate() 组件更新之前执行的函数。
updated -> onUpdated() 组件更新完成之后执行的函数。
beforeUnmount -> onBeforeUnmount() 组件卸载之前执行的函数。
unmounted -> onUnmounted() 组件卸载完成后执行的函数
keepalive专属 - 被包含在<keep-alive>中的组件,因为生命周期函数不再执行,会多出两个生命周期钩子函数。
activated -> onActivated() 被激活时执行,刚进入页面时执行。
deactivated -> onDeactivated() 失活时执行,比如从 A 组件,切换到 B 组件,A 组件消失时执行。
捕获异常 - 用于捕获组件中未处理的 JavaScript 错误
errorCaptured -> onErrorCaptured(error,vm) 当捕获一个来自子孙组件的异常时激活钩子函数。
当组件中出现 JavaScript 错误时,这个错误会沿着组件树向上冒泡直到根组件,如果任何一个组件中定义了 onErrorCaptured 钩子函数,则会调用该函数来捕获错误。
这个钩子函数接收两个参数:error 和 vm。
error: 表示捕获到的 JavaScript 错误对象。
vm: 传递给该钩子函数的组件实例对象,可以通过它来访问到组件的状态和属性。
在 DOM 更新之后执行回调函数 - 作用是等待当前 DOM 更新队列中的所有同步和异步更新完成后,再执行传入的回调函数。
1.等待当前 DOM 更新队列中的所有同步和异步更新完成后执行回调函数。
2.用于确保在 DOM 更新之后执行操作,例如获取更新后的 DOM 元素。
await nextTick()
DOM 更新后执行的操作xxx
toRef: 将一个响应式状态或其他可响应式对象转换为一个只读的 ref 对象。
toRef( 对象 , '属性名' )
const refObj = toRef( sourceObj, key )
toRefs( '对象' )
toRefs(person)
<template>
<div>
<p>person:{{ person }}</p>
<p>姓名: {{ name }}</p>
<p>年龄: {{ age }}</p>
<p>工资: {{ salary }}</p>
<button @click="name += '三'">姓名++</button>
<button @click="age++">年龄++</button>
<button @click="salary++">工资++</button>
</div>
</template>
<script>
import { reactive, toRef, toRefs } from "vue"; // 引入 toRef, toRefs函数
export default {
name: "App",
setup() {
let person = reactive({
name: "张三",
age: 18,
job: {
type: "前端程序员",
salary: 15,
},
});
return {
person, // 响应式的person对象
name: toRef(person, "name"), // 将响应式对象中的某个属性单独提供给外部使用时。
age: toRef(person, "age"),
salary: toRef(person.job, "salary"),
/ ...toRefs(person) 解构toRefs函数处理的数据,name,age,job;把整个响应式的person对象解构出来的属性,都暴露给外边使用,也不会失去响应式; /
};
},
};
</script>
shallow:浅的,浅层次的;
let person = reactive({
name: "张三",
age: 18,
job: {
type: "前端程序员",
salary: 15,
},
});
person = readonly(person) // 整个person对象只读
person = shallowReadonly(person) // 只有person对象第一层只读name,age;深层次job对象中的数据可以修改
raw:未加工的,原始的;mark:标记,记号;
toRaw:
markRaw:
custom:自定义;track:追踪;trigger:触发;delay:延迟;
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
import { customRef } from 'vue'
const myCustomRef = customRef((track, trigger) => {
// 定义初始值
let value = 0
// 定义增加初始值的方法
const increment = () => {
value++
trigger() // 触发更新
}
// 定义减少初始值的方法
const decrement = () => {
value--
trigger() // 触发更新
}
// 返回get和set方法,返回increment方法和decrement方法;
return {
get() {
track() // 追踪依赖的数据,value;
return value
},
set(newValue) {
value = newValue
trigger() // 触发更新
},
increment,
decrement
}
})
const count = myCustomRef.value
console.log(count) // 0
myCustomRef.increment()
console.log(count) // 1
myCustomRef.decrement()
console.log(count) // 0
myCustomRef.value = 5
console.log(count) // 5
实现祖孙组件间的通信;
provide 提供数据;
==> provide( “数据名”, 传递的数据);inject 接受数据;
⇒ inject( “数据名” );祖先组件中:
import { provide } from 'vue
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
......
}
后代组件中:
import { inject } from 'vue
setup(props,context){
......
const car = inject('car')
return {car}
......
}
reactive
或者 readonly
方法创建的代理;fragment:碎片,片段;
在Vue3中:组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中;
fragment:传送;
元素之下。<button @click="isShow = true">点击弹窗</button>
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
suspense:悬念;
组件有两个插槽:#default 和 #fallback。方法1: 通过defineAsyncComponent(() => import('路径'));
import {defineAsyncComponent} from 'vue' // 引入定义异步组件的函数
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
import() // 异步引入
方法2:使用Suspense包裹组件,通过插槽,配置好 #default 与 #fallback;
<template>
<Suspense>
<template #default>
<h1>这是异步组件的内容...</h1>
</template>
<template #fallback>
<h1>异步组件正在加载中...</h1>
</template>
</Suspense>
</template>
#default 插槽用于显示异步组件加载完成后的内容,即 <h1>这是异步组件的内容</h1>。
#fallback 插槽用于显示异步组件加载过程中的占位符,即 <h1>异步组件正在加载中...</h1>。
/ 当异步组件加载过程中,<Suspense> 组件会显示 #fallback 插槽中的内容,待异步组件加载完成后,会切换到显示 #default 插槽中的内容。/
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: ''
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}