Vue3語法快速入門以及寫一個倒計時組件

Vue3寫一個倒計時組件

vue3 beta版本發佈已有一段時間了,文檔也大概看了一下,不過對於學一門技術,最好的方法仍是實戰,因而找了一個比較簡單的組件用vue3來實現,參考的是vant的countdown組件。javascript

Vue Composition API文檔:
若是對vue3語法還不熟悉的,能夠先看一下語法html

目前文檔仍是英文的,這裏簡單翻譯下幾個比較核心的東西:vue

setup

setup函數是一個新的組件option選項,做爲在組件內使用Composition API的入口。java

1. 調用時機

setup會在組件實例建立而且初始props解析後當即調用。對於生命週期這一層面,會在beforeCreate鉤子以前調用。react

2. 配合template使用

若是setup返回一個對象,那麼對象的屬性會被合併到當前組件的render上下文。git

從setup中返回的refs在template中獲取值時會被自動unwrapped(猜想多是get值時用了unref。unref也是一個新的api,val = isRef(val) ? val.value : val的語法糖),所以在模板中取值時無需加上.value。github

3. 配合render函數使用

setup裏不只能夠返回一個對象,也能夠直接返回一個render函數。不過要注意的是,跟template會本身unwrapped不一樣,在render中使用refs的值時,須要加上.value。api

4. 參數

setup接收兩個參數,第一個是用的最多的props,第二是ctx上下文,不過是精簡版,只提供了三個屬性attrs,slots,emit,這三個都是寫組件必不可少的。數組

  • emit: 同父組件通訊用
  • slots: 插槽分發內容
  • attrs: 能夠用於封裝高階組件, 配合v-bind進行屬性透傳

5. this在setup中不可用

在vue2,你能夠在每個生命週期或者方法中經過this獲取當前實例,不過在setup方法中是沒法獲取到this的。可是,能夠經過getCurrentInstance獲取到當前實例。數據結構

reactive

傳入一個對象並返回對目標源的響應式代理結果,等同於2版本的Vue.observable()。

ref

相似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)

computed

接收一個回調函數做爲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, toRefs

toRef可用於爲源響應對象上的某個屬性建立ref。

export default {
    //因爲Javascript函數參數是按值傳遞,因此若是傳遞的是基本類型,傳參能夠理解爲複製變量值。基本類型複製後倆個變量徹底獨立,以後任何一方改變都不會影響另外一方。若是直接傳遞props.numner也就是10進去,函數內部跟外部是獨立的,函數裏面的操做沒法影響到外部變量,除非你傳遞的是一個對象好比整個props,才能保持引用。可是若是你只須要某個屬性,傳整個進去也是不必的。此時toRef就顯的頗有用了。toRef返回的就是一個對象,經過這個對象.value能夠獲取到值。
  setup(props) {
    // {number:10}
    useSomeFeature(toRef(props, 'number'))
  }
}

toRefs能夠將響應式對象轉換爲普通對象,其中結果對象上的每一個屬性都是指向原始對象中相應屬性的引用。能夠用於解構的時候防止丟失響應。

生命週期

生命週期大部分都改了名字,寫法上也稍有不一樣。

  1. 須要本身import;
  2. 在setup中調用;
  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

語法就大概介紹這些,具體的內容仍是要看看官方文檔。下面看一下組件的具體代碼

<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>

DEMO

<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>

展現效果

效果

倉庫地址

https://github.com/linrunzheng/vue3-learn

相關文章
相關標籤/搜索