vue3 六大亮点:
Performance:性能比vue2快1.2~2倍。
Tree shaking support:按需编译,体积比vue2更小
Composition API:组合API
BetterTypescript support:更好的TS支持
Custom Renderer API:暴露了自定义渲染API
Fragment,Teleport(portal),Suspense:更先进的组件。
vue3如何变快的?
diff算法优化:
Vue2中的虚拟算法dom是进行全量的对比。
vue3新增了静态标记(patchFlag);
与上一次虚拟节点进行对比时候,之对比带有patch flag的节点
并且可以通过falg的信息得知当前节点要对比的具体内容
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("span", null, "hello"),
_createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
Vue3.0 diff 在创建虚拟Dom的时候会根据Dom中内容会不会发生变化,添加静态标记。
hoistStatic 静态提升;
Vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染。
vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "hello", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
cacheHandlers 事件侦听器缓存;
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化
但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.btnClick }, null, 8 /* PROPS */, ["onClick"])
]))
}
ssr渲染:
当有大量静态的内容时候,这些内容会被当作纯字符串推进一个buffer里面。
即使存在动态的绑定,会通过模版插入值嵌入进去。这样会比通过虚拟dom来渲染的快的多
当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node
这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
vue-cli 4
Vite : 实现原理 利用ES6的import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去了webpack冗长的打包时间。
npm install -g create-vite-app
create-vite-app projectName
cd projectName
npm run dev
API:
createApp
setup()
reactive()
filter()
this.objArr = this.objArrs.filter((item,ind)=>item.name)
const obj1 = Object.assign({},obj)
const minN = (...arr, n = 0) => arr.sort((a, b) => a - b).slice(0, n)
Vue.createApp({
})
<template>
<div>
{{ count }}<button @click="addCount">sub</button>
<div>
<input v-model="inputVal" @click.enter="handleInputVal" />
</div>
<div v-for="(item,ind) in state.stus" :key="item.id" @click="removeStu(ind)" >{{ind}}--{{item.id}}--{{item.name}}</div>
<form>
<input type="number" v-model="state2.stu.id" /><input type="text" v-model="state2.stu.name" /><input type="submit" @click="addStu" />
</form>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
import { useAddStu } from './addStu';
export default {
name: 'Index',
setup() { // setup函数是组合API的入口函数
// 定义了一个名称叫做count变量,这个变量的初始值是0
// 这个变量发生改变之后,Vue会自动更新UI
// ref()函数注意点:
// ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象/数组)
let count = ref(0);
// reactive()
let state = reactive({
stus: [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Tome' }
]
})
// 在组合API 中,如果想定义方法,不用定义到methods中,直接定义即可,return 暴露出去
function addCount() {
console.log(count) // count是一个对象
count.value++;
}
function removeStu(index) {
state.stus = state.stus.filter((item, ind) => ind !== index)
}
// 写法1:
// let inputVal = ref('kobe')
// function handleInputVal() {
// let id = state.stus[state.stus.length - 1].id
// state.stus.push({ id, name: inputVal })
// }
//抽离 state, removeStu() 写法
// let {state, removeStu} = useRemoveStu();
let { inputVal, handleInputVal } = useAddStu(state);
//注意点:
// 在组合API中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx } 暴露出去
return { count, addCount, state, removeStu, inputVal, handleInputVal, }
}
}
// 抽离出来 解决数据与业务逻辑分散的问题
function useRemoveStu() {
let state = reactive({
stus: [
{ id: 1, name: 'Jack' },
{ id: 2, name: 'Tome' }
]
})
// 点击删除该项
function removeStu(index) {
state.stus = state.stus.filter((item, ind) => ind !== index)
}
return { state, removeStu }
}
/* 写法2: 抽离为一个单独的函数; 写法3: 或者可以抽离为一个单独的文件
function useAddStu(state) {
let inputVal = ref('kobe')
function handleInputVal() {
let id = state.stus[state.stus.length - 1].id
state.stus.push({ id, name: inputVal })
}
}
*/
</script>
import {
ref,
reactive
} from 'vue'
function useAddStu(state) {
let inputVal = ref('kobe');
function handleInputVal() {
let id = state.stus[state.stus.length - 1].id
state.stus.push({
id,
name: inputVal
})
}
return {
inputVal,
handleInputVal
}
}
function useAddStudent(state) {
let id = state.stus[state.stus.length - 1].id
let state2 = reactive({
stu: {
id,
name: ''
}
})
function addStudent(state) {
e.preventDefault()
const stu = Object.assign({}, state2.stu)
state.stus.push(stu)
}
return {
state2,
addStudent
}
}
export {
useAddStu,
useAddStudent
}
setup:
<template>
<div>
<span>{{name}}</span><button @click="handleFnOne">按钮1</button>
<span>{{sex}}</span><button @click="handleFnTwo">按钮2</button>
</div>
</template>
<script>
/*
1.Composition API 和 Option API 混用
2.Composition API 本质(组合API / 注入API)
3.setup 执行时机
beforeCreate : 表示组建刚刚被创建出来,组件的data和methods还没有初始化完成
setup
created : 表示组建刚刚被创建出来,并且组件的data和methods已经初始化完成
4.setup注意点:
1.setup是在beforeCreate和Created之间执行。
由于在执行setup函数的时候,还没有执行Created生命周期方法,所以在setup函数中,是无法使用data和methods
setup函数中是不能够使用data和methods的
2.由于我们不能在setup函数中使用data和methods,所以Vue为了避免我们错误的使用,它直接将setup函数中this修改成了undefined
3.setup函数只能是同步的不能是异步的
*/
import { ref, reactive } from 'vue'
export default {
name: 'Demo2',
data() {
return {
name: 'kobe'
// sex:'man'
}
},
methods: {
handleFnOne() {
console.log(this.name)
}
// handleFnTwo() {
// console.log(this.sex) // 本质 运行时将Composition API 暴露出来的数据注入到 Option API 中
// }
},
setup() { // setup函数是组合API的入口函数
let sex = ref('man')
function handleFnTwo() {
console.log(sex.value)
}
console.log(this) // undefined
console.log(this.name) // 报错
return { count, handleFnTwo }
}
}
// 测试点 composition和option中是否可以相互改变对方数据。
</script>
reactive:
<template>
<div>
<p>{{state.time}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/*
1.什么是reactive?
reactive是VUE3中提供的实现响应式数据的方法
在Vue2中响应式数据是通过defineProperty来实现的
而在Vue3中响应式数据是通过ES6的proxy 来实现的
2.reactive 注意点:
reactive参数必须是对象(json/arr)
如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新, 如果想更新,可以通过重新赋值的方式
*/
import { ref, reactive } from 'vue'
export default {
name: 'Demo2',
setup() { // setup函数是组合API的入口函数
let sex = ref('man')
// let state=reactive(123) // 由于在创建响应式数据的时候传递的不是一个对象,所以无法是响应式。
// let state = reactive([1,3,5]) // state[0]=666 可以实现响应式
let state = reactive({
time: new Date()
})
function handleClick() {
// state = 666; // 传入的不是对象时,state值为123
// state[0] = 666
// 直接修改以前的,界面不会更新
// state.time.setDate(state.time.getDate()+1)
// 重新赋值
const newTime = new Date(state.time.getTime())
newTime.setDate(state.time.getDate() + 1)
state.time = newTime
console.log(state.time)
console.log(state)
}
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
ref:
<template>
<div>
<!-- 如果是通过ref创建的数据,那么在template中使用的时候不用通过 .value来获取,因为Vue会自动给我们添加 .value -->
<!-- ref和reactive的区别:
如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加.value
如果在template里使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value
Vue是如何决定是否需要自动添加。value的, vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不自动添加.value
vue是如何怕断当前的数据是否是ref类型的
通过当前数据的__v_ref 来判断的
如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据 -->
<p>{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/*
1.什么是ref?
ref 和 reactive一样,也是用来实现响应式数据的方法
由于reactive必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue3就给我们提供了ref方法,实现对简单值的监听
2.ref本质:
ref底层的本质其实还是reactive
系统会自动根据我们给reef传入的值将它转换成 ref(xx) -> reactive({value:xx})
3.ref 注意点:
在Vue中使用ref的值不用通过value获取
在JS中使用ref的值必须通过value获取
*/
import { ref, reactive,isRef,isReactive } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
let state = ref('man')
let state=reactive({value:'man'})
// ref本质: ref本质其实还是reactive,当我们给ref函数传递一个值之后,ref函数底层会自动将ref转换成reactive
function handleClick() {
state.value = 'woman'
console.log(state)
console.log(isRef(state))
console.log(isReactive(state))
}
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
shallowRef:
<template>
<div>
<p>{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/*
1.递归监听
默认情况下,无论是通过ref还是reactive都是递归监听
2.递归监听存在的问题
如果数据量比较大,非常消耗性能, 每一层都包装成一个proxy对象
3.非递归监听
只能监听第一层,只把第一层包装成一个proxy对象,如果深层数据第一层数据不改变,改变其他层数据,数据不更新。
注意点:
shallowRef : 如果是通过shallowRef创建数据,那么Vue监听的是 .value 的变化, 并不是第一层的变化。
4.应用场景
一般情况下我们使用ref和reactive 即可,
只有在需要监听的数据量比较大的时候,我们才使用shallowref/ shallowReactive
shallowRef本质:
shallowRef -> shallowReactive
shallowRef(10) -> shallowReactive({value:10})
所以如果是通过shallowRef 创建的数据,它监听的是 .value的变化,因为底层本质上value才是第一层。
toRaw:
从Reactive 或 ref 中得到原始数据
toRaw作用
做一些不想被监听的事情(提升性能)
*/
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
// let state=ref({b:{
// c:{
// d:{
// e:'e1'
// }
// }}})
let state = reactive({
a: 'man',
b: {
c: {
d: {
e: 'e1'
}
}
}
})
function handleClick() {
state.b.c.d.e = 'woman'
// state.value.b.c.d.e = 'e2'
//triggerRef:
state.value.b.c.d.e = 'e3'
// Vue只提供了triggerRef方法,没有提供triggerReactive 方法,所以如果是reactive 类型的数据,那么是无法主动触发界面更新。
triggerRef(state)
}
/*
ref / reactive 数据类型的特点:
每次修改都会被追踪,都会更新UI 界面,但是这样其实是非常消耗性能的,所以如果我们有一些操作不需要追踪,不需要更新UI界面,那么这个时候,
我们可以通过toRaw 方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪,这样就不会更新UI界面,这样性能就好了
*/
let obj = { name: 'kobe', age: 18 }
let state = reactive(obj)
console.log(state === obj) // false
let obj2 = toRaw(state)
console.log(obj2 === obj) // true
// state和obj 的关系:
// 引用关系,state的本质是一个Proxy对象, 在这个Proxy对象中引用了 obj
function myFn() {
// 如果直接修改obj, 那么是无法触发界面更新的
// 只有通过包装之后的对象来修改,才会触发界面的更新
obj.name = 'zs'
console.log(obj) // {name:'zs',age:18}
console.log(state) // {name:'zs',age:18}
obj2.name = 'zs'
console.log(obj2)
console.log(state)
// state.name='zs'
// console.log(state)
}
// ref本质:reactive ref(obj) -> eactive({value:obj})
// 注意点:如果想通过toRaw 拿到ref类型的原始数据(创建时传入的那个数据),
// 那么就必须明确的告诉toRaw 方法,要获取的是 .value的值, 因为经过Vue处理之后吗.value中保存的才是当初创建时传入的那个原始数据
let obj={name: 'kobe', age: 18}
let state=ref(obj)
let obj2=toRaw(state.value)
console.log(obj)
console.log(state) //
console.log(obj2)
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
markRaw:
<template>
<div>
<p>{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/*
1.markRaw 解除数据跟踪响应式
*/
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
obj = markRaw(obj) // 解除跟踪
let state = reactive(obj)
function handleClick() {
state.name = 'james' // 不会改变
}
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
obj = markRaw(obj) // 解除跟踪
let state = ref(obj.name)
function handleClick() {
state.name = 'james' // 不会改变
}
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
toRef / toRefs:
<template>
<div>
<p>{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/*
1.markRaw 解除数据跟踪响应式
2.ref 和 toRef 的区别:
ref -> 复制,修改响应式数据不会影响以前的原始数据
toRef -> 引用, 修改响应式数据会影响到以前的原始数据
ref -> 数据发生改变,UI界面就会自动更新
toRef -> 数据发生改变,UI界面也不会自动更新
toRef 应用场景:
如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef
*/
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
obj = markRaw(obj) // 解除跟踪
let state = reactive(obj)
function handleClick() {
state.name = 'james' // 不会改变
}
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
obj = markRaw(obj) // 解除跟踪
let state = ref(obj.name)
function handleClick() {
state.value = 'james' // 不会改变
}
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
// ref -> 复制
let state = ref(obj.name)
function handleClick() {
state.value = 'james' //
/**
* 结论: 如果利用ref将某一个对象中的属性变成响应式的数据
* 我们修改响应式的数据是不会影响到原始数据的,本质是将原始数据中的name取出来了,然后复制
*/
console.log(obj, state) // 原始obj不会变,state改变
}
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
// toRef -> 引用
let state = toRef(obj, 'name')
function handleClick() {
state.value = 'james' //
/**
* 结论: 如果利用toRef将某一个对象中的属性变成响应式的数据
* 我们修改响应式的数据是会影响到原始数据的,但是如果响应式的数据是通过toRef 创建的,那么修改了数据并不会触发UI界面的更新
*/
console.log(obj, state) // 原始数据obj,state均会改变
}
let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
// toRefs -> 多个属性都要变成响应式时
let state = toRefs(obj)
function handleClick() {
state.name.value = 'james'
state.address = 'beijing'
console.log(obj, state) // 原始数据obj,state均会改变
}
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
customRef:
<template>
<div>
<p>{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/**
* 1.customRef 自定义一个ref
* 返回一个ref 对象,可以显式的控制依赖追踪和触发响应
*
*/
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef } from 'vue'
function useDebouncedRef(value, delay = 200) {
let timer
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
function myRef() {
return customRef((track, trigger) => {
return {
get() {
track() // 追踪数据变化
console.log('get', value)
return value
},
set(newValue) {
value = newValue
console.log('set', newValue)
trigger() // 触发界面更新
}
}
})
}
function myRef1(val) {
return customRef((track, trigger) => {
fetch(val)
.then(res => res.json())
.then(data => {
console.data
value = data
trigger()
})
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
trigger()
}
}
})
}
export default {
setup() { // setup函数是组合API的入口函数
// let obj = { name: 'kobe', address: 'los angeles', team: 'lakers' }
// let state = myRef(obj.name)
// function handleClick() {
// state.value = 'james'
// console.log(obj, state) //
// }
// let state=ref([])
// fetch('./public/data.json')
// .then((res)=>{
// return res.json()
// }).then(data=>{
// console.log(data)
// state.value=data
// }).catch(err=>{
// console.log(err)
// })
let state = myRef1('./public/data.json')
console.log(this) // undefined
return { state, handleClick }
}
}
</script>
re获取元素:
<template>
<div>
<p ref="box">{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/**
* 1.获取元素
* 在Vue2.x 中我们可以通过给元素添加ref='xxx', 然后再代码中通过refs.xxx的方式来获取元素
* 在Vue3.x 中我们也可以通过ref 来获取元素
*/
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef, onMounted, readonly, isReadonly, shallowReadonly } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
// let box = ref(null)
// onMounted(()=>{
// console.log('onMounted',box.value) // p块
// })
// console.log(box.value) // null
// readonly : 用于创建一个只读的数据,并且是递归只读
/**
* const 和 readonly 区别:
* const : 赋值保护,不能给变量重新赋值
* readonly: 属性保护,不能给属性重新赋值
* */
let state = readonly({
name: 'kobe', attr: { address: 'china' }
})
function handleClick() {
state.name = 'james' // 只读属性,值不会变化
state.attr.address = 'beijing' // 只读属性,值不会变化
console.log(state)
console.log(isReadonly(state)) // true
}
// shallowReadonly : 用于创建一个只读的数据,但是不是递归只读的
let state = shallowReadonly({
name: 'kobe', attr: { address: 'china' }
})
function handleClick() {
state.name = 'james' // 第一层只读属性,第一层值不会变化
state.attr.address = 'beijing' // 只值发生改变,UI不会同步更新
console.log(state)
console.log(isReadonly(state)) // true
}
console.log(this) // undefined
return { box, handleClick }
}
}
</script>
vue3响应式:
<template>
<div>
<p ref="box">{{state}}</p>
<button @click="handleClick">按钮</button>
</div>
</template>
<script>
/**
* 1.在Vue3 响应式数据本质
* 在Vue2.x 中是通过defineProperty来实现响应式数据的
* 在Vue3.x 中是通过proxy来实现响应式数据的
*/
let obj = { name: 'kobe', address: 'los' }
let arr = [1, 3, 5, 7]
let state = new Proxy(obj, {
get(obj, key) {
console.log('obj', obj, key) // {name:'kobe',address:'los'} name
return obj[key]
},
set(obj, key, value) {
console.log(value, key, value)
obj[key] = value
console.log('更新Ui界面')
return true // 注意点 为何要return true
}
})
console.log(state.name) // kobe
state.name = 'james'
console.log(state)
/**
* shallowReactive, shallowRef
*/
function shallowReactive(obj) {
return new Proxy(obj, {
get(obj, key) {
console.log(obj, key)
return obj[key]
},
set(obj, key, value) {
console.log(obj, key, value)
obj[key] = value
console.log('更新UI界面')
return true
}
})
}
function shallowRef(val) {
return shallowReactive({ value: val })
}
let obj = { a: 'a', b: { c: 'c' } }
let state = shallowReactive(obj)
state.a = '1'
state.b.c = '3'
let state = shallowRef(obj)
state.value = { a: '1', b: { c: '3' } }
/**
* reactive, ref
*/
function reactive(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
// 如果是一个数组,那么取出数组中的每一个元素,
//判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成Proxy
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = reactive(item)
}
})
} else {
// 如果是一个对象,那么取出对象属性的值,
//判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成Proxy
for (let key in obj) {
let item = obj[key]
if (typeof obj[key] === 'object') {
obj[key] = reactive(item)
}
}
}
return new Proxy(obj, {
get(obj, key) {
console.log(obj, key)
return obj[key]
},
set(obj, key, value) {
console.log(obj, key, value)
obj[key] = value
console.log('更新UI界面')
return true
}
})
} else {
console.warn('not object')
return
}
}
let obj = { a: 'a', b: { c: 'c' } }
let state = reactive(obj)
let arr = [{ a: 'a' }, { b: 'b' }]
let state = reactive(arr)
function ref(val) {
return reactive({ value: val })
}
/**
* shallowReadonly, readonly
*/
function shallowReadonly(obj) {
return new Proxy(obj, {
get(obj, key) {
console.log(obj, key)
return obj[key]
},
set(obj, key, value) {
// console.log(obj, key, value)
// obj[key] = value
console.log(key+'只读的,不能赋值')
// return true
}
})
}
let obj={a:'a'}
let state = shallowReadonly(obj)
import { ref, reactive, isRef, isReactive, shallowReactive, shallowRef, triggerRef, toRaw, markRaw, toRef, toRefs, customRef, onMounted, readonly, isReadonly, shallowReadonly } from 'vue'
export default {
setup() { // setup函数是组合API的入口函数
let state = ref({
})
function handleClick() {
console.log(state)
}
console.log(this) // undefined
return { box, handleClick }
}
}
</script>
支持ts,proxy代替defineProperty,重写虚拟DOM的实现和Tree Shaking, Composition(组合)API
setup:
setup函数在beforeCreate之后,created之前执行。setup()函数中无法访问到this!!!!
接收props数据:
在props中定义当前组件允许外界传递过来的参数名称:props:{p1:String}
通过setup函数的第一形参,接收props数据:setup(props){console.log(props.p1)}
context:
setup函数的第二个形参是一个上下文对象,这个上下文对象中包含了一些有用的属性,这些属性在vue2.x 中需要通过this才能访问到,在vue3.x中,它们的访问方式:
const MyComponent={
setup(props,context){
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
}
}
reactive:
reactive()函数接收一个普通对象,返回一个响应式的数据对象。
等价于vue2.x中的Vue.observable()函数,Vue3.x中提供了reactive()函数,用来创建响应式的数据对象
import {reactive, ref, isRef, toRef, computed, watch, onMounted, onUpdated} from '@vue/composition-api'
//创建响应式数据对象,等到的state类似于vue2.x中data() 返回的响应式对象
setup(){
const state = reactive({count:0})
return state
}
ref:
ref函数用来根据给定的值创建一个响应式的数据对象,ref()函数调用的返回值是一个对象,这个对象上只包含一个.value属性:
setup(){
const count =ref(0) // 创建响应式数据对象 count,初始值为0
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
return {count, name: ref('zs')}
}
在reactive 对象中访问ref创建的响应式数据
当把ref()创建处理的响应式数据对象,挂载到reactive() 上时,会自动把响应式数据对象展开为原始的值,不需要通过 .value就可以直接访问
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count++ // 此处不需要通过.value 就能直接访问原始值
console.log(count) // 1
注意: 新的ref 会覆盖旧的ref
const c1=ref(0)
const state=reactive({c1})
const c2 = ref(9)
state.c1 = c2
state.c1++
console.log(state.c1) // 10
console.log(c2.value) // 10
console.log(c1.value) // 0
isRef:
isRef() 用来判断某个值是否为ref() 创建出来的对象,应用场景:当需要展开某个可能为ref() 创建出来的值的时候
const unwrapped = isRef(foo)?foo.value:foo
toRefs:
toRefs()函数可以将reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每一个属性节点,都是ref()类型的响应式数据
setup(){
const state=reactive({count:0})
const increment = ()=>{
state.count++
}
return { ...toRefs(state), increment}
}
toRef:
const state=reactive({
foo:1,
bar:2
})
const fooRef = toRef(state,'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
toRefs:
const state=reactive({
foo:1,
bar:2
})
const stateAsRefs=toRefs(state)
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
function useFeatureX(){
const state=reactive({
foo:1,
bar:1
})
return toRefs(state)
}
export default{
setup(){
const {foo,bar} = useFeatureX()
return { foo, bar }
}
}
computed:
computed()用来创建计算属性,computed()函数的返回值是一个ref 的实例。
setup(){
const count=ref(0)
// 根据count 的值,创建一个响应式的计算属性plusOne
// 它会根据依赖的ref 自动计算并返回一个新的ref
const plusOne=computed(()=>count.value+1) //
console.log(plusOne.value) // 1
plusOne.value++ //error
const plusTwo = computed({
get:()=>count.value+1,
set:(val)=>{
count.value = val-1
}
})
plusTwo.value=9 // 为计算属性赋值的操作,会触发set函数
// 触发set函数后,count 的值会被更新
console.log(count.value) // 8
}
watch:
const count = ref(0)
// 定义watch,只要count值变化,就会触发watch回调
// watch 会在创建时会自动调用一次
watch(()=>console.log(count.value)) // 0
setTimeout(()=>{
count.value++ // 输出1
},1000)
const state = reactive({count:0})
watch(
()=>state.count,
(count,preCount)=>{
}
)
监视ref 类型的数据源:
const count =ref(0)
watch(count,(count,prevCount)=>{
})
监听多个数据源
监视reactive 类型的数据源:
const state = reactive({count:0,name:'zs'})
watch(
[()=>state.count,()=>state.name], // Object.values(toRefs(state))
([count,name],[prevCount,prevName])=>{
console.log(count)
console.log(name)
console.log(prevCount)
console.log(prevName)
},
{
lazy:true // 在watch 被创建的时候,不执行回调函数中的
}
)
setTimeout(()=>{
state.count++
state.name='ls'
},1000)
监视ref 类型的数据源:
const count =ref(0)
const name=ref('zs')
watch(
[count,name],
([count,name],[prevCount,prevName])=>{
console.log(count,name)
console.log(prevCount,prevName)
},
{
lazy:true
}
)
setTimeout(()=>{
count.value++
name.value='kobe'
},1000)
清除监听
在setup() 函数内创建的watch监听,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监听,可以调用watch()函数的返回值即可
const stop=watch(()=>{
})
stop()
在watch 中清除无效的异步任务
有时候,当被watch 监视的值发生变化时,或者watch本身被stop之后,我们期望能够清除那些无效的异步任务,
此时,watch回调函数中提供了一个cleanup registrator function 来执行清除的工作,这个清除函数会在如下情况下被调用:
watch被重复执行了
watch被强制stop了
<input type="text" v-model="keywords" />
const keywords = ref('')
// 异步任务:打印用户输入的关键词
const asyncPrint=val=>{
return setTimeout(()=>{
console.log(val)
},1000)
}
// 定义 watch 监听
watch(
keywords,
(keywords,prevKeywords,onCleanup)=>{
// 执行异步任务,并得到关闭异步任务的 timerId
const timerId = asyncPrint(keywords)
// 如果 watch 监听被重复执行了, 则会先清除上次未完成的异步任务
oncleanup(()=>clearTimeout(timerId))
},
// watch 刚被创建的时候不执行
{
lazy:true
}
)
// 把template 中需要的数据 return 出去
return {
keywords
}
provide & inject
provide() 和 inject() 可以实现嵌套组件之间的数据传递,这两个函数只能在setup()函数中使用,
父级组件中使用provide()函数向下传递数据,子组件中使用inject()获取上层传递过来的数据。
共享普通数据:
App.vue 根组件:
import { provide } from '@vue/composition-api'
setup(){
provide('globalColor','red')
},
components:{
LevelOne
}
LevelOne.vue 组件:
import { inject } from '@vue/composition-api'
setup(){
const themeColor =inject('globalColor')
return { themeColor }
},
components:{
LevelTwo
}
LevelTwo.vue 组件:
import { inject } from '@vue/composition-api'
setup(){
const themeColor =inject('globalColor')
return { themeColor }
}
共享ref 响应式数据:
实现了点按钮切换主题颜色的功能,主要修改了 App.vue 组件中的代码,LevelOne.vue 和 LevelTwo.vue 中的代码不受任何改变:
<button @click="themeColor='red'">红色</button>
<button @click="themeColor='blue'">蓝色</button>
<button @click="themeColor='orange'">橘黄色</button>
App.vue 根组件:
import { provide } from '@vue/composition-api'
setup(){
const themeColor = ref('red')
provide('globalColor',themeColor)
},
components:{
LevelOne
}
template refs:
通过 ref() 还可以引用页面上的元素或组件。
元素的引用:
<h3 ref="h3Ref">TemplateRefOne</h3>
import { ref, onMounted } from '@vue/composition-api'
setup() {
// 创建一个 DOM 引用
const h3Ref = ref(null)
// 在 DOM 首次加载完毕之后,才能获取到元素的引用
onMounted(() => {
// 为 dom 元素设置字体颜色
// h3Ref.value 是原生DOM对象
h3Ref.value.style.color = 'red'
})
// 把创建的引用 return 出去
return {
h3Ref
}
}
组件的引用:
TemplateRefOne.vue
<button @click="showNumber">获取TemplateRefTwo中的count值</button>
// 3. 为组件添加 ref 引用
<TemplateRefTwo ref="comRef" />
setup(){
const comRef = ref(null)
const showNumber=()=>{
console.log(comRef.value.count)
}
return { comRef, showNumber }
},
components:{
TemplateRefTwo
}
TemplateRefTwo.vue
<button @click="count+1">+1</button>
setup(){
const count = ref(0)
return { count }
}
使用模版语法遍历获取dom
<li v-for="(item,index) in 3" :key="index" :ref="(el)=>{divs[index]=el}">{{ item }}</li>
setup(){
const divs = ref([])
组件挂载后,即能通过divs.value获取遍历元素的所有dom
return { divs }
}
createComponent:
这个函数不是必须的,除非你想要完美结合TypeScript 提供的类型推断来进行项目的开发。
这个函数仅仅提供了类型推断,方便在结合TypeScript 书写代码时,能为setup()中的props 提供完整的类型推断。
import {createComponent} from 'vue'
export default {
props:{
foo:String
},
setup(props){
props.foo
}
}