vue3 beta版本发布已有一段时间了,文档也大概看了一下,不过对于学一门技术,最好的方法仍是实战,因而找了一个比较简单的组件用vue3来实现,参考的是vant的countdown组件。javascript
Vue Composition API文档:
若是对vue3语法还不熟悉的,能够先看一下语法html
目前文档仍是英文的,这里简单翻译下几个比较核心的东西:vue
setup函数是一个新的组件option选项,做为在组件内使用Composition API的入口。java
setup会在组件实例建立而且初始props解析后当即调用。对于生命周期这一层面,会在beforeCreate钩子以前调用。react
若是setup返回一个对象,那么对象的属性会被合并到当前组件的render上下文。git
从setup中返回的refs在template中获取值时会被自动unwrapped(猜想多是get值时用了unref。unref也是一个新的api,val = isRef(val) ? val.value : val的语法糖),所以在模板中取值时无需加上.value。github
setup里不只能够返回一个对象,也能够直接返回一个render函数。不过要注意的是,跟template会本身unwrapped不一样,在render中使用refs的值时,须要加上.value。api
setup接收两个参数,第一个是用的最多的props,第二是ctx上下文,不过是精简版,只提供了三个属性attrs,slots,emit,这三个都是写组件必不可少的。数组
在vue2,你能够在每个生命周期或者方法中经过this获取当前实例,不过在setup方法中是没法获取到this的。可是,能够经过getCurrentInstance获取到当前实例。数据结构
传入一个对象并返回对目标源的响应式代理结果,等同于2版本的Vue.observable()。
相似reactive,可是传入的是基本值,取值时须要加上.vaule去获取,而reactive包裹的对象能够直接像对象那样去获取值。
不过当数据结构是数组或者Map时,即便数组已经被reactive包裹了,若是数组里面的某一项是ref,依然须要经过.value去获取值。
//ref const count = ref(0) console.log(count.value); //reactive const state = reactive({ count }) console.log(state.count); //reactive with array const arr = reactive([ref(0),3,5]) // need .value here console.log(arr[0].value)
接收一个回调函数做为getter,或者传入带有getter和setter对象 。一个computed会返回一个对象,有多个computed时就须要调用屡次
const plusOne = computed(() => count.value + 1) const plusTwo = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } })
toRef可用于为源响应对象上的某个属性建立ref。
export default { //因为Javascript函数参数是按值传递,因此若是传递的是基本类型,传参能够理解为复制变量值。基本类型复制后俩个变量彻底独立,以后任何一方改变都不会影响另外一方。若是直接传递props.numner也就是10进去,函数内部跟外部是独立的,函数里面的操做没法影响到外部变量,除非你传递的是一个对象好比整个props,才能保持引用。可是若是你只须要某个属性,传整个进去也是不必的。此时toRef就显的颇有用了。toRef返回的就是一个对象,经过这个对象.value能够获取到值。 setup(props) { // {number:10} useSomeFeature(toRef(props, 'number')) } }
toRefs能够将响应式对象转换为普通对象,其中结果对象上的每一个属性都是指向原始对象中相应属性的引用。能够用于解构的时候防止丢失响应。
生命周期大部分都改了名字,写法上也稍有不一样。
<script> import { h, reactive, onMounted, onBeforeUnmount, toRef } from "vue"; import { formatTime } from "./utils"; export default { props: { time: { type: Number, default: 0 }, millisecond: { type: Boolean, default: false }, autoStart: { type: Boolean, default: true } }, setup(props, { emit }) { const interval = props.millisecond ? 16 : 1000; let leftTime = toRef(props, "time").value; let autoStart = toRef(props, "autoStart").value; let ticker = null; let timeData = reactive(formatTime(leftTime)); const updateTime = (timeData, leftTime) => { const data = formatTime(leftTime); Object.keys(timeData).forEach(k => { timeData[k] = data[k]; }); }; const start = () => { if (!ticker && leftTime > 0) { ticker = setInterval(() => { leftTime -= interval; if (leftTime <= 0) { leftTime = 0; clearInterval(ticker); emit("finish"); } else { emit("change", leftTime); } updateTime(timeData, leftTime); }, interval); } }; const stop = () => { clearInterval(ticker); ticker = null; }; const restart = () => { clearInterval(ticker); ticker = null; leftTime = props.time; emit("change", leftTime); updateTime(timeData, leftTime); start(); }; onMounted(() => { autoStart && start(); }); onBeforeUnmount(() => { stop(); }); return { timeData, start, stop, restart }; }, render({ $slots, timeData, millisecond }) { const time = ["hours", "minutes", "seconds", "millisecond"] .filter(v => v != "millisecond" || millisecond) .map(v => timeData[v]) .join(":"); const defaultContent = h( "span", { style: { fontSize: "14px", color: "#333" } }, time ); return h( "div", ($slots.default && $slots.default(timeData)) || defaultContent ); } }; </script>
主要的变化仍是在setup,没有data,也没有methods了,都需在setup里面返回才可使用。基本上绝大部门的代码都写在setup里面,包括事件,生命周期等, 固然这也很变量的做用域有关。也能够考虑把逻辑抽取出去,不过传参的时候,须要使用toRef或者toRefs,不能传基本值。
主要是用attrs来实现属性的绑定,可是具体是否是这样写,我还不太肯定。
<template> <div> <p>{{ word }}</p> <countdown v-bind="attrs"></countdown> </div> </template> <script> import Countdown from "../components/countdown"; export default { components: { Countdown }, props: { word: { type: String } }, setup(props, { attrs }) { return { attrs }; } }; </script>
<template> <div class="home"> <p>基本使用</p> <countdown :time="66000"></countdown> <p>毫秒数</p> <countdown :time="44444" millisecond></countdown> <p>获取组件实例以及方法</p> <countdown :time="66000" ref="count" :auto-start="false"></countdown> <button @click="count.start()">开始</button> <button @click="count.stop()">关闭</button> <button @click="count.restart()">重启</button> <p>使用插槽自定义样式</p> <countdown :time="66000" @change="change"> <template v-slot="timeData"> <span class="block">{{ timeData.hours }}</span> <span class="colon">:</span> <span class="block">{{ timeData.minutes }}</span> <span class="colon">:</span> <span class="block">{{ timeData.seconds }}</span> </template> </countdown> <p>高阶组件</p> <higher :time="8888" word="测试higher" /> </div> </template> <script> import Countdown from "../components/countdown"; import Higher from "../components/higher"; import { ref, onMounted } from "vue"; export default { name: "Home", components: { Countdown, Higher }, setup() { const change = () => {}; const count = ref(null); onMounted(() => { console.dir(count.value); }); return { change, count }; } }; </script>