createApp
方法.mount()
每个 Vue 应用都是通过 createApp
函数创建一个新的 Vue应用实例
createApp()
:创建应用实例
mount
方法的应用实例对象。app
应用实例 ②重写app.mount
方法import { createApp } from 'vue'
import App from'./App.vue' //引入根组件
//const app = createApp({
/* 根组件选项 */
//})
const app = createApp(App)
app.mount('#app')//将ID为app的节点挂载到Vue上
/*
vue2 写法
import Vue from 'vue';
new Vue({
render: h => h(App)
}).$mount('#app')
*/
作用:通过Vue.use()
安装插件
Vue.use()
方法至少传入一个参数,该参数类型必须是Object或Function。
Vue.use()
执行时相当于执行install方法,同一个插件多次调用,该插件只会被安装一次。
1.上述app是Vue的一个实例,app.use()用法等同于Vue.use、
2.第一个参数是插件本身,可选的第二个参数是要传递给插件的选项。
setup()
用于配合组合式API,为用户提供一个地方,用于建立组合逻辑、创建响应式数据、创建通用函数、注册声明周期钩子等能力。
render
函数(props,context)
props
,这个props
指向的是外部的props
。如果没有定义props选项
,setup
中的第一个参数将为undifined
。context
,context作为上下文取代this,但是context
中只有emit
,attrs
,和slots
。说明
setup函数
只会被挂载一次。在beforeCreate
之前执行一次,this
是undefined
(setup调用时机是在props解析完毕,组件实例创建之前执行),所以setup
中无法获取组件的实例。//返回一个模板
<template>
{{name}}
{{say()}}
</template>
<script>
export default {
name: 'App',
setup() {//这里定义的数据不是响应式数据
//数据
let name="ranan";
//方法
function say() {
console.log(`hello${name}`);
}
return {name,say}
}
}
</script>
//返回一个渲染函数
<template></template>
<script>
import {h} from 'vue' //createElement函数
export default {
name: 'App',
setup() {
//渲染函数要把h函数调用的返回值返回
//return ()=>{return h('h1','ranan')}
return ()=> h('h1','ranan')
}
}
</script>
Vue3.2 (vue2.7也支持)中只需要在 script
标签上加上 setup
属性
说明
1.组件在编译的过程中代码运行的上下文是在 setup()
函数中
2.无需 return
,在template
可直接使用。
//语法糖
<script setup="props, context" lang="ts">
context.attrs
context.slots
context.emit
<script>
//同时支持结构语法
<script setup="props, { emit }" lang="ts">
<script>
3.在 script setup
中,引入的组件可以直接使用,不用通过components
进行注册,并且无法指定当前组件的名字,会自动以文件名为主,也就是不用再写name属性了。
<template>
<HelloWorld />
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
</script>
方法1:修改文件名,文件名=组件名
方法2:新建一个与script setup同级的script标签,在该标签内抛出name
<script setup lang="ts">
</script>
<script lang="ts">
export default {
name: 'app-viewer'
}
方法3:使用插件unplugin-vue-define-options
插件安装方法
ref()
使数据具有响应式shallowRef()
只处理基本数据类型的响应式, 不进行对象的响应式处理。reactive()
定义的深层次的对象响应式,内部基于Proxy
实现,通过代理对象操作源对象内部数据。shallowReactive()
创建一个浅层次响应对象(一层响应)ref()
使数据具有响应式,将值包装成RefImpl
引用对象(Ref
对象)返回
创建一个对象,对象的value属性设置为传入的参数,然后对对象做响应式处理,最后返回响应式对象
defineProperty
中的getter
,setter
属性名.value
获取,但在模板中可以直接使用属性名(自动解包),模板中会自动属性名.value
<script setup lang="ts">
import {ref} from 'vue'
let count = ref(0) //生成一个0的响应式数据
console.log(count.value) //RefImpl{value:0....}
console.log(count) //0
count=10 //对0重新赋值,现在count不再是响应式数据
</script>
<template>
<div>{{count }}</div>
</template>
const 返回值obj = ref(参数对象)
返回值obj本身还是RefImpl对象,但是obj.value
返回的是对参数对象进行Proxy
代理的对象,通过对代理对象的操作间接操作源对象。
<script setup lang="ts">
import {ref} from 'vue'
/*
count是响应式的,name也是响应式的
const tmp = {name:ref("ranran")] name是响应式的,tmp不是响应式的
*/
const count = ref({name:"ranran"})
console.log(count) //返回一个RefImpl对象,对象的value值为传入的参数{value:{name:"ranran"}}
console.log(count.value) //Proxy(Object) {name: 'ranran'}
console.log(count.name) //注意这个是访问不到的,因为ref函数是创建一个对象,对象的value属性设置为传入的参数,然后对对象做响应式处理,最后返回响应式对象
</script>
<template>
<div>{{count }}</div>
</template>
template
标签里的自动解包问题:自动解包只会解包顶层属性顶层属性.value.xxx
const obj = ref({
name:"ranran"
})
const obj2 = {
name:ref("biubiu")
}
</script>
<template>
<div>
//自动解包obj.value.name
<div>{{obj.name }}</div>
//obj2不是响应式数据,所以没有自动解包。错误写法
<div>{{obj2.name }}</div>
//正确写法
<div>{{obj2.name.value}}</div>
</div>
</template>
function ref(value) {
return createRef(value, false); //创建ref对象,第一个参数时需要响应的数据,第二个参数是是否浅拷贝
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl {
constructor(value, _shallow) {
this._shallow = _shallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = _shallow ? value : toRaw(value);
this._value = _shallow ? value : convert(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : convert(newVal);
triggerRefValue(this, newVal);
}
}
}
如果参数是一个ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val
的语法糖函数。
使用场景:当一个值可能是ref或者是ref之外的值
const 代理对象 = reactive(源对象)
:定义一个对象的响应式代理,接受一个对象(数组),返回一个Proxy的实例对象。
reactive()
定义的响应式数据是深层次的,内部基于Proxy
实现,通过代理对象操作源对象内部数据。shallowReactive()
创建一个浅层次响应对象(一层响应)<script setup lang="ts">
import {reactive} from 'vue'
const stu = reactive({name:"ranran"})
console.log(stu)
/*
{
"name": "ranran"
}
*/
</script>
作用: toRef(响应式对象,该对象的属性)
创建一个ref对象
,该ref对象
的value
值指向参数对象中的某个属性(ref对象的value值改变,参数对象中的该属性也会改变,反之也会改变)
说明: 本质是引用,与原始数据有关联
应用场景: 要将响应式对象中的某个属性单独提供给外部使用
说明
toRef返回值
和对象 Object
两者保持引用关系(一个变化另外一个也会变化)toRefs(obj)
:返回一个和参数一致的普通对象,只不过属性的值都变成了ref对象
说明
toRefs
对于新增的属性不会生效 return{
...toRefs(person)
}
1.
ref
的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
toRef、toRefs
的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
2.toRef、toRefs
不创造响应式,而是延续响应式。
当一个数据发生变化时,effect
函数自动重新执行,我们就称该数据是响应式的。(响应式数据发生变化时effect()
会自动执行)
副作用函数effect()
的执行会直接或间接影响其他函数的执行。
//假设obj是一个响应式数据
const obj = {text:'xxx'}
function effect(){//effect函数
console.log(obj.text);
}
//修改响应式数据
obj.text = 'yyy'
//手动模拟
effect()//响应式数据发生变化,自动执行effect()
本质上是通过 Proxy 代理了数据对象的读写
track()
:track()
收集依赖,对象的每个属性都有自己的dep
,将所有依赖于该属性的effect函数
都收集起来,放在dep
里。trigger()
:trigger()
通知依赖。将依赖搜集起来之后,只要变量一改变,就执行trigger()
通知dep
里所有依赖该变量的effect()
执行,实现依赖变量的更新。effect函数
都收集起来,放在dep
里。Map
来存储各个属性和dep的对应关系,key为属性名,value为该属性的dep(使用Set来存储)。WeakMap
来存储多个对象的Map
Proxy-Reflect
实现自动收集和触发依赖track-trigger函数
//reactive 将对象变成响应式的
function reactive(target) {
const handler = {
get(target, key, receiver) {
track(receiver, key) // 访问时收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
trigger(receiver, key) // 设值时自动通知更新
}
}
return new Proxy(target, handler)
}
reactive | ref | |
---|---|---|
定义数据 | 对象类型数据 | 基本类型数据 如果定义对象,.value会借住reactive将对象转为代理对象 |
原理 | Proxy实现响应式(数据代理) 通过Reflect操作源对象内部的属性 |
通过Object.defineProperty()的get和set来实现响应式(数据劫持) |
使用 | 不需要使用.value | 操作数据需要.value,模板中自动解包读取不需要.value |
computed属性是Vue3中的一个响应式
计算属性(返回一个ref对象),根据其他响应式数据的变化(不会检测非响应式数据的的更新)而自动更新其自身的值。
import {computed} from 'vue'
setup(){
//计算属性fullName -简写内部是函数形式,只符合被读取的情况
let fullName = computed(()=>{
return person
})
//计算属性-完整写法
let fullName = computed({
get(){},
set(val){}
})
}
computed属性的原理是使用了一个getter函数和一个setter函数来实现,computed的依赖数据(监听变化的数据)需要是响应式的
watch()
用来侦听特定的数据源,并在回调函数中执行副作用。默认情况仅在侦听的源数据变更时才执行回调。
watch(data,function(),[option])
ref
对象getter
函数:可以简单的理解为获取数据的一个函数,该函数是一个返回值的操作reactive
定义的属性,会把deep:true
强制开启说明
handler(newVal,oldVal)
函数调用,初始化时不调用(可以设置)//写法1
new Vue({
el:'#root',
data:{
firstName:'ran',
lastName:'an'
},
watch:{
isHot:{//固定配置
immediate:false; //默认false,初始化时调用handler函数
//当监视的属性发生变化时,handler调用
handler(newVal,oldVal){}
}
}
});
//写法2
vm.$watch('isHot',{配置信息})
//简写
watch:{
isHot(newVal,oldVal){}
}
vm.$watch('isHot',function(newVal,oldVal){//handler函数})
import {ref,computed,watch} from 'vue'
const message = ref("ranran");
const newMessage = computed(() => {
return message.value;
});
watch(newMessage, (newValue, oldValue) => {
});
const x1 = ref(12);
const x2 = ref(13);
watch(
() => x1.value + x2.value,//类似计算属性
(newValue, oldValue) => {
}
);
监听reactive
定义的属性,会强制开启deep:true
深度监听(自动创建一个深层侦听器,deep配置无效),该响应式对象里面的任何属性发生变化,都会触发监听函数中的回调函数。
无法正确的获取oldValue,oldValue和newValue的值一样了。因为对象是引用类型,即使里面的属性发生了变化,但是对象的地址没有发生变化,oldValue和newValue指向了同一个地址。
import {reactive,watch} from 'vue'
let person= reactive({
name:"ranna",
age:18,
job:{
j1:{
salary:20
}
}
})
watch(person,(newValue,oldValue)=>{
})
1.watch 不能直接监听响应式对象的属性,相当于你直接向 watch
传递了一个非响应式的数字,然而 watch
只能监听响应式数据。
//错误写法
const number = reactive({ count: 0 });
const countAdd = () => {
number.count++;
};
watch(number.count, (newValue, oldValue) => {
});
解决办法: 使用 getter
函数的形式,采用这种情况可以获取到oldValue
watch(
() => number.count,//返回的是基本数据类型
(newValue, oldValue) => {
}
);
2.特殊情况:Proxy是自动深度,Object按需深度
如果我们是使用的 getter()
返回响应式对象的形式,那么响应式对象的属性值发生变化,是不会触发 watch 的回调函数的。
//自动开启deep
watch(person.job,(newValue,oldValue)=>{ })
watch(()=>person.job,(newValue,oldValue)=>{},{deep:true})//返回的是一个obj,需要手动开启deep
推荐getter()
监听响应式对象中的某一个属性
watch
还可以监听数组,前提是这个数组内部含有响应式数据。
let sum = ref(0)
let msg= ref("hello")
watch([sum,msg],(newvalue,oldValue)=>{
//此时newvalue和oldValue都是数组,里面的顺序和第一个参数的顺序一致
})
监听整个props
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
listData: {
type: Array,
default: [],
}
});
const childList = ref([])
watch(props, (nweProps)=>{
childList.value = nweProps.listData
})
</script>
监听props中的具体属性
<script setup>
import { ref} from 'vue';
const appearState = ref(false);
const props = defineProps({
show: {
type: Boolean,
default: () => false,
},
});
watch(
() => props.show,
(newVal) => {
appearState.value = newVal;
},
);
watchEffect()
也是一个监听器,第一个参数是回调函数,第二个参数是配置对象,该函数初始化时就会执行(从无到有)。在回调函数中,自动监听响应属性,当回调函数里面的响应数据发生变化,回调函数就会立即执行。
//一上来就会执行
watchEffect(()=>{
})
监听器的回调函数中获取DOM,默认是获取更新前的DOM。
方法1:配置选项
想要在回调函数里面获取更新后的 DOM,只需要再给监听器多传递一个参数选项flush: 'post'
。
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
方法2:watchPostEffect
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
通常一个组件被销毁或者卸载后,监听器也会跟着停止。但一些特殊情况监听器依然存在,需要手动关闭,否则容易造成内存泄露。
//案例
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// 这个时候监听器没有与当前组件绑定,所以即使组件销毁了,监听器依然存在。
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
解决办法:利用一个变量接收监听器函数的返回值,返回值是一个关闭当前监听器的函数。
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
defineProps
和 defineEmits
都是只能在
中使用的编译器宏。他们不需要导入,且会随着
的处理过程一同被编译掉。defineProps
或defineEmits
要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。props是只读属性,子组件不可以修改传入的值。
vue2写法
//写法1:子组件接收值,简单接收
props:['name','sex','age']
//写法2:子组件接收值,设置传来值的类型
props:{
name:String,
sex:String,
age:Number
}
//写法3: 子组件接收值,完整写法
props:{
name:{
type:String,
required:true //是否必须
},
age:{
type:Number,//类型
default:99 //默认值
}
vue3写法
//第一种,简单接收
defineProps(["name"]);
//第二种设置接收的类型
defineProps({
page:Number
});
//第三种设置接收的类型和默认值
defineProps({
page:{
type:Number,
default:2
}
});
//第四种设置传来值的多种类型
defineProps:{
page:[String,Number]
}
withDefaults
编译器宏,可以给props
设置默认值
interface Form {
age: number
name:string
}
//没有办法设置默认值
const props = defineProps<{
msg: string
form?: Form
}>()
//使withDefaults设置默认值
const props = withDefaults(defineProps<{
msg: string
form?: Form
}>(),{
form: {
age: 1,
name: "vue",
}
}
)
()=>[]
如果直接采用{}和[],当多个使用该组件的父组件都没有传递props而使用默认值。假设其中一个父组件修改了默认的值(不推荐,会报警告),其他的父组件由于指向的是同一个内存中的引用地址,也会发生改变。
使用了函数的形式去返回,保证每次函数执行出来的都是返回一个新的对象。
规则
1.使用default定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用默认值。
2.没有定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用的是该类型的默认值。
String ''
Number 0
Array []
Object {}
//子组件
<template>
<button @click="butFn">改变page值:{{page}}</button>
</template>
<script setup>
import { ref, reactive, toRefs, defineProps, defineEmits } from "vue";
defineProps(["page"]); //接收父组件传来的值
const emit = defineEmits(["pageFn"]); //定义一个变量来接收父组件传来的方法
const butFn=()=>{
emit("pageFn",5)//触发该方法,5为传递的参数
}
</script>
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
change: [id: number] // 具名元组语法
update: [value: string]
}>()