相对于 Vue2.0 的 optionsAPI,Vue3.0 使用 compositionAPI.
setup 接收props
和context
的函数.
在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); 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 ) } } 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); console .log (counter.value ); counter.value ++; console .log (counter.value );
生命周期钩子 在setup
中,注册生命周期钩子.与 optionsAPI 名称相同,前缀为on
,即mounted
为onMounted
.beforeCreate
和created
直接在setup
中.其他的生命周期加上on
. 另外需要 import 引入. 没有beforeCreate
和created
是因为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 的区别
watch 可以访问新旧值,watchEffect 不可以
watchEffect 有副作用,DOM 挂载或者更新前会触发,需要手动清除
watch 是惰性执行,只有监听值发生改变才会执行,watchEffect 每次代码加载都会执行
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 ); console .log (twiceTheCounter.value );
新创建的计算变量的 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 <template> <MyMarker /> </template> <script > import MyMarker from './MyMarker.vue' export default { components : { MyMarker }, provide : { location : 'North Pole' , geolocation : { longitude : 90 , latitude : 135 } } } </script > export default { inject : ['location' , 'geolocation' ] } <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 <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, }); 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} }