为了可以使用Composition API, 咱们须要有一个能够实际使用它的地方。在vue组件中,咱们将此位置称为setuphtml
setup函数是在组件建立以前执行的,setup函数中的props一旦被解析,就将做为Composition API 的入口vue
须要注意的是: 咱们不能在setup函数中使用this, 由于它不会找到组件实例。 缘由是 虽然组件实例是在执行setup以前就会被建立出来了,可是 setup函数被调用以前,data,computed,methods等都没有被解析,因此它们没法在setup中被获取。node
做为setup函数中的第一个参数,props是响应式的,当传入新的prop时,它将会被更新react
其实就是父组件传递过来的属性会被放到props对象中web
export default {
props:{
message:{
type:String,
default:"hello"
}
},
setup(props){
consloe.log(props.message)
}
}
复制代码
!! 因为 props
是响应式的, 因此咱们不能用 解构,它会消除props的响应性。api
若是须要结构props,那么须要用到 toRefs
函数来完成操做。数组
import {toRefs} from 'vue'
setup(props){
const message = toRefs(props,'message')
console.log(message.value)
}
复制代码
做为setup函数的第二个参数, context是一个普通的JavaScript对象,它暴露组件的三个property安全
!!因为context只是一个普通的JavaScript对象,因此它不是响应式的,这就意味着咱们能够安全地对 context
使用 解构markdown
export default{
setup(props,{attrs,slots,emit}){
...
}
}
复制代码
因为setup是一个函数,那么它就会有返回值网络
若是setup的返回值是一个对象,那么该对象的property就能够在模板中访问到。
注意的是:从setup中返回的 refs
在模板中是会自动浅解包的,因此没必要在模板中用 .value
返回对象的响应式副本
若是想在setup中定义的数据是具备响应式的,那么就可使用 reactive
const state = reactive({ name:'wangpf',age:18 })
复制代码
为何会经过 reactive
就能够变成响应式的呢?
reactive
函数将其变成了响应式对象的注意的俩点:
reactive
将解包全部深层的 refs,同时维持着 ref 的响应性
const count = ref(0)
const obj = reactive({ count })
// ref 会被解包
console.log(count.value === obj.value) // true
// 会更新到 'obj.count'
count.value++
console.log(count.value) // 1
console.log(obj.count) // 1
// 也会更新到 ref 'count'
obj.count++
console.log(count.value) // 2
console.log(obj,value) // 2
复制代码
当把 ref
分配给 reactive
时,将会自动解包
const count = ref(1)
const obj = reactive({ })
obj.count = count
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
复制代码
可是因为 reactive
对传入的类型是有限制的,它要求咱们必须传入的是一个对象或者数组
若是咱们传入的是一个基本的数据类型(String,Number,Boolean)则会报警告.
这时,咱们可使用另外一个API, ref
接受一个内部值并返回一个响应式且可变的 ref 对象。 ref对象具备指向内部值的单个property (.vlaue)
ref 会返回一个可变的响应式对象,该对象做为一个响应式的引用(reference) 维护着它内部的值。
内部的值是在 ref的 vlaue属性中被维护的
咱们经过
reactive
或者ref
能够获取到一个响应式的对象,可是某些状况下, 咱们传入给其余地方(组件) 的这个响应式对象但愿在另一个地方(组件)被使用,可是不能被修改,这个时候就能够用 readonly 了
readnoly 会返回原生对象的只读代理 原理在于: 利用 proxy 中的set,在set方法中将其劫持,而且设置该值不能修改
检查对象是否由
reactive
或readonly
建立的 proxy
const state = reactive({ count : 0 })
const count = 0
isProxy(state) // true
isProxy(readonly(state)) // true
isProxy(count) // false
复制代码
检查对象是否由
reactive
建立处理的相应手机代理
但若是代理是由 readonly
建立的,但包裹了由 reactive
建立 另外一个代理,也会返回 true
const state = reactive({ count : 0 })
isReactive(state) // true
isReactive(readonly(state)) // true
复制代码
检查对象是否由
readonly
建立的只读代理
返回
reactive 或 readonly 代理的原始对象
(不建议保留对原始对象的持久化引用,谨慎使用)
shallow(浅层)
建立一个响应式代理,他跟踪其自身property的响应性,但不执行嵌套对象的深层响应式转换 (深层仍是原生对象)
相似于浅拷贝,只把第一层转为响应式了,深层仍是原始对象
建立一个 proxy , 使其自身的 property 为只读, 但不执行嵌套对象的深度只读转换
就是说第一层是只读的,可是深层仍是可读,可写的
因为咱们使用ES6的解构语法对 reactive 返回的对象进行解构赋值,那么解构后的数据是不具备响应式的
而使用 toRefs 能够将 reative 返回的对象中的属性都转成 ref 这样咱们再次解构出来的数据都是 ref的。
const state = reactive({ name:'wangpf' , age:18 });
const { name, age } = state; // 这样解构出来的数据是没有响应式的
const { name, age } = toRefs(state) // 这样的解构出来的数据转换成了ref的,是响应式的
// 上述的作法, 至关于在 state.name 和 name.value 之间创建了链接, 修改任何一个都会引发另一个变化
复制代码
若是咱们但愿转换一个 reactive 对象中的属性为 ref ,那么可使用 toRef 的方法
const state = reactive({ name:'wangpf' , age:18 });
const name = toRef(state,"name"); // 该 name 是ref的,
// 一样的, name.value 和 state.name 之间创建了链接, 修改会互相影响
复制代码
若是想要获取一个ref引用中的value,能够经过 unref 方法:
若是参数是一个 ref, 则返回内部值,不然返回参数本事
实际上是一个语法糖:
val = isRef(val) ? val.value : val
复制代码
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x) // unwrapped 如今必定是数字类型
}
复制代码
判断值是不是一个ref对象
建立一个浅层的ref对象
const info = shallowRef({ name: "wangpf" })
const changeInfo = () => {
info.value.name = 'wpf' // 此时的修改是不可以实现响应式的
}
复制代码
手动触发和 shallowRef 相关联的反作用
const info = shallowRef({ name: "wangpf" })
const changeInfo = () => {
info.value.name = 'wpf' // 此时的修改是不可以实现响应式的
// 使用 triggerRef 能够触发
triggerRef(info)
}
复制代码
建立一个自定义的 ref ,并对其依赖项跟踪和更新触发进行显示控制
import { customRef } from "vue";
export default function (value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
}
复制代码
<template>
<input type="text" v-model="message" />
<p>{{ message }}</p>
</template>
<script>
import useDebounceRef from "../hooks/useDebounceRef";
export default {
name: "Demo2",
setup() {
const message = useDebounceRef("hello", 300);
return {
message,
};
},
};
</script>
复制代码
该API 的方法 和 vue2 的同样, 只不过写的地方是在setup函数中了, 但还需注意的点是 computed 返回的值是一个 ref
const firstName = ref("wangpf");
const lastName = ref("ok");
const fullName = computed(()=> `${firstName.value} ${lastName.value}`)
复制代码
const firstName = ref("wangpf");
const lastName = ref("ok");
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
const names = newValue.split(" ");
firstName.value = names[0];
lastName.value = names[1];
},
});
const changeName = () => {
fullName.value = "wpf err";
console.log(fullName.value);
};
复制代码
在 Composition API 中, 咱们可使用 watchEffect 和 watch 来完成响应式数据的侦听
当侦听到某些响应式数据变化时,咱们但愿执行某些操做,这个时候就能够用 watchEffect
来看一个案例:
const name = ref('wangpf')
const age = ref(18)
watchEffect(() => {
console.log('watchEffect执行了',name.value,age.value)
})
复制代码
看一个案例:
const stopWatch = watchEffect(() => {
console.log('watchEffect执行了',name.value,age.value)
})
const changeAge = () => {
age.value++;
if(age.value > 20){
stopWatch() // 掉用 watchEffect 的返回值就能够中止侦听了
}
}
复制代码
用途: 好比在开发中咱们须要在侦听函数中执行网络请求,可是在网络请求尚未达到的时候,咱们中止了侦听器,或者侦听器侦听函数被再次执行了,这时咱们须要把上一次的网络请求 取消掉, 就能够用到该方法了
看了一个案例:
watchEffect((onInvalidate) => {
console.log('watchEffect执行了',name.value,age.value)
const timer = setTimeout(() => {
console.log('1s后执行的操做')
},1000)
onInvalidate(() => {
// 在这里操做一些清除工做
clearTimeour(timer)
})
})
复制代码
在上述代码中,咱们给 watchEffect 传入的函数被回调时,能够获取到一个参数:onInvalidate (该参数是一个函数),能够在这个参数中,执行一些清除工做。
在默认状况下 , 组件的更新会在 watchEffect (反作用函数) 以前执行
const title = ref(null); // 该title 已和 div 标签绑定了
watchEffect(() => {
console.log(title.value);
});
return { title };
复制代码
那么,当咱们在 watchEffect (反作用函数) 获取元素时, 第一次执行是确定是个null, 是不能够的。只有当DOM挂载完毕后,才会给 title 赋新的值,watchEffect (反作用函数)才会再次执行, 打印出对应的元素
咱们但愿第一次就打印出该元素的话,这时候须要给 watchEffect 传入第二个参数, 该参数是一个 对象,对象中的 flush 可取三个值 : 'pre' (默认的) , 'post' , 'async' (不建议使用)
// 在组件更新后触发,这样你就能够访问更新的 DOM。
// 注意:这也将推迟反作用的初始运行,直到组件的首次渲染完成。
watchEffect(
() => {
/* ... */
},
{
flush: 'post' // 在 DOM元素挂载或更新以后执行, "pre" 当即执行 (默认的)
}
)
复制代码
watch API 的功能和 vue 2 的 option API 中的 watch 功能同样, 默认状况下, watch 只有当被侦听的源发送改变时才会去回调
与 watchEffect 比较,不一样的是:
想要侦听单个数据源的话, 有俩种方法: 传入 getter 函数 或 ref 对象
// 侦听 getter
const state = reactive({ name : 'wangpf' })
watch(() => state.name , (newVal,oldVal) => {
/* ... */
})
// 侦听 ref
const name = ref('wamgpf')
watch(name,(newVal,oldVal) => {
/* ... */
// 这里的 newVal,oldVal 的值是 是返回的 ref.value 的值
})
复制代码
方法: 传入数组
const firstName = ref("AAA");
const lastName = ref("bbb");
const changeName = () => {
firstName.value = "A";
lastName.value = "b";
};
watch([firstName, lastName], (newVal, oldVal) => {
console.log("newVal:", newVal, "oldVal:", oldVal);
});
// newVal: ["A", "b"] oldVal: ["AAA", "bbb"]
复制代码
就是侦听 reactive 对象
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers)
}
)
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
复制代码
想要深度侦听嵌套对象或数组时,须要 deep 设为 true,
const state = reactive({
id: 1,
attributes: {
name: '',
}
})
watch(
() => state,
(state, prevState) => {
console.log(
'not deep',
state.attributes.name,
prevState.attributes.name
)
}
)
watch(
() => state,
(state, prevState) => {
console.log(
'deep',
state.attributes.name,
prevState.attributes.name
)
},
{ deep: true }
)
state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"
复制代码
可是会发现 新的值和旧的值是同样的。这时候为了彻底侦听,须要使用深拷贝了
em.... 去看文档吧,
功能和以前同样,
咱们能够经过 provide 来提供数据
咱们能够经过 jnject 来注入须要的内容
let count = ref(100)
let info = { name : "wangpf" , age : 18 }
provide("count",readonly(count))
provide("info",readonly(info)) // 这里建议使用 readonly 对值进行包裹,防止传递的数据不会被 inject 的组件更改
// 在后代组件中 经过 inject 来获取
const count = inject("count")
const info = inject("info")
复制代码
vue在生成真实DOM以前,会将咱们的节点转换成VNode,而VNode组合起来会造成一颗树结构,即 虚拟DOM
在 template 中的 html 是使用渲染函数生成的对应的VNode
若是咱们想要利用 JavaScript 来编写 createVNode 函数,生成对应的VNode 那么就可使用 h() 函数
h() 函数是一个用于常见 VNode 的一个函数, 其实更准确的命名是 createVNode() 函数,但为了简便 vue 将其简化为 h() 函数
h() 函数接收三个参数: (标签,属性,后代)
h(
// {String | Object | Function} tag
// 一个 HTML 标签名、一个组件、一个异步组件、或
// 一个函数式组件。
//
// 必需的。
'div',
// {Object} props
// 与 attribute、prop 和事件相对应的对象。
// 咱们会在模板中使用。
//
// 可选的。
{},
// {String | Array | Object} children
// 子 VNodes, 使用 `h()` 构建,
// 或使用字符串获取 "文本 Vnode" 或者
// 有插槽的对象。
//
// 可选的。
[
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
复制代码
注意:若是没有props,能够将 children 做为第二个参数传入, 可是会产生歧义,因此通常会将 null 做为第二个参数传入,将 children 做为第三个参数传入
export default {
render(){
return h('div', { class:'app' }, 'hello app')
}
}
export default {
setup(){
return () => h('div', { class:'app' }, 'hello app')
}
}
复制代码
这样写代码,不只慢并且阅读性通常, 因此 推荐使用 jsx, 语法和 react同样, 这里不细说了。可看文档
在 Vue 中,代码复用和抽象的主要形式是组件。然而,有的状况下,你仍然须要对普通 DOM 元素进行底层操做,这时候就会用到自定义指令。
自定义指令分为俩种:
一个指令定义的对象,Vue提供了以下的几个钩子函数:
这几个钩子函数中可传入四个参数:el
、binding
、vnode
和 prevNnode
瞬移组件, 能够将该组件转移到其余dom元素上。
一般用于 封装模态框、土司之类的,将它放在Body元素上和 div#app 元素平级
<teleport :to='#demo'>
<h2>hello</h2>
</teleport>
// 该元素就会被转移到 id 为 demo 元素上
复制代码
一般状况下,咱们向Vue全局去添加一些功能时,会采用插件的模式
install
的函数,该函数会在安装插件时执行// plugin_obect.js
export default {
install(app) {
app.config.globalProperties.$name = "wangpf";
},
};
// main.js
import plugin_object from "./plugins/plugin_object";
app.use(plugin_object);
// app.vue
import { getCurrentInstance } from "vue";
setup() {
const Instance = getCurrentInstance();
console.log("Instance", Instance.appContext.config.globalProperties.$name);
}
复制代码