setup函数
举个栗子:(进行了context的解构)
setup(props,{attrs,slots,emit}){
console.log(props.message);
console.log(attrs.id, attrs.class);
console.log(slots);
console.log(emit);
const increment=()=>{
console.log("increment");
}
return{
increment;
}
}
setup特性
它主要有两个参数:
-
第一个参数:props
- 不可以使用this
- this并没有指向当前组件实例
- 在setup被调用之前,data、computed、methods等都没有被解析
- 调用 createComponentInstance 创建组件实例;
- 调用 setupComponent 初始化component内部的操作;
- 调用 setupStatefulComponent 初始化有状态的组件;
- 在 setupStatefulComponent 取出了 setup 函数;
- 通过callWithErrorHandling 的函数执行 setup;
- 从上面的代码我们可以看出, 组件的instance肯定是在执行 setup函数之前就创建出来的
- 不可以使用this
-
第二个参数:context
- attrs:所有的非prop的attribute;
- slots:父组件传递过来的插槽
- emit:当我们组件内部需要发出事件时会用到emit
-
setup的返回值可以在模板template中被使用;
-
也就是说我们可以通过setup的返回值来替代data选项;
reactive API
reactive
import {reactive} from 'vue'
如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数
const state = reactive({
name:"hahhaha",
counter:100
})
变成响应式的原因:
- 当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集
- 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
- 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的
reactive判断的API
import {} from 'vue'
isProxy
- 检查对象是否是由 reactive 或 readonly创建的 proxy。
isReactive
- 检查对象是否是由 reactive创建的响应式代理:
- 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
- 检查对象是否是由 readonly 创建的只读代理。
toRaw
- 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
- 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
- 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)
ref API
import {ref} from 'vue'
reactive API
对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型
- ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
- 它内部的值是在ref的 value 属性中被维护的
const message = ref("Hello World");
注意:
- 在模板中引入ref的值时,Vue会自动帮助我们进行
解包操作
,所以我们并不需要在``模板中`通过 ref.value 的方式来使用; - 但是在
setup 函数内部
,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value
的方式
toRefs
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:
Vue为我们提供了一个toRefs
的函数,可以将reactive返回的对象中的属性都转成ref;
举个栗子:
const info = reactive({
name:"hahaha",
age:19
})
//const {name, age} = info; //此时解构出来的属性不是响应式,只相当于一次赋值
const { name, age } = toRefs(info);
- 那么我们再次进行结构出来的 name 和 age 本身都是 ref的;
- 这种做法相当于已经在
state.name和ref.value
之间建立了链接,任何一个修改都会引起另外一个变化
toRef
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法
const age = toRef(info,"age");
//toRef(reactive对象,key值)
ref其它API
unref
- 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
- 如果参数是一个 ref,则返回内部值,否则返回参数本身;
- 这是
val = isRef(val) ? val.value : val
的语法糖函数;
isRef
判断值是否是一个ref对象。
shallowRef
- 创建一个浅层的ref对象;
triggerRef
- 手动触发和 shallowRef 相关联的副作用:
customRef
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:
- 它需要一个工厂函数,该函数接受
track 和 trigger 函数
作为参数; - 并且应该返回一个带有
get
和set
的对象
举个栗子:双向绑定的属性进行debounce(节流)的操作
//debounceRef.js
import {customRef} from 'vue'
export function DebounceRef(value,delay=200){
let timeout = null;
return customRef((track,trigger)=>{
return{
get(){
track();
return value;
},
set(newValue){
clearTimeout(timeout);
timeout = setTimeout(()=>{
value = newValue;
trigger();
},delay);
}
}
})
}
//App.vue
<template>
<div>
<input v-model="message">
<h2>{{message}}</h2>
</div>
</template>
<script>
import {DebounceRef} from '../hook/DebounceRef.js'
export default{
setup(){
const message = DebounceRef("Hello World");
return {
message
}
}
}
</script>
readonly
reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改
readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改)
//常用方法
const info1 = {name:"hahha"};
const readonlyInfo = readonly(info1);
computed
composition API中的computed计算属性使用:
- 方式一:接收一个
getter
函数,并为 getter 函数返回的值,返回一个不变的 ref 对象;
const fullName = computed(()=>{
return firstName.value + " " + laseName.value;
})
- 方式二:接收一个具有
get 和 set 的对象
,返回一个可变的(可读写)ref 对象;
const fullName = computed({
get:()=>{
return firstName.value + " " + laseName.value;
},
set:()=>{
const names = newValue.split(" ");
firstName.value = names[0];
laseName.value = names[1];
}
})
侦听数据变化
在前面的Options API中,我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些操作
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听;
- watchEffect用于自动收集响应式数据的依赖;
- watch需要手动指定侦听的数据源;
watchEffect
- 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
- 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
const name = ref("hahha");
const age = ref(18);
const changeName = ()=> name.value="lalala"
const changeAge = ()=> age.value++;
watchEffect(()=>{
console.log("watchEffect-----执行:", name.value,age.value);
//自动收集依赖
})
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可 stopWatch();
const changeAge = ()=> {
age.value++;
if(age.value>30){
stopWatch();
}
}
watchEffect清除副作用
在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用。
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
- 当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;
- 我们可以在传入的回调函数中,执行一些清除工作
const stopWatch = watchEffect((onInvalidate)=>{
console.log("watchEffect-----执行:", name.value,age.value);
const timer = setTimeout(()=>{
console.log("2s后......");
},2000);
onInvalidate(()=>{
//onInvalidate回调函数执行清除工作
clearTimeout(timer);
});
});
setup中使用ref
<h2 ref="title">哈哈哈</h2>
...
this.$refs.title
setup中使用refimport {ref} from 'vue
setup(){
const titleRef = ref(null);
//先默认赋值null,等挂载后就会赋值
console.log(titleRef.value)//此时还是null
watchEffect(()=>{
console.log(titleRef.value)
//不适用flush时会输出两个值,第一次默认输出null,然后输出真实值
},{
flush:"post"
//调整watchEffect的执行时机
//等挂载完再执行
})
return{
titleRef
}
}
watch
- watch的API完全等同于组件watch选项的Property:
- watch需要侦听特定的数据源,并在回调函数中执行副作用;
- 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
- 与watchEffect的比较,watch允许我们:
- 懒执行副作用(第一次不会直接执行);
- 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
- 访问侦听状态变化前后的值
侦听单个数据源
watch侦听函数的数据源有两种类型:
- 一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
reactive对象
const state = reactive({
name: "hahha",
age:18
})
watch(()=>state.name, (newValue,oldValue)=>{
console.log(newValue,oldValue);
})
const changeName = ()=>{
state.name = "lalalala"
}
- 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)
ref对象
const name = ref("kobe")
watch(name, (newValue, oldValue)=>{
console.log(newValue,oldValue);
})
const changeName = ()=>{
name.value = "wuyuzi"
}
能用ref就尽量少用reactive,不方便抽离
侦听多个数据源
const name = ref("ahhaha")
const age = (18)
const changeName = ()=>{
name.value="lalala"
}
watch([name,age],(newValues, oldValues)=>{
//还可以对newValues做解构--> newName,newAge,oldName,oldAge
//如果数组是reactive对象,也可以做解构--> [()=>({...info}),name]
console.log(newValues,oldValues);
})
watch的选项
- 如果是reactive对象是会默认进行深度监听的,
- 如果解构成了普通对象,则不会深度监听,
- 如果要深度侦听可以传入第三个参数(对象)
{ deep:true, immediate:true }
生命周期钩子
options API | Hook inside setup
beforeCreate | Not needed*
created | Not needed*
beforeMount | onBeforeMount
mounted | onMounted
beforeUpdate | onBeforeUpdate
updated | onUpdated
beforeUnmount | onBeforeUnmount
unmounted | onUnmounted
activated | onActivated
deactivated | onDeactivated
简单的hook
hook的引入
引入js
import useCounter from './hook/useCounter.js'
setup内部:
const {counted, doubleCounter} = useCounter()
useTitle
import { ref, watch } from 'vue'
export function useTitle( title = '默认值' ){
const titleRef = ref(title);
watch(titleRef,(newValue)=>{
document.title = newValue;
},{
immediate:true
})
return titleRef;
}
useScrollPosition
import { ref } from 'vue'
export function useScrollPosition(){
const scrollX = ref(0);
const scrollY = ref(0);
document.addEventListener('scroll',()=>{
scrollX.value = window.scrollX;
scrollY.value = window.scrollY;
})
return{
scrollX,
scrollY
}
}
useMousePosition
import { ref } from 'vue'
export function useMousePosition(){
const mouseX = ref(0);
const mouseY = ref(0);
window.addEventListener('mousemove',(event)=>{
mouseX.value = event.pageX;
mouseY.value = event.pageY;
})
return{
mouseX,
mouseY
}
}
useLocalStorage
import { ref } from 'vue'
export function useLocalStorage(key,defaultValue){
const data = ref(defaultValue);
if(defaultValue){
window.localStorage.setItem(key,JSON.stringify(defaultValue))
}else{
data.value = JSON.parse(window.localStorage).getItem(key)
}
watch(data,()=>{
window.localStorage.setItem(key,JSON.stringify(data.value))
})
return data;
}
渲染函数(h()函数)
Vue推荐在绝大数情况下使用模板来创建HTML
一些特殊的场景,需要JavaScript的完全编程的能力,这个时候可以使用 渲染函数 ,它比模板更接近编译器---->h()函数
h()函数
h()函数是一个用于创造vnode的函数,更准确的命名时createvNode()函数
它有三个参数:(tag,props,children)
- 如果没有props,那么通常可以将children作为第二个参数传入;
- 如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入
h函数可以在两个地方使用:
- render函数选项中;
import {h} from 'vue'
export default{
render(){
return h('div', {class:"app"}, "Hello APP")
}
}
- setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode);
import {h} from 'vue'
export default{
setup(){
return () => h('div', {class:"app"}, "Hello App")
}
}
案例
计算器案例
data(){
return{
counter:0
}
},
render(){
return h(
'div',
{class:"app"},
[
h("h2", null, `当前计数:${this.counter}`),
h("button",{
onClick:() => this.counter++
}, "+1"),
h("button",{
onClick:() => this.counter--
}, "-1")
]
)
}
//setup计算器案例
setup(){
const counter = ref(0);
return ()=>{
return h(
'div',
{class:"app"},
[
h("h2", null, `当前计数:${this.counter}`),
h("button",{
onClick:() => this.counter++
}, "+1"),
h("button",{
onClick:() => this.counter--
}, "-1")
])
}
}
自定义指令
自定义指令分为两种:
- 自定义局部指令:组件中通过
directives
选项,只能在当前组件中使用;
//挂载自动获取焦点
<input type="text" v-focus>
export default{
directives:{
focus:{
mounted(el){
el.focus();
}
}
}
}
- 自定义全局指令:app的
directive
方法,可以在任意组件中被使用;
app.directive("focus",{
mounted(el){
el.focus();
}
})
指令的生命周期
- created:在绑定元素的 attribute 或事件监听器被应用之前调用;
- beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用;
- mounted:在绑定元素的父组件被挂载后调用;
- beforeUpdate:在更新包含组件的 VNode 之前调用;
- updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用;
- beforeUnmount:在卸载绑定元素的父组件之前调用;
- unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次;
Teleport
- 在组件化开发中,封装一个组件A,在另外一个组件B中使用:
- 那么组件A中template的元素,会被挂载到组件B中template的某个位置;
- 最终的应用程序会形成一颗DOM树结构;
- 但是某些情况下,希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置:
- 比如移动到body元素上,或者有其他的div#app之外的元素上;
- 这个时候就可以通过
teleport
来完成;
- Teleport是什么呢?
- 它是一个Vue提供的内置组件,类似于react的Portals;
- teleport翻译过来是心灵传输、远距离运输的意思;
- 它有两个属性:
- to:指定将其中的内容移动到的目标元素,可以使用选择器;
- disabled:是否禁用 teleport 的功能;
<teleport to="#body">
<h2>lalalala</h2>
</teleport>
Vue插件
- 向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:
- 对象类型:一个对象,但是必须包含一个
install
的函数,该函数会在安装插件时执行;
export default { install(app, options){ console.log(app); app.config.globalProperties.$name = "hahaha" } }
- 函数类型:一个function,这个函数会在安装插件时自动执行;
export default function(app, options){ console.log(app); }
- 对象类型:一个对象,但是必须包含一个
- 插件可以完成的功能没有限制,比如下面的几种都是可以的:
- 添加全局方法或者 property,通过把它们添加到
config.globalProperties
上实现; - 添加全局资源:指令/过滤器/过渡等;
- 通过全局 mixin 来添加一些组件选项;
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能;
vue2中使用全局属性
- 添加全局方法或者 property,通过把它们添加到
mounted(){
console.log(this.$name);
}
composition API使用全局属性
import { getCurrentInstance } from 'vue'
setup(){
const instance = getCurrentInstance();
console.log(instance.$name)
}