Vue3學習筆記(四)——組件、生命週期

一、組件

如果我們將一個頁面中所有的處理邏輯全部放在一起,處理起來就會變得非常複雜,而且不利於後續的管理以及擴展,但如果,我們將一個頁面拆分成一個個小的功能塊,每個功能塊完成屬於自己這部分獨立的功能,那麼之後整個頁面的管理和維護就變得非常容易了。如果我們將一個個功能塊拆分後,就可以像搭建積木一下來搭建我們的項目。
 

1.0、SPA

SPA指的是Single Page Application,就是隻有一張Web頁面的應用。單頁應用程序 (SPA) 是加載單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。 瀏覽器一開始會加載必需的HTML、CSS和JavaScript,所有的操作都在這張頁面上完成,都由JavaScript來控制。因此,對單頁應用來說模塊化的開發和設計顯得相當重要。

單頁Web應用,顧名思義,就是隻有一張Web頁面的應用。瀏覽器一開始會加載必需的HTML、CSS和JavaScript,之後所有的操作都在這張頁面上完成,這一切都由JavaScript來控制。因此,單頁Web應用會包含大量的JavaScript代碼,複雜度可想而知,模塊化開發和設計的重要性不言而喻。

速度:更好的用戶體驗,讓用戶在web app感受native app的速度和流暢

MVVM:經典MVVM開發模式,前後端各負其責

ajax:重前端,業務邏輯全部在本地操作,數據都需要通過AJAX同步、提交

路由:在URL中採用#號來作爲當前視圖的地址,改變#號後的參數,頁面並不會重載

優點:

1.分離前後端關注點,前端負責View,後端負責Model,各司其職;
2.服務器只接口提供數據,不用展示邏輯和頁面合成,提高性能;
3.同一套後端程序代碼,不用修改兼容Web界面、手機;
4.用戶體驗好、快,內容的改變不需要重新加載整個頁面
5.可以緩存較多數據,減少服務器壓力
6.單頁應用像網絡一樣,幾乎隨處可以訪問—不像大多數的桌面應用,用戶可以通過任務網絡連接和適當的瀏覽器訪問單頁應用。如今,這一名單包括智能手機、平板電腦、電視、筆記本電腦和臺式計算機。

缺點:

1.SEO問題
2.剛開始的時候加載可能慢很多
3.用戶操作需要寫邏輯,前進、後退等
4.頁面複雜度提高很多,複雜邏輯難度成倍

1.1、什麼是組件?

組件(Component)是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素, Vue.js 的編譯器爲它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴展。

組件系統是 Vue 的另一個重要概念,因爲它是一種抽象,允許我們使用小型、獨立和通常可複用的組件構建大型應用。仔細想想,幾乎任意類型的應用界面都可以抽象爲一個組件樹:

組件允許我們將 UI 劃分爲獨立的、可重用的部分,並且可以對每個部分進行單獨的思考。在實際應用中,組件常常被組織成層層嵌套的樹狀結構:

組件樹

這和我們嵌套 HTML 元素的方式類似,Vue 實現了自己的組件模型,使我們可以在每個組件內封裝自定義內容與邏輯。Vue 同樣也能很好地配合原生 Web Component。如果你想知道 Vue 組件與原生 Web Components 之間的關係,可以閱讀此章節

1.2、定義一個組件

1.2.1、不使用語法糖

定義一個組件含一個按鈕,點擊時值增加,顯示當前值。

components/Counter.vue

<template>
  <div>
    <button @click="n++">n的當前值爲:{{ n }}</button>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "Counter", //名稱
  setup() {
    let n = ref(0);
    return { n };
  },
});
</script>
<style></style>

App.vue

在components中註冊並改名:

<template>
  <cnt />
  <cnt />
  <cnt />
</template>

<script lang="ts">
import Counter from "./components/Counter.vue";
export default {
  setup() {},
  components: {
    cnt: Counter,
  },
};
</script>

不註冊不改名稱

<template>
  <Counter />
  <Counter />
  <Counter />
</template>

<script lang="ts">
import Counter from "./components/Counter.vue";
export default {};
</script>

1.2.2、使用語法糖定義與使用組件

 Counter.vue

<template>
  <div>
    <button @click="n++">n的當前值是:{{ n }}</button>
  </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let n = ref(0);
</script>

App.vue

<template>
  <Counter />
  <Counter />
  <Counter />
</template>
<script lang="ts" setup>
import Counter from "./components/Counter.vue";
</script>

運行效果:

當使用構建步驟時,我們一般會將 Vue 組件定義在一個單獨的 .vue 文件中,這被叫做單文件組件 (簡稱 SFC):

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

當不使用構建步驟時,一個 Vue 組件以一個包含 Vue 特定選項的 JavaScript 對象來定義:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // 或者 `template: '#my-template-element'`
}

這裏的模板是一個內聯的 JavaScript 字符串,Vue 將會在運行時編譯它。你也可以使用 ID 選擇器來指向一個元素 (通常是原生的 <template> 元素),Vue 將會使用其內容作爲模板來源。

上面的例子中定義了一個組件,並在一個 .js 文件裏默認導出了它自己,但你也可以通過具名導出在一個文件中導出多個組件。

1.3、使用組件

我們會在接下來的指引中使用 SFC 語法,無論你是否使用構建步驟,組件相關的概念都是相同的。示例一節中展示了兩種場景中的組件使用情況。

要使用一個子組件,我們需要在父組件中導入它。假設我們把計數器組件放在了一個叫做 ButtonCounter.vue 的文件中,這個組件將會以默認導出的形式被暴露給外部。

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

通過 <script setup>,導入的組件都在模板中直接可用。

當然,你也可以全局地註冊一個組件,使得它在當前應用中的任何組件上都可以使用,而不需要額外再導入。關於組件的全局註冊和局部註冊兩種方式的利弊,我們放在了組件註冊這一章節中專門討論。

組件可以被重用任意多次:

<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

你會注意到,每當點擊這些按鈕時,每一個組件都維護着自己的狀態,是不同的 count。這是因爲每當你使用一個組件,就創建了一個新的實例。

在單文件組件中,推薦爲子組件使用 PascalCase 的標籤名,以此來和原生的 HTML 元素作區分。雖然原生 HTML 標籤名是不區分大小寫的,但 Vue 單文件組件是可以在編譯中區分大小寫的。我們也可以使用 /> 來關閉一個標籤。

如果你是直接在 DOM 中書寫模板 (例如原生 <template> 元素的內容),模板的編譯需要遵從瀏覽器中 HTML 的解析行爲。在這種情況下,你應該需要使用 kebab-case 形式並顯式地關閉這些組件的標籤。

<!-- 如果是在 DOM 中書寫該模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

請看 DOM 模板解析注意事項瞭解更多細節。

1.4、傳遞 props

1.4.1、不使用語法糖

不使用setup語法糖的方式可以在對象中指定props對象或數組聲明屬性。

父組件App.vue:

<template>
  <Counter :data="data" title="超級計算器" />
</template>

<script lang="ts">
import { reactive } from "vue";
import Counter from "./components/Counter.vue";
export default {
  setup() {
    let data = reactive([1, 2, 3]);
    return { data };
  },
  components: {
    Counter,
  },
};
</script>

子組件Counter.vue:

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="n++">n的當前值爲:{{ n }}</button>
    {{ data }}
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "Counter",
  setup() {
    let n = ref(0);
    return { n };
  },
  props: ["title", "data"],
});
</script>

<style></style>

      // 使用 props
      const useProps = () => {
        console.log(props.title) // 默認值
    }

XCounter

<template>
  <div>
    <h3 v-if="title">{{ title }}</h3>
    <button @click="n++">n的當前值是:{{ n }}</button>
    <h3>{{ cntData }}</h3>
  </div>
</template>
<script lang="ts">
import { ref, defineComponent } from "vue";

export default defineComponent({
  name: "XCounter",
  setup(props) {
    let n = ref(100);
    console.log(props.user);
    return { n };
  },
  props: ["title", "cntData", "user"],
});
</script>

XApp.vue

<template>
  <XCounter title="超級計算器1號" :cntData="data" :user="{ a: 100, b: 200 }" />
  <XCounter />
  <XCounter />
</template>
<script lang="ts">
import { reactive } from "vue";
import XCounter from "./components/XCounter.vue";
export default {
  setup() {
    let data = reactive([1, 2, 3]);
    return { data };
  },
};
</script>

1.4.2、使用語法糖

在聲明瞭setup語法糖的setup塊中定義屬性,子組件接受值,通過defineProps 來接受, defineProps是無須引入的直接使用即可。

父組件App.vue

<template>
  <Counter />
  <Counter
    title="這是一個超級計算器"
    :cntData="data"
    :user="{ a: 100, b: 200 }"
  />
  <Counter />
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import Counter from "./components/Counter.vue";
let data = reactive([1, 2, 3]);
</script>

子組件Counter.vue

數組寫法:

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="n++">n的當前值是:{{ n }}</button>
    {{ cntData }}
    <hr />
    {{ user }}
  </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let n = ref(0);

//2、使用數組定義
let props = defineProps(["title", "cntData", "user"]);
console.log(props);
</script>

ts寫法:

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="n++">n的當前值是:{{ n }}</button>
    {{ cntData }}
    <hr />
    {{ user }}
  </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let n = ref(0);

//使用ts語言環境下聲明屬性
//定義屬性類型
type PropType = {
  title?: string;
  cntData?: number[];
  user?: object;
};
//定義屬性並返回屬性值
let props = defineProps<PropType>();
console.log(props);


</script>

非ts寫法

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="n++">n的當前值是:{{ n }}</button>
    {{ cntData }}
    <hr />
    {{ user }}
  </div>
</template>
<script setup>
import { ref } from "vue";
let n = ref(0);

/*
//1、使用ts語言環境下聲明屬性
//定義屬性類型
type PropType = {
  title?: string;
  cntData?: number[];
  user?: object;
};
//定義屬性並返回屬性值
let props = defineProps<PropType>();
console.log(props);
*/

/*
//2、使用數組定義屬性
let props = defineProps(["title", "cntData", "user"]);
console.log(props);
*/

//3、使用對象定義屬性
let props = defineProps({
  title: {
    type: String, //類型
    required: false, //是否是必填屬性
  },
  cntData: { type: Array },
  user: { type: Object },
});
console.log(props);
</script>

如果我們正在構建一個博客,我們可能需要一個表示博客文章的組件。我們希望所有的博客文章分享相同的視覺佈局,但有不同的內容。要實現這樣的效果自然必須向組件中傳遞數據,例如每篇文章標題和內容,這就會使用到 props。

Props 是一種特別的 attributes,你可以在組件上聲明註冊。要傳遞給博客文章組件一個標題,我們必須在組件的 props 列表上聲明它。這裏要用到 defineProps 宏:

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

defineProps 是一個僅 <script setup> 中可用的編譯宏命令,並不需要顯式地導入。聲明的 props 會自動暴露給模板。defineProps 會返回一個對象,其中包含了可以傳遞給組件的所有 props:

const props = defineProps(['title'])
console.log(props.title)

TypeScript 用戶請參考:爲組件 props 標註類型

如果你沒有使用 <script setup>,props 必須以 props 選項的方式聲明,props 對象會作爲 setup() 函數的第一個參數被傳入:

export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

一個組件可以有任意多的 props,默認情況下,所有 prop 都接受任意類型的值。

當一個 prop 被註冊後,可以像這樣以自定義 attribute 的形式傳遞數據給它:

<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

在實際應用中,我們可能在父組件中會有如下的一個博客文章數組:

const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

這種情況下,我們可以使用 v-for 來渲染它們:

<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

留意我們是如何使用 v-bind 來傳遞動態 prop 值的。當事先不知道要渲染的確切內容時,這一點特別有用。

以上就是目前你需要了解的關於 props 的全部了。如果你看完本章節後還想知道更多細節,我們推薦你深入閱讀關於 props 的完整指引

1.4.3、默認值與驗證

(1)、不使用setup語法糖的形式

 props: {
    // 基礎類型檢測 (`null` 意思是任何類型都可以)
    propA: Number,
    // 多種類型
    propB: [String, Number],
    // 必傳且是字符串
    propC: {
      type: String,
      required: true
    },
    // 數字,有默認值
    propD: {
      type: Number,
      default: 100
    },
    // 數組/對象的默認值應當由一個工廠函數返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函數
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="n++">n的當前值爲:{{ n }}</button>
    {{ data }}
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "Counter", //名稱
  setup() {
    let n = ref(0);
    return { n };
  },
  props: {
    title: {
      type: String,
      required: false,
      default: "普通計算器",
    },
    data: {
      type: Array,
      validator: (value: Array<number>) => {
        return value.length === 3;
      },
      required: false,
      default: [1, 2, 3],
    },
  },
});
</script>
<style></style>

type表示屬性的類型,required表示是否爲必要屬性,default表示默認值,validator表示驗證屬性的值是否合理。

(2)、使用setup語法糖的形式

ts模式:

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="clickHandler">n的當前值爲:{{ n }}</button>
    {{ data }}
  </div>
</template>

<script lang="ts" setup>
import { defineComponent, ref } from "vue";

let n = ref(0);
let emit = defineEmits(["countAdd"]);
function clickHandler() {
  n.value++;
  emit("countAdd", n.value);
}

type Props = { title: string; data: number[] };
withDefaults(defineProps<Props>(), {
  title: "超級計算器",
  data: () => [1, 2, 3],
});
</script>

<style></style>

 非ts寫法:

    // 非ts寫法
    const props = defineProps({
        title: {
          type: String,
          default: '默認值',
          required: false
        }
      })

1.5、監聽事件

讓我們繼續關注我們的 <BlogPost> 組件。我們會發現有時候它需要與父組件進行交互。例如,要在此處實現 A11y 的需求,將博客文章的文字能夠放大,而頁面的其餘部分仍使用默認字號。

在父組件中,我們可以添加一個 postFontSize ref 來實現這個效果:

const posts = ref([
  /* ... */
])

const postFontSize = ref(1)

在模板中用它來控制所有博客文章的字體大小:

<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

然後,給 <BlogPost> 組件添加一個按鈕:

<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

這個按鈕目前還沒有做任何事情,我們想要點擊這個按鈕來告訴父組件它應該放大所有博客文章的文字。要解決這個問題,組件實例提供了一個自定義事件系統。父組件可以通過 v-on 或 @ 來選擇性地監聽子組件上拋的事件,就像監聽原生 DOM 事件那樣:

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

子組件可以通過調用內置的 $emit 方法,通過傳入事件名稱來拋出一個事件:

<!-- BlogPost.vue, 省略了 <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

因爲有了 @enlarge-text="postFontSize += 0.1" 的監聽,父組件會接收這一事件,從而更新 postFontSize 的值。

我們可以通過 defineEmits 宏來聲明需要拋出的事件:

<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

這聲明瞭一個組件可能觸發的所有事件,還可以對事件的參數進行驗證。同時,這還可以讓 Vue 避免將它們作爲原生事件監聽器隱式地應用於子組件的根元素。

和 defineProps 類似,defineEmits 僅可用於 <script setup> 之中,並且不需要導入,它返回一個等同於 $emit 方法的 emit 函數。它可以被用於在組件的 <script setup> 中拋出事件,因爲此處無法直接訪問 $emit

<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

TypeScript 用戶請參考:爲組件 emits 標註類型

如果你沒有在使用 <script setup>,你可以通過 emits 選項定義組件會拋出的事件。你可以從 setup() 函數的第二個參數,即 setup 上下文對象上訪問到 emit 函數:

export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

以上就是目前你需要了解的關於組件自定義事件的所有知識了。如果你看完本章節後還想知道更多細節,請深入閱讀組件事件章節。

1.5.1、自定義事件—使用語法糖

Counter.vue

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="clickHandler">n的當前值爲:{{ n }}</button>
    {{ data }}
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
let n = ref(0);
defineProps<{
  title: string;
  data: number[];
}>();
const emit = defineEmits(["countAdd"]);
function clickHandler() {
  n.value++;
  emit("countAdd", n);
}
</script>

<style></style>

App.Vue

<template>
  <Counter :data="data" title="超級計算器" @countAdd="countAddHandler" />
</template>

<script lang="ts" setup>
import { reactive } from "vue";
import Counter from "./components/Counter.vue";
let data = reactive([1, 2, 3]);

function countAddHandler(n) {
  console.log(n);
}
</script>

1.5.2、自定義事件—不使用語法糖

Counter.vue

<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="clickHandler">n的當前值爲:{{ n }}</button>
    {{ data }}
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "Counter",
  setup(props, context) {
    let n = ref(0);
    function clickHandler() {
      n.value++;
      context.emit("oncounter", n.value);
    }
    return { n, clickHandler };
  },
  props: ["title", "data"],
});
</script>

<style></style>

App.vue

<template>
  <Counter :data="data" title="超級計算器" @oncounter="countAddHandler" />
</template>

<script lang="ts" setup>
import { reactive } from "vue";
import Counter from "./components/Counter.vue";
let data = reactive([1, 2, 3]);

function countAddHandler(n) {
  console.log(n);
}
</script>

1.5.3、子組件暴露成員給父組件

子組件暴露給父組件內部屬性通過defineExpose,我們從父組件獲取子組件實例通過ref。

Counter.vue暴露數據組父組件:

defineExpose({ c: 300, d: 400 });

接收子組件暴露的數據:

<template>
  <Counter />
  <Counter
    title="這是一個超級計算器"
    :cntData="data"
    :user="{ a: 100, b: 200 }"
  />
  <Counter ref="counter3" />

  <button @click="getData">counter3暴露的值</button>
</template>
<script lang="ts" setup>
import { functions } from "lodash";
import { reactive, ref } from "vue";
import Counter from "./components/Counter.vue";
let data = reactive([1, 2, 3]);

let counter3 = ref();

function getData() {
  console.log(counter3);
  console.log(counter3.value.c, counter3.value.d);
}
</script>

1.6、通過插槽來分配內容

一些情況下我們會希望能和 HTML 元素一樣向組件中傳遞內容:

<AlertBox>
  Something bad happened.
</AlertBox>

我們期望能渲染成這樣:

This is an Error for Demo Purposes

Something bad happened.

這可以通過 Vue 的自定義 <slot> 元素來實現:

<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

如上所示,我們使用 <slot> 作爲一個佔位符,父組件傳遞進來的內容就會渲染在這裏。

以上就是目前你需要了解的關於插槽的所有知識了。如果你看完本章節後還想知道更多細節,請深入閱讀組件插槽章節。

1.7、setup的參數

props:值爲對象,包含:組件外部傳遞過來,且組件內部聲明接收了的屬性。

context:上下文對象

attrs: 值爲對象,包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性, 相當於 this.$attrs。

slots: 收到的插槽內容, 相當於 this.$slots。

emit: 分發自定義事件的函數, 相當於 this.$emit。

1.8、動態組件

有些場景會需要在兩個組件間來回切換,比如 Tab 界面:

上面的例子是通過 Vue 的 <component> 元素和特殊的 is attribute 實現的:

<!-- currentTab 改變時組件也改變 -->
<component :is="tabs[currentTab]"></component>

在上面的例子中,被傳給 :is 的值可以是以下幾種:

  • 被註冊的組件名
  • 導入的組件對象

你也可以使用 is attribute 來創建一般的 HTML 元素。

當使用 <component :is="..."> 來在多個組件間作切換時,被切換掉的組件會被卸載。我們可以通過 <KeepAlive> 組件強制被切換掉的組件仍然保持“存活”的狀態。

1.9、DOM 模板解析注意事項

如果你想在 DOM 中直接書寫 Vue 模板,Vue 則必須從 DOM 中獲取模板字符串。由於瀏覽器的原生 HTML 解析行爲限制,有一些需要注意的事項。

TIP

請注意下面討論只適用於直接在 DOM 中編寫模板的情況。如果你使用來自以下來源的字符串模板,就不需要顧慮這些限制了:

  • 單文件組件
  • 內聯模板字符串 (例如 template: '...')
  • <script type="text/x-template">

1.9.1、大小寫區分

HTML 標籤和屬性名稱是不分大小寫的,所以瀏覽器會把任何大寫的字符解釋爲小寫。這意味着當你使用 DOM 內的模板時,無論是 PascalCase 形式的組件名稱、camelCase 形式的 prop 名稱還是 v-on 的事件名稱,都需要轉換爲相應等價的 kebab-case (短橫線連字符) 形式:

// JavaScript 中的 camelCase
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

1.9.2、閉合標籤

我們在上面的例子中已經使用過了閉合標籤 (self-closing tag):

<MyComponent />

這是因爲 Vue 的模板解析器支持任意標籤使用 /> 作爲標籤關閉的標誌。

然而在 DOM 模板中,我們必須顯式地寫出關閉標籤:

<my-component></my-component>

這是由於 HTML 只允許一小部分特殊的元素省略其關閉標籤,最常見的就是 <input> 和 <img>。對於其他的元素來說,如果你省略了關閉標籤,原生的 HTML 解析器會認爲開啓的標籤永遠沒有結束,用下面這個代碼片段舉例來說:

<my-component /> <!-- 我們想要在這裏關閉標籤... -->
<span>hello</span>

將被解析爲:

<my-component>
  <span>hello</span>
</my-component> <!-- 但瀏覽器會在這裏關閉標籤 -->

1.9.3、元素位置限制

某些 HTML 元素對於放在其中的元素類型有限制,例如 <ul><ol><table> 和 <select>,相應的,某些元素僅在放置於特定元素中時纔會顯示,例如 <li><tr> 和 <option>

這將導致在使用帶有此類限制元素的組件時出現問題。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

自定義的組件 <blog-post-row> 將作爲無效的內容被忽略,因而在最終呈現的輸出中造成錯誤。我們可以使用特殊的 is attribute 作爲一種解決方案:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

當使用在原生 HTML 元素上時,is 的值必須加上前綴 vue: 纔可以被解析爲一個 Vue 組件。這一點是必要的,爲了避免和原生的自定義內置元素相混淆。

以上就是你需要了解的關於 DOM 模板解析的所有注意事項,同時也是 Vue 基礎部分的所有內容。祝賀你!雖然還有很多需要學習的,但你可以先暫停一下,去用 Vue 做一些有趣的東西,或者研究一些示例

完成了本頁的閱讀後,回顧一下你剛纔所學到的知識,如果還想知道更多細節,我們推薦你繼續閱讀關於組件的完整指引。

二、生命週期

三、示例下載

https://gitee.com/zhangguo5/vue3_-chapter1.git

四、視頻

【Vue3 + Vuex + Pinia + TypeScript + Router】 https://www.bilibili.com/video/BV1at4y1F75D?share_source=copy_web&vd_source=475a31f3c5d6353a782007cd4c638a8a

三、作業

3.1、請完成課程中的所有示例。

3.2、請定義一個vue分頁組件,可以實現客戶端分頁功能,接收參數

參考代碼:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>vue分頁組件</title>
  <style>
    .page {
      font-weight: 900;
      height: 40px;
      text-align: center;
      color: #888;
      margin: 20px auto 0;
      background: #f2f2f2;
    }

    .pagelist {
      font-size: 0;
      background: #fff;
      height: 50px;
      line-height: 50px;
    }

    .pagelist span {
      font-size: 14px;
    }

    .pagelist .jump {
      border: 1px solid #ccc;
      padding: 5px 8px;
      -webkit-border-radius: 4px;
      -moz-border-radius: 4px;
      border-radius: 4px;
      cursor: pointer;
      margin-left: 5px;
    }

    .pagelist .bgprimary {
      cursor: default;
      color: #fff;
      background: #337ab7;
      border-color: #337ab7;
    }

    .jumpinp input {
      width: 55px;
      height: 26px;
      font-size: 13px;
      border: 1px solid #ccc;
      -webkit-border-radius: 4px;
      -moz-border-radius: 4px;
      border-radius: 4px;
      text-align: center;
    }

    .ellipsis {
      padding: 0px 8px;
    }

    .jumppoint {
      margin-left: 30px;
    }

    .pagelist .gobtn {
      font-size: 12px;
    }

    .bgprimary {
      cursor: default;
      color: #fff;
      background: #337ab7;
      border-color: #337ab7;
    }
    .pagelist .jump.disabled{
      pointer-events: none;
      background: #ddd;
    }
  </style>
</head>

<body>
  <div id="app">
    <div>
      <div class="page"  v-show="show">
        <div class="pagelist">
          <span class="jump" :class="{disabled:pstart}" @click="{current_page--}">上一頁</span>
          <span v-show="current_page>5" class="jump" @click="jumpPage(1)">1</span>
          <span class="ellipsis"  v-show="efont">...</span>
          <span class="jump" v-for="num in indexs" :class="{bgprimary:current_page==num}" @click="jumpPage(num)">{{num}}</span>
          <span class="ellipsis"  v-show="ebehind">...</span>

          <span :class="{disabled:pend}" class="jump" @click="{current_page++}">下一頁</span>
          <span v-show="current_page<pages-4" class="jump" @click="jumpPage(pages)">{{pages}}</span>

          <span class="jumppoint">跳轉到:</span>
          <span class="jumpinp"><input type="text" v-model="changePage"></span>
          <span class="jump gobtn" @click="jumpPage(changePage)">GO</span>
        </div>
      </div>
    </div>
  </div>

  <script src="http://www.jq22.com/jquery/vue.min.js"></script>
  <script>
    var newlist = new Vue({
      el: '#app',
      data: {
        current_page: 1, //當前頁
        pages: 50, //總頁數
        changePage:'',//跳轉頁
        nowIndex:0
      },
      computed:{
         show:function(){
             return this.pages && this.pages !=1
         },
         pstart: function() {
           return this.current_page == 1;
         },
         pend: function() {
           return this.current_page == this.pages;
         },
         efont: function() {
           if (this.pages <= 7) return false;
           return this.current_page > 5
         },
         ebehind: function() {
           if (this.pages <= 7) return false;
           var nowAy = this.indexs;
           return nowAy[nowAy.length - 1] != this.pages;
         },
         indexs: function() {

           var left = 1,
             right = this.pages,
             ar = [];
           if (this.pages >= 7) {
             if (this.current_page > 5 && this.current_page < this.pages - 4) {
               left = Number(this.current_page) - 3;
               right = Number(this.current_page) + 3;
             } else {
               if (this.current_page <= 5) {
                 left = 1;
                 right = 7;
               } else {
                 right = this.pages;

                 left = this.pages - 6;
               }
             }
           }
           while (left <= right) {
             ar.push(left);
             left++;
           }
           return ar;
         },
       },
      methods: {
        jumpPage: function(id) {
          this.current_page = id;
        },
      },

    })
  </script>

</body>

</html>
View Code

3.3、請完使用vue2實現圖書列表與詳細展示功能,效果如下:

 

參考地址

3.4、使用Vue 組件(component)完成一個精美的日曆,要求IOS , 安卓, PC 的IE9+都能運行,如下圖所示:

參考

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章