Skip to content

學習筆記

基本認識

Details

img

Proxy

如果你把 this 印出來,會看到一個 Proxy 結構,內含 handler & target,target 內會有自訂義的資料等等。

options 改寫成 composition API

setup

  • setup() 函数中需手动暴露大量的状态和方法,裡面不需要使用 this。
  • 方法使用一般 function 或是箭頭韓式都可以。
  • 需要顯示在畫面上的資料,響應式需要改用 ref 或是 reactive 來定義。
  • 如果不需要顯示在畫面上的內部參數,就不需要用 ref 等定義及 return 了。
  • 資料改成 const xxx= ref() 後,使用一定要用 xxx.value=ooo;
js
 <div id="app">
      {{ num }}
     <button type="button" @click="add">累加</button>
  </div>
  <script type="module">
            // import { createApp, ref } from "vue"; //vue Cli,ESM Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
            const { createApp, ref } = Vue;
            // const app = Vue.createApp({
            const app = createApp({
              // data() {
              //   return {
              //     num: 1,
              //   }
              // },
              // methods: {
              //   add() {
              //     this.num++;
              //   }
              // },
              setup() {
                const num = ref(1);
                function add() {
                  // 在 JavaScript 中需要 .value, 不用 this
                  num.value++;
                }
                // 不要忘记同时暴露 add 函数,資料跟方法都要
                return {
                  num,
                  add,
                };
              },
            });

            app.mount("#app");
          </script>

script setup

在 setup() 函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码

響應式 API

ref (推薦使用)

  • Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
  • Ref 是一個 RefImpl 結構,內容 value 可以是純值或是 Proxy。
  • 改值都是透過.value 去改。
js
            const { createApp, ref } = Vue;
            const app = createApp({
              setup() {
                const num = ref(123);
                console.log("num", num);

                const person = ref({ name: "Nana" });可設定對象
                console.log("person", person);
                person.value.name = "L";  記得用.value 設定
                person.value = { name: "Lala" };  
                return { num, person };
              },
            });

ref.html:34
num
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true,
 _rawValue: 123, _value: 123}
ref.html:37
person
RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true,
 _rawValue: {…}, _value: Proxy(Object)}

reactive(單純的 Proxy)

  • 非原始值将通过 reactive() 转换为响应式代理。
  • 是一個 Proxy 對象,需注意不要重新賦予對象,會使 Proxy 失去作用。所以建議用 const 來定義。
js
const { createApp, reactive } = Vue;
const app = createApp({
  setup() {
    //  const person1 = reactive(1); //不可以使用純值設定,會爆出警告 vue.global.js:1346 value cannot be made reactive: 1
    const person = reactive({ name: "Nana" }); //內容是對象
    console.log(person); //可以看到這會是 Proxy
    person.name = "L";

    //let person = reactive({ name: "Nana" });建議用 const
    //person = { name: "name" };Proxy 會消失
    //console.log(person);
    return { person };
  },
});

計算屬性 computed

  • 计算属性
    • 计算属性 publishedBooksMessage。computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似
    • 计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建

watch

js
const app = createApp({
  setup(props) {
    // 監聽純值
    const productName = ref("蛋餅");
    const watchText = ref("");
    watch(productName, (newV, oldV) => {
      watchText.value = `newV:${newV} old:${oldV}`;
    });
    // 監聽物件
    // # ref, reactive 在此運作相同
    const product = ref({
      name: "蛋餅",
      price: 30,
      vegan: false,
      other: {
        name: "送安檢",
      },
    });
    const watchText2 = ref("");
    watch(
      () => product.value.other.name, 對象內容需要用箭頭
      (newV, oldV) => {
        watchText2.value = `newV:${newV} old:${oldV}`;
      }
    );
    return {
      product,
      productName,
      watchText,
      watchText2,
    };
  },
});
  • 多值監聽

    js
    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    });
  • 深層監聽

錯誤寫法

注意 watch 使用上的錯誤寫法。

  • 在 Vue 3 中,watch 的第一个参数必须是一个 getter 函数、一个 ref 对象、一个响应式对象
js
// 錯誤寫法 裡面要用箭頭 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
// watch(pagination.value.current_page, (newValue, oldValue) => {
//   window.scrollTo({top: 0, behavior: 'smooth'});
// });
const pagination = ref({ 等等 });
watch(
  () => pagination.value.current_page,
  (newValue, oldValue) => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  }
);

WatchEffect [Computed 結合體]

Watch 與 Computed 的結合體

  • 會自動去偵測要監聽的值,不需要先定義,有用到就監聽了,但沒辦法拿到前一個修改值
  • 初始會先執行一次,不像 watch 初次沒有值。
js
const app = createApp({
  setup(props) {
    const productName = ref("蛋餅");

    const product = ref({
      name: "蛋餅",
      price: 30,
      vegan: false,
      other: {
        name: "送安檢",
      },
    });

    const watchText = ref("");
    watchEffect(() => {
      console.log("productName", productName);
      console.log("product.value.name", product.value.name);
      watchText.value = `值:${product.value.name}`;
    });
    return {
      product,
      productName,
      watchText,
    };
  },
});
  • watchEffect 是可以被停止的
js
const num = ref(0);
const price = ref(0);
const stopThis = watchEffect(() => {
  price.value = `price 值:${product.value.price}`;
  num.value++;
  if (num.value === 10) {
    stopThis();
  }
});

生命週期(same)

對應表可以參考 - 元件的生命週期與更新機制

  • onMounted 可以定義多次,並放在相關資料附近,增加查找效率。
js
  setup() {
       const num = ref(1);
       onMounted(() => {// 可以定義多次
         num.value = 100;
       })
     const person = ref({
         name: "小明",
         price: 0,
       });
       const add = (n) => {
         num.value += n;

     onMounted(() => {// 可以定義多次
         person.value = {
           name: "小美",
           price: 0,
         };
       })
   return {
         num,person,ad
     };
  }
};

props [父傳子]

  • 在没有使用 的组件中,prop 可以使用 props 选项来声明
  • prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
  • Props
js
<script setup>const props = defineProps(['foo']) console.log(props.foo)</script>;

VS;

export default {
  props: ["foo"],
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo);
  },
};

傳入對象

js

//item={ id: 1,date: "s",title: "s",tags: ["vue", "bootstrap", "pinia", "vite", "cypress"],details: ["xx"],}
<template>
  <WorkItem :item="item"></WorkItem>
</template>


//WorkItem 組件
<template>
  <div>
    {{ item.date }}
  </div>
</template>



<script setup lang="ts">
import { defineProps } from 'vue';

// 定义组件的 props
const props = defineProps<{
  item: {
    id: number;
    date: string;
    title: string;
    tags: string[];
    details: string[];
  };
}>();

// 通过 props 传递给 WorkItem 组件
const item = props.item;
</script>

emit [子對父]

子组件还可以向父组件触发事件。

setup 中存 context 取得

  • 组合式 API:setup()
  • API:setup(props,context), context 內容有 attrs, slots, emit, expose
  • 而 emit 的用法跟前面章節一樣。
js
     setup(props, { emit }) {
         function pushData() {
           console.log("pushData");
           emit("push-data", "內部傳出去的資料");
         }

         return {
           pushData,
         };
       },
     };

script setup 使用 defineEmits

js
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const childMsg = ref('No child msg yet')
</script>


//ChildComp
<template>
  <ChildComp @response="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>

<script setup>
const emit = defineEmits(['response'])

emit('response', 'hello from child')
</script>

<template>
  <h2>Child component</h2>
</template>

訪問模板引用 dom 的 ref

js
<button type="button" ref="btn">
  這裡有一個按鈕
</button>;

const app = createApp({
  components: {
    card,
  },
  setup() {
    const person = ref({
      name: "卡斯伯",
    });

    const btn = ref(null); 必須跟 html ref 同名
    onMounted(() => {
      console.log("ref", btn.value); // <button type="button" ref="btn">這裡有一個按鈕</button>
    });
    return {
      person,
      btn,
    };
  },
});

改寫 [vue2 to vue3]

diff
- <xxx ref="AddCartConfirm">
-  const confirmModal = $refs.AddCartConfirm;
-  confirmModal.showModal();


  <xxx ref="cartConfirm">
+  const cartConfirm = ref(null);
+  const confirmModal = cartConfirm.value;
    confirmModal.showModal();

Provider 與 Inject [父子跨層級 props]

  • 預設不會有雙向綁定,內層採用 v-model 並不會互動。
  • 如果要互動的話就改用 ref(person...) 來定義
  • 依賴注入
    • 常推荐在一个单独的文件中导出这些注入名 Symbol

     const app = createApp({
              components: {
                card,
              },
              setup(props) {
                const person = {
                  name: "Nana",
                };

                provide("person", person);
                return {
                  person,
                };
              },
            });



        const card = {
              template: `<div class="card" style="width: 18rem;">
    <div class="card-body">
      <h5 class="card-title"></h5>
      <input type="text">
    </div>
  </div>`,
              setup(props) {
                const person = inject("person");
                return { person };
              },
            };

組合式函式[邏輯復用]

組合式函數,類似 hook,按照惯例,组合式函数名以“use”开头。

  • 组合式函数

    • “组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。核心逻辑完全一致,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样。

      INFO

      nuxt3 是放在 composables 目錄,也可以把一些通用函示 js 包在裡面。

异步状态示例

js
// fetch.js
import { ref } from "vue";

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err));

  return { data, error };
}

路由

路由參數

js
import { useRoute } from "vue-router";
const route = useRoute();

//route.params.category

監聽路由位置變化

  • vue3 监听路由变化
    • onBeforeRouteUpdate |组件内部通过 beforeRouteLeave 钩子/方式三:通过 watch 监听
    • onBeforeRouteLeave -> setup `
js
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
watch(
  () => route.path,
  () => {
    xxx;
  }
);

路由導向

js
//   this.$router.push(`xxx`)

import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const goToCart = () => {
  router.push("/user/cart/flow");
};

TODO