Vue3.0

相对于 Vue2.0 的 optionsAPI,Vue3.0 使用 compositionAPI.

setup

接收propscontext的函数.

setup避免使用this,因为它会找不到组件实例.setup的调用发生在data,computed, methods被解析之前,在setup中无法获取.

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
components: { a, b, c },
props: {
user: {
type: String,
required: true,
},
},
setup(props) {
console.log(props); // {user: ''}
return {};
},
};

第一个参数 props

props 是响应式的,所以不能使用 ES6 解构.它会消除 prop 的响应性.
如果非要解构 props,可以在setup函数中使用toRefs函数.

1
2
3
4
5
6
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)

console.log(title.value)
}

可选 props

如果title是可选 prop, 则传入的props中可能没有title.则toRefs将不会为title创建一个 ref.
需要使用toRef替代.

1
2
3
4
5
import { toRef } from 'vue'
setup(props){
const title = toRef(props, 'title')
console.log(title.value)
}

第二个参数 Context

context 暴露组件的三个属性;

1
2
3
4
5
6
7
8
9
10
11
export default {
setup(props, context){
console.log(context.attrs)

console.log(context.slots)

console.log(context.emit)
}
}
//context不是响应式的所以可以使用解构
setup(props,{attrs, slots, emit}){}

模板中不再使用.value,因为访问时是被自动浅解包的.

使用渲染函数

1
2
3
4
5
6
7
8
9
10
import { h, ref, reactive ) from 'vue'

export default {
setup(){
const readersNumber = ref[0]
const book = reactive({title: "Vue3 Guide"})

return () => h('div', [readersNumber.value, book.title])
}
}

使用 this

在 setup() 内部,this 不是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。

toRefs

可以把基本类型的数据变为响应式的,适用于对象解构后的处理。
可以把响应式数据内的所有属性都转化为响应式的。

reactive

可以把

ref的响应式变量

在 3.0 中,可以通过ref函数使任何响应式变量在任何地方起作用.
ref接收参数并将其包裹在一个带有value属性的对象中返回,然后可以使用该属性访问或更改响应式变量的值.

1
2
3
4
5
6
7
8
9
import { ref } from "vue";

const counter = ref(0);

console.log(counter); // {value: 0}
console.log(counter.value); //0

counter.value++;
console.log(counter.value); //1

生命周期钩子

setup中,注册生命周期钩子.与 optionsAPI 名称相同,前缀为on,即mountedonMounted.
beforeCreatecreated直接在setup中.其他的生命周期加上on.
另外需要 import 引入.
没有beforeCreatecreated是因为setup是围绕他们运行的不需要显示的定义他们.

watch 响应式更改

接收三个参数,一个响应式引用或 getter 函数,一个回调,可选的配置选项.

1
2
3
4
5
6
import { ref, watch } from "vue";

const counter = ref[0];
watch(counter, (newValue, oldValue) => {
console.log("new value:" + counter.value);
});

每当 counter 被修改,侦听将触发并执行回调(第二个参数).
watch 是惰性执行,只有发生改变才会执行,想要立即执行,第三个参数immediate: true.

1
2
3
4
5
6
7
8
9
<!-- 监听单个ref定义的响应式数据 -->
setup(){ let mes = ref("hello") watch(mes, (old, new) => {}, {immediate: true})
} // 监听多个ref setup(){ let mes1 = ref("hello") let mes2 = ref("world")
watch([mes1,mes2], (old,new)=>{},{immediate: true}) } //
监听reactive定义的属性(基本数据),默认开启deep:true setup(){ let res = reactive({
name: 'Tom', obj: { foo: 10 } }) // 监听name watch(() => res.name, (old,new) =>
{}) } // 监听reactive定义的引用数据,需要自己开启deep:true setup(){ //
沿用上面的res watch([() => res.name, () => res.obj], (old,new) => {}, {deep:
true}) }

watchEffect

立即执行传入的回调,并响应式追踪其依赖,依赖改变时重新执行回调.

1
2
setup(){ const id = ref(0) watchEffect(() => { console.log(id) }) setTimeout(()
=> { id.value = 1 },1000) }

停止监听

watchEffect 会在组件卸载时自动停止,显示的调用返回值会立即停止监听

1
const stop = watchEffect(()=>{}) stop()

清除副作用

使用onInvalidate函数作为参数,用来清除副作用.
触发时机:

  • 副作用即将重新执行
  • 停止监听
1
2
3
4
import { watchEffect, ref } from 'vue' const count = ref(0)
watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => {
console.log('执行了onInvalidate') }) }) setTimeout(()=> { count.value++ }, 1000)
// 0 => 执行了onInvalidate => 1

分析:初始化时先打印 count 的值 0, 然后由于定时器把 count 的值更新为 1, 此时副作用即将重新执行,因此 onInvalidate 的回调函数会被触发,打印执行了 onInvalidate,然后执行了副作用函数,打印 count 的值 1。

1
2
3
import { watchEffect, ref } from 'vue' const count = ref(0) const stop =
watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => {
console.log('执行了onInvalidate') }) }) setTimeout(()=> { stop() }, 1000)

上述代码:当我们显示执行 stop 函数停止侦听,此时也会触发 onInvalidate 的回调函数。同样,watchEffect 所在的组件被卸载时会隐式调用 stop 函数停止侦听,故也能触发 onInvalidate 的回调函数。

watchEffect 的应用

场景一:
定时器的注册和清除; DOM 事件的监听和清除(DOM 事件只能在挂载后,onMounted)

1
2
3
4
5
6
7
watchEffect((onInvalidate) => { let timer = setInterval(()=>{},1000)
onInvalidate(()=> clearInterval(timer)) }) // dom事件的监听 onMounted(() => {
watchEffect((onOnvalidate) => {
document.getElementById('.btn').addEventListener('click',function(){})
onInvalidate(()
=>document.getElementById('.btn').removeEventListener('click',function(){}) })
})

场景二:
利用 watchEffect 做防抖节流

1
2
3
const id = ref(1) watchEffect((onInvalidate) => { // 异步请求 const token =
await fetch('...',id) onInvalidate(() => { // id变化频繁就取消请求
token.cancel() }) })

watch 和 watchEffect 的区别

  1. watch 可以访问新旧值,watchEffect 不可以
  2. watchEffect 有副作用,DOM 挂载或者更新前会触发,需要手动清除
  3. watch 是惰性执行,只有监听值发生改变才会执行,watchEffect 每次代码加载都会执行
  4. watch 需要指明监听的对象,也需要监听的回调.watchEffect 不用指明,监听的回调中用到那个属性就监听哪个.

独立的computed属性

1
2
3
4
5
6
7
8
import { ref, computed } from "vue";

const counter = ref[0];
const twiceTheCounter = computed(() => counter.value * 2);

counter.value++;
console.log(counter.value); //1
console.log(twiceTheCounter.value); //2

新创建的计算变量的 twiceTheCounter 也需要使用.value的方式调用.

Provide/Inject

使用 provide

provide 有两个参数,name(类型)和 value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//Vue2
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
//MyMarker.vue
export default {
inject: ['location', 'geolocation']
}

//Vue3重构
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
components: {
MyMarker
},
setup(){
provide('location','North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}

使用 inject

inject 有两个参数,一个是要注入的属性 name,一个是默认值(可选)

1
2
3
4
5
6
7
8
9
10
11
12
import { inject } from "vue";
export default {
setup() {
const userLocation = inject("location", "The Universe");
const userGeolocation = inject("geolocation");

return {
userLocation,
userGeolocation,
};
},
};

响应性

更新 inject 的数据,建议 provide 一个方法来改变响应式属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup(){
const location = ref('North Pole')
const geolacation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
provide('location', location)
provide('geolocation', geolocation)
//+
provide('updateLocation', updateLocation)
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//MyMarker.vue
<script>
import { inject } from 'vue'
export default {
setup(){
const userLocation = inject('location','The Universe')
const userGeolocation = inject('geolocation')
const updateUserLocation = inject('updateLocation')

return {
userLocation,
userGeolocation,
updateUserLocation
}
}
}
</script>

如果确保通过provide传递的数据不会被inject的组件更改,我们建议对提供者的属性使用readonly.

1
2
3
4
5
6
import { provide, reactive, readonly, ref } from 'vue'
...

provide('location',readonly(location))
provide('geolocation',readonly(geolocation))
provide('updateLocation', updateLocation)

模板引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div ref="root">this is a root element</div>
</tempalte>

<script>
import {ref, onMounted} from 'vue'
export default {
setup(){
const root = ref(null)

onMounted(() => {
console.log(root.value)
})
return {
root
}
}
}
</script>

在渲染上下文暴露root,并通过ref="root",将其绑定到 div 作为其 ref.在虚拟 DOM 补丁算法中,如果 VNode 的ref键对应于渲染上下文的 ref,则 VNode 的相应元素或实例将被分配给 ref 的值.这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值.

JSX 中的用法

1
2
3
4
5
6
7
8
9
10
11
12
export default {
setup() {
const root = ref(null);

return () =>
h("div", {
ref: root,
});
//with JSX
return () => <div ref={root} />;
},
};

侦听模板引用

因为 watch()和 watchEffect()在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新.
所以,使用模板引用的侦听器应该用{flush: 'post'}来定义,这将在 DOM 更新后运行副作用,

1
2
3
4
5
6
7
8
9
10
setup(){
const root = ref[null]
watchEffect(()=> {
console.log(root.value)
},
{
flush: 'post'
})
return {root}
}