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>