---
# System prepended metadata

title: 第三堂：Vue 起步走，指令學起來
tags: [' 六角學院', 2022 Vue 作品實戰班]

---

---
title: 第三堂：Vue 起步走，指令學起來
tags: 2022 Vue 作品實戰班, 六角學院
date: 20220121
image:
---

# 第三堂：Vue 起步走，指令學起來

# 最終作業主題
- 做自已有興趣的
    - 浪浪動物
    - 服飾
- 先收集素材
    - 賣酒，你的產品有哪些
    - 台灣啤酒，unsplash 可能沒有
    - 換個方向，選擇國外高檔葡萄酒
- 撰寫文案
- 作品牆
    - 健身廚房
    - 麵包
        - 感受到點麵包的愛
        - 配色一致
    - 誇張系
    - 注意授權
        - 小魔女諾貝塔雜貨工坊
        - 使用免授權圖片
        - 台中行李箱
            - local 生活感
- api 限制
    - NoSql
    - 試著突破極限
    - 延伸加入更多的欄位
    - 同學自行發揮
    - 求職網![](https://i.imgur.com/iKhP66L.png)
    - 先做電商主題
    - 熟悉 api 可以做到什麼程度
    - 亞洲媒體藝術節
        - 預約功能
        - 調整時間
        - 新建了web server
        - Nuxt
        - middle ware![](https://i.imgur.com/aTxv5Bb.png)
    - 鼓勵玩自已想玩的東西

# Vue 起步走

## 物件傳參考
- 物件傳參考的特性：https://youtu.be/y1odVMpi6dU

## JavaScript 型別知識
- 原始型別：字串、布林、數字、undefined、null...
- 物件型別：物件、陣列、函式
> Javascript 有沒有"function" 型別
> 沒有
- 物件型別有傳參考的問題

## 物件參考特性
#### ex:one:
```javascript=
物件：function, array, object
var text = '一段文字';
var text2 = text;
text2 = '更新文字'; // 純值原始型別
console.log(text === text2);
```
- `false`
- text2 重新賦值後，和原來的值沒有關聯性

#### ex:two:
```javascript=
  var a = {
    name: 'a',
  }
  var b = {
    name: 'a',
  }
  console.log(a === b);
```
- `false`
- 2個一模一樣的物件
- 宣告物件時，每一組 `{}` 都會產生新的記憶體空間![](https://i.imgur.com/dRkwKaO.png)
- 比對的時候，是比對記憶體位置

#### ex:three:
```javascript=
  var a = {
    name: 'a',
  }
  var b = a;
  b.name = 'b';
  console.log(a.name);
```
- b 指向 a 物件
- 把 b 中的 name 改成 b
- ` console.log(a.name);` = **b**

#### ex:four:
```javascript=
var a = {
    name: 'a',
  }
  var b = a;
  b.name = 'b';
  console.log(a === b);
```
- true
- 只有宣告一個物件
- 2個變數都指向同一個物件
- 修改同一個記憶體空間的 nmae![](https://i.imgur.com/yWDTNuD.png)

#### ex:five:

```javascript=
  var a = {
    name: 'a',
  }

  function changeData(param) {
    param.name = 'b'
    return param;
  }
  var b = changeData(a);
  console.log(a === b);
```

- 程式執行流程
  ![](https://i.imgur.com/CkOj3b8.png)
  ---
  ![](https://i.imgur.com/cw2ksVe.png)
  ---
- 實戰中可能會踩到的雷點
- true
- **ESLint**建議不要調整傳入的物件屬性，會改到原始值
- 透過參數傳遞，傳參考特性不會變，這幾個是一樣的![](https://i.imgur.com/Jzgvj18.png)
- 從頭到尾只有建立一次物件
:::success
所有的函式執行一定會 return，沒有加 return，也會 return undefined
:::

#### ex:six:
```javascript=
  var a = {
    name: 'a',
  }

  function changeData(param) {
    return {
      name: param.name
    }
  }
  var b = changeData(a);
  console.log(a === b);
```
- 把參數帶進物件，return 物件 
![](https://i.imgur.com/eIdKdDj.png)
- 實戰應用面
- fasle
- 有大括號就會建立新物件
![](https://i.imgur.com/5q83BZz.png)
- 這2個物件沒有關聯性

## 複製物件
### 淺層複製
```javascript=
  var a = {
    name: 'a',
  }

  function changeData(param) {
    // 方法一：Object assign
    // const newData = Object.assign( {}, param);
    // console.log('Object.assign', newData); // 淺層複製

    // 方法二：展開
    const newData = { ...param };
    console.log('Spread syntax', newData); // 淺層複製
    return newData;
  }
  var b = changeData(a);
  console.log(a === b);
```
#### `Object.assign()`
- 宣告一個新物件，把第2個參數的值展開過來![](https://i.imgur.com/5g7FnEI.png)

#### `const newData = { ...param };`
- 寫法比較簡潔
- 和 `Object.assign()` 的結果一樣

### 淺層拷貝觀念
- 展開背後做了什麼
  ![](https://i.imgur.com/8ipfrWI.png)
- 第二層的物件並沒有複製，只有指向而已
  ![](https://i.imgur.com/BSRGlcd.png)
- 實戰中很常遇到
- 記憶體空間不相同時，修改值才不會誤改
- 第一層物件，a,b 不相同
![](https://i.imgur.com/an9Jdm6.png)
  
  ![](https://i.imgur.com/WbfTZqi.png)
  ---
- 深層物件是指向的，並沒有複製![](https://i.imgur.com/RqVkszj.png)
- 深層物件比對，得到相等的結果
  ![](https://i.imgur.com/b8RLPAc.png)

  ![](https://i.imgur.com/ms53uuk.png)
  ---
- 修改會改到被參考的物件

:::success
陣列、函式都是物件
物件要有幾層，就有幾層
:::


### 深層複制
#### 原生 JS 寫法
```javascript=
  var a = {
    name: 'a',
  }

  function changeData(param) {
    const newData = JSON.parse( JSON.stringify(param) );
    console.log('stringify', newData); // 淺層複製
    return newData;
  }
  var b = changeData(a);
  console.log(a === b);
```

#### 超雷無限深層範例
```javascript=
const z = {
    z: '123'
}
z.z = z;
console.log(z);
```
- `console.log()`
![](https://i.imgur.com/nfCL3M3.png)
- 試著用記憶體指向的方式解釋無限層的原因，加深物件傳參考的觀念

### 深層拷貝觀念
#### 先轉成字串，再轉成物件
```javascript=
const newData = JSON.parse( JSON.stringify(param) );
```

* 下圖為淺層拷貝
![淺層拷貝](https://i.imgur.com/SXHmLWp.png)
- 字串轉成物件時，會宣告新的記憶體位置
- 為什麼會有淺層拷貝，因為淺層的 code 比較好寫
- 淺層、深層都會用到
- 如果沒有深層物件，用淺層拷貝就可以

### 物件製範例

#### ex0
```javascript=
  <script>
  const person1 = {
    name: '小明',
  }

  const person2 = person1;
  person2.name = '小美';

  console.log(person1.name); // 值為 小明 or 小美
```
- 小美

#### ex1
```javascript=
  const person1 = {
    name: '小明',
  }

  const person2 = person1;
  person2 = {
    name: '小美',
  }

  console.log(person1.name); // 值為 小明 or 小美
```
- error
- 只要建立新物件，參考的位置就不一樣， const 就會出錯![](https://i.imgur.com/khKSjTp.png)

---

```javascript=
      const person1 = {
        name: '小明',
      };

      const person2 = person1;
      person2.name = '小美';

      console.log(person1.name); // 值為 小明 or 小美
```
- 不會出錯
- 小美![](https://i.imgur.com/l0cT1UN.png)
- 參考值不變，可以使用 const

---

```javascript=
  let person1 = {
    name: '小明',
  }

  let person2 = person1;
  person2 = {
    name: '小美',
  }

  console.log(person1.name); // 值為 小明 or 小美
```
- 用 `let` 宣告，eslint 無法通過，值沒有重新指派，要用 `const`

#### ex2
```javascript=

  const person1 = {
    name: '小明',
  }

  function fn(item) {
    item.name = '小美';
  }

  fn(person1);
  console.log(person1.name); // 值為 小明 or 小美
```
- 和上一題一樣
- 透過參數改變值
- 小美

#### ex3(機車題)
```javascript=
const person1 = {
name: '小明',
}

function fn(item) {
item = {};
item.name = '小美';
}

fn(person1);
console.log(person1.name); // 值為 小明 or 小美

```
- 小明
- 從頭到尾只有1個 person1，先把參數傳進來，再把參數指向新物件，再把物件裡面的 name 換掉![](https://i.imgur.com/pepxHAo.png)
- 指向 `{}`，就和原來的 `person1` 沒有關係了，所以不會改到原來的 `person1`


#### ex4(小樂高)
```javascript=
let person1 = {
    name: '小明',
}
let person2;

function fn(item){
    person2 = person1; //此段如何修改，可以避免最後的值變成小美
    person2.name = '小美';
}

fn(person1);
console.log(person1.name);
```
- `person2 = JSON.parse(JSON.stringify(person1));`
- `person2 = { ...person1 };`
- 使用展開或深拷貝

#### ⚡⚡ex大樂高(複製物件時可能會遇到)
```javascript=
  const people = [
      {
        name: '卡斯伯',
        like: '鍋燒意麵',
        price: 95,
        imageUrl: 'https://images.unsplash.com/photo-1569562211093-4ed0d0758f12?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80'
      },
      {
        name: '瑞',
        like: '炒麵',
        price: 80,
        imageUrl: 'https://images.unsplash.com/photo-1612929633738-8fe44f7ec841?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8ZnJpZWQlMjBub29kbGVzfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=400&q=60'
      },
      {
        name: '小明',
        like: '黑胡椒燴飯',
        price: 120,
        imageUrl: 'https://images.unsplash.com/photo-1637362520022-81292a4bff4b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80'
      },
      {
        name: '喬伊',
        like: '生菜沙拉',
        price: 80,
        imageUrl: 'https://images.unsplash.com/photo-1505253716362-afaea1d3d1af?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80'
      }
    ];

    people.forEach((person) => {
      if (person.name === '卡斯伯') {
        person = {
          name: '杰倫',
          like: '香菜',
          price: 180,
        }
      }
    });

    console.log(people); // Ｑ：請問卡斯伯是否有被替換？
```
- 沒有被替換，因為 person 指向新物件
- 流程示意圖![](https://i.imgur.com/6HNk3bw.png)
- 只要看到大括號，就會產生新的參考位置
- 正確寫法
    ```javascript=
          people.forEach((person, key) => {
            if (person.name === '卡斯伯') {
              people[key] = {
                name: '杰倫',
                like: '香菜',
                price: 180,
              };
              // person = {
              //   name: '杰倫',
              //   like: '香菜',
              //   price: 180,
              // };
            }
          });
    ```
    ![](https://i.imgur.com/i7o9IcL.png)


# MVVM
## Vue 起手式

<iframe src="https://codesandbox.io/embed/v-init-2022-dgyjy?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="v-init 2022"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

- option api，和 vue2 幾乎一模一樣
- data, function return
- methods, 物件
- mounted，函式
- ⚡⚡常見結構，Vue 初始化
    ```javascript=
      const app = Vue.createApp({
        data() {
          return {
            ...data
          };
        },
        methods: {},
        mounted() {}
      });

      const data = {...};
    ```
- 實體化，`app.mount("#app");`
- 生命週期，`mounted` 最常用

#### Option Api
- 簡單、易學
- 資料、方法、生命週期已經分類做好了
- 較好理解

#### Composition Api
- 進階使用
- 需要熟悉 ES6
- 沒有分類，code 內容靠經驗分類
- 官方 doc 提到不適合初學者

#### 加入元件的方式
- `app.component();`

## Vue 藍圖方法
![](https://i.imgur.com/yKWaagp.png)

- Vue 用藍圖方法建立元件
- 元件內有資料、方法、生命週期…等等
- 使用 `createApp()` 把元件建立起來
- 使用 `createApp()` 建立的元件稱為==根元件==
- 網頁上有許多區塊，Vue 通常會掛載在 `#app` (最外層的 dom 元素)上，用 `mount` 的方法掛載上來
- 根元件內可以插入許多子元件

### 雙向綁定
#### 在 `mounted` 中觀察 `this`
```javascript=
mounted() {
  console.log(this);
}
```
![](https://i.imgur.com/MEk82Gz.png)
- `Proxy`，Vue3 的新機制，效能非常好，會對內容的資料監控，當值有變動時，會重新渲染到畫面上

#### 專注在資料渲染
![](https://i.imgur.com/vvGCtok.png)
- 在方法或生命週期中調整資料
- 資料變動，就會重新渲染
- MVVM概念
    - 當資料變動時，能直接渲染至html畫面上
    - 資料的調整可透過「方法」、「生命週期」
    - 使用Vue時，可專注在資料處理，不需要花太多時間解決資料渲染

#### 從畫面調整資料
- 透過指令v-model雙向綁定後，可直接調整資料內容
![](https://i.imgur.com/cG2fP9q.png)

:::success
#### ⚡⚡學習等級
- 第1級：僅有閱讀
- 第2級：動手操作
- 第3級：自已寫一次
- 第4級：用自已的方式做成筆記
    - 小錯誤 ok der
    - 沒有時間，之後會更沒有時間
- 儘量做到 3、4
:::

### 指令

<iframe src="https://codesandbox.io/embed/v-init-2022-dgyjy?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="v-init 2022"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>
   
#### Vue 常用指令
- [常用指令](https://hackmd.io/@hexschool/S1DJeKTdL/%2FRhud3_1PR9qv1RJyMfwUmA)
![](https://i.imgur.com/z0wRWkU.png)


#### `v-model`，雙向綁定
- vue-devtools 如果沒出現，重新開一個分頁
- ⚡⚡養成習慣，預先定義資料，避免一開始就報錯

#### 方法寫在 `methods` 裡
- 在 `methods` 定義方法
- 在 button `v-on:click` 觸發方法，修改資料

#### 元件和指令之間的關聯性
![](https://i.imgur.com/Lf5lr5j.png)
- 全部的集合都是物件，只有 data 是 function return，因為 Vue 要做元件化
- 方法會立刻執行
- 生命週期符合條件，會立刻觸發，只會觸發一次


#### 指令分三大類
![](https://i.imgur.com/rfcyi6E.png)

- 雙向綁定，`v-model`，可以讀出、寫入資料
- 渲染方法，把資料讀出來，呈現到畫面上
    - `{{...}}`
    - `v-for`，`v-show`，`v-text`
- 事件綁定，`v-on`
    - 觸發方法，修改資料，重新渲染畫面

### 取出多筆資料，呈現在 `ul` 上

```javascript=
 <ul>
    <li v-for="(item,i) in list" v-bind:key="item.name">
      {{i}} / {{item}}
    </li>
 </ul>
```
- v-for 沒綁 key 的 error 看 [常用指令](https://hackmd.io/@hexschool/S1DJeKTdL/%2FRhud3_1PR9qv1RJyMfwUmA)
- `v-bind:key` 不建議帶 `v-for` 的第2個參數
- 通常帶 id，data 裡沒 id ，可以這樣做，想辦法 bind 獨一無二的值![](https://i.imgur.com/s9bDFBi.png)

### html 屬性運用
- `v-bind:src=""`
#### 指令縮寫
- `v-bind` → `:`
- `v-on:` → `@`

### 判斷式
- button，記得加上 `type="button"`
- ask:事件觸發要用什麼方法
    - `v-on`
- 修改 `isShow` 的 true、false 可以怎麼做
    - `@click` 後面可以加入表達式
    - `==` 是判斷式
- 指令儘量練習縮寫

#### `v-if`，toggle 效果
```javascript=
  <button type="button" @click="isShow=!isShow">{{isShow}}</button>
  <div v-if="isShow">這段要顯示</div>
  <div v-else>這段不要顯示</div>
```

#### 指令不熟
- 去玩一下每日任務
- 表單應用滿重要的



# Options API 簡述

## opening
- 我理解很多人沒有時間
- 畢竟，業界不會管你有沒有時間
- 作業 Lv1：補上註解
- 參考範例 code
- 我們尾牙要取消了
- 其實我們對員工不錯吧, array
- 每人一台 mac
- 獎金暑假、年尾各一次，加年終
- 每週三全遠端👀
- 實際上有一點血汗

## computed & watch

### 如何區分 computed & watch
>  除了生命週期外，其它(data、methods)都是集合
* computed 監聽多個資料
* watch 監聽單一資料


### :one: computed
![](https://i.imgur.com/hcMAXnZ.png)
- 把資料撈出來，重新運算，回傳到畫面上
- 監聽多個資料
- computed 不會更動到資料，只撈資料、渲染到畫面上。
- 優點:自動監聽data()內值的變動，自動運行

#### computed sample
<iframe src="https://codesandbox.io/embed/computed-sample-g60yg?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="computed-sample"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>
   
- .sort()會帶出前一個值與後一個值用年齡或價格排序
- 主要調整的內容，people
- 目的：把 people 讀出來，重新運算，渲染到畫面上
- 閒聊歸閒聊，苓膏龜苓膏，課程還是要上

#### 加入升降冪
- 用 computed 中的 `sortPeople` 取代原本 data 的 `people`
- 在 `sortPeople` 中重新排序
- 排序的陣列方法，`.sort()`
- 使用三元運算子，`return ascending ? 真的結果 : 假的結果`
- 取資料時記得加 `this`![](https://i.imgur.com/i1KXARj.png)

#### 加入價格、年齡排序
- 用 `this.sortBy` 取代原本的 `name`
- 修改 `data` 中的 `sortBy`
    ```javascript=
        <thead>
          <tr>
            <th>姓名</th>
            <th @click="sortBy = 'age'">年齡</th>
            <th>喜好</th>
            <th @click="sortBy = 'price'">價格</th>
          </tr>
        </thead>
    ```
- `computed` 只有把資料讀出來，沒有寫入
- `computed` 會對使用到的變數進行監聽，監聽到變動會再觸發函式，只要有 `this` 都是會被監聽的
    ```javascript=
            computed: {
              // #1 先完成價格排序
              // #2 加入 升降冪
              // #3 加入價格、年齡的排序
              sortPeople() {
                const newPeople = this.people.sort((a, b) => {
                  return this.ascending
                    ? b[this.sortBy] - a[this.sortBy]
                    : a[this.sortBy] - b[this.sortBy];
                });
                return this.people;
              }
            }
    ```
- `mounted` 只會觸發一次，`computed` 只要變數有變動就會觸發
- 監聽是針對資料監聽

#### :A: getter

- 一偵測到資料集的內容產生更動，就同步觸發 computed 內的函式一次。
- 因為上述特性，computed 很容易拿來做搜尋功能。

#### :B: setter
> 待補

### :two: watch
![](https://i.imgur.com/UTymwsp.png)

- 重新調整資料內容，並渲染到畫面上
- 能更動資料內容
- 主要監聽data()資料中**單一的**值，當值有變化，就觸發 methods。
- 觸發 methods 後，watch 可**修改 Data 資料內容**，再把資料渲染到畫面上，或重新取得遠端的值。
- watch 不會產生新的值。

#### watch example
<iframe src="https://codesandbox.io/embed/watch-sample-l1jzk?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="watch sample"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>
   
- watch內的 `方法名稱` 和 `data()資料` 同名，就會被監聽
![](https://i.imgur.com/Jn9yfa6.png)
- [random user](https://randomuser.me/)
    - [seeds](https://randomuser.me/documentation#seeds)
    - 固定的 `seed` 會取出固定的結果
- 第1個參數是 **當前值**，第2個參數是 **前一個值**
- seed 值變動的時候會去取遠端的資料

### debounce 技巧 與 watch
⚡⚡防止 watch 時 call爆 api

###### tags: debounce

lodash，為老牌函式庫
[lodash - debounce](https://lodash.com/docs/4.17.15#debounce)

- 會有持續監聽的行為，但不會立即做反應
(連續觸發時不會有作用，有一個延遲反映的時間)
- JS中加入 lodash 的 debounce
`import { debounce } from "https://cdn.jsdelivr.net/npm/@esm-bundle/lodash@4.17.21/esm/index.js";`
- 有的搜尋工具會分2層，第1層先找 localstorage，debounce 時間到才去打 api
- 💡debounce 裡改傳統函式，`this` 才能正確指向
    ```javascript=
    watch: {
              text(current, old) {
                // #1 使用 Watch 取得當前值及舊有值
                console.log(current, old);
              },

              // debounce
              seed: debounce(function (current) {
                console.log(current);

                axios
                  .get(`https://randomuser.me/api/?seed=${current}`)
                  .then((res) => {
                    console.log(res.data);

                    this.people = res.data;
                  });
              }, 1000)
    ```
    - https://stackoverflow.com/questions/45178621/how-to-correctly-use-vue-js-watch-with-lodash-debounce

#### watch test1 如何在 text 文字更新時，觸發 fn 函式
<iframe src="https://codesandbox.io/embed/watch-test-1-n9ikv?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="watch test 1"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>
   



### :three: methods
methods 本身是一個大物件，內層可以包很多函式，是主動觸發的事件。
#### 觸發方式：
- 指令觸發 (點擊)
- 由其他 options API 觸發 methods

`備註` 以上三者皆為 options API，在操作中會大量使用 this，因此盡量不要在 options API 中直接使用箭頭函式，this 的指向會出錯。


## 卡老師叮嚀
![](https://i.imgur.com/Bu7wZpx.png)
- 儘量參與作業討論
- 用3~4個月，抵過1年
- 第4周滿難的
- 過年把第4周做完
- 第4周以後難度會上升很多
- 熟悉 github pages
- 裝 nodejs
- 建議裝 14版
- 16版移除了一些語法，有一些套件不支援

### nodejs 相關
- [安裝 nvm 環境](https://wcc723.github.io/development/2022/01/10/install-nvm/)
- [無法辨識 'XXX' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱](https://hsiangfeng.github.io/nodejs/20190801/449913843/)
- [nvm安裝](https://www.796t.com/article.php?id=418016)
- [顯示 Mac 隱藏檔案 的三個方法](https://macuknow.com/2017/08/26/1428/%e4%b8%89%e6%8b%9b%e8%ae%93-mac-%e9%a1%af%e7%a4%ba%e5%87%ba%e9%9a%b1%e8%97%8f%e6%aa%94%e6%a1%88/)
- 前端必裝工具
- JavaScript 環境
- 可以跑後端
- `nove -v`
- iterm2 主題![](https://i.imgur.com/lRI3iHC.png)


# Bootstrap JS
## Alerts Dismissing
- [Component Alerts Dismissing](https://getbootstrap.com/docs/5.0/components/alerts/#dismissing)
- [Component Alerts Dismissing(中文)](https://bootstrap5.hexschool.com/docs/5.1/components/alerts/#dismissing)
- alert 元件可以透過叉叉關閉
- `data-bs` 開頭的屬性，bootstrap 專屬的屬性
- `data-bs-dismiss="alert"`，移除 alert

## Modal
- [Modal live demo](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#live-demo)
- `data-bs-toggle="modal"`，切換 modal![](https://i.imgur.com/QE0neQ5.png)
- `data-bs-target="#exampleModal"`，用 `id` 選擇目標![](https://i.imgur.com/umd2bJp.png)

## 如何使用 JS 操作元件
<iframe src="https://codesandbox.io/embed/vue-bootstrap5-001-bdxqu?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Vue Bootstrap5 001"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

- 拿掉 button 中的 `data-bs` 屬性
    ```javascript=
        <button type="button" class="btn btn-primary" id="modalBtn">
          Launch demo modal
        </button>
    ```
- [modal usuage](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#usage)
    - 透過資料屬性
    - 透過 JavaScript
        - `var myModal = new bootstrap.Modal(document.getElementById('myModal'), options)`
        - modal 實體化
        - 選擇 dom 元素
        - [options](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#options)，相關選項
        - [modal 相關方法](https://bootstrap5.hexschool.com/docs/5.1/components/modal/#methods)
            - show
            - hide

## 在 Vue 中使用 bootstrap modal
<iframe src="https://codesandbox.io/embed/vue-bootstrap5-003-zxii4?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="Vue Bootstrap5 003"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

- 在 `mounted` 擷取 dom 元素


## 討論串
- [討論串](https://discord.com/channels/801807326054055996/905656987583397908)

