<<玩轉 vue3>>筆記(1)

前言

學習前端勢在必行, 這裏是一個初學者(只寫過 JS+BootStrap)學習 vue3 的學習筆記, 課程在 玩轉 Vue 3 全家桶 (geekbang.org)

爲什麼是 vue3

前端發展歷程

  • 純靜態網頁: 純靜態, 手動更新頁面
  • 模板語言網頁: 直接嵌入後端數據, 數據更新需要重新刷新頁面
  • ajax: 前端異步獲取數據並動態刷新
  • jquery+bootStrap: 通過 jquery 操作 dom, bootStrap 做基本的響應式和柵格處理
  • angularJs+nodeJs: MVVM 模式, 前端可以入侵到後端, 數據驅動頁面, 數據變化則頁面自動變化, 而不需要操作 dom, 開發者只需要關注數據的變化, 對 dom 的修改由框架完成
  • 百花齊放: angularJs/vue/react.....

三大框架

  • angularJs
  • vue
  • react

實現原理

  • angular : 髒檢查, 每次交互都檢查數據是否變化, 從而更新 dom
  • vue: 響應式, 對於每個需要變化的數據都建立一個 watcher 監聽數據的屬性, 有變化時才通知修改對應的 dom
  • react: 虛擬 dom, 通過 js 來生成虛擬的 dom, diff 檢測數據更新直接更改虛擬 dom, 更快速

vue 和 angular 區別

  • angular 通過 diff 來自己進行數據變化的感知, vue 框架本身在數據變化時會主動通知

vue 和 react 區別

vue爲了實現數據感知, 需要在框架內生成若干 watcher, 數量多就影響性能.
react 生成虛擬 dom, 每次需要對虛擬 dom 進行 diff 來得知數據變化, 當虛擬 dom 很大則影響性能

  • react 使用將虛擬 dom 分片的方式將 dom 切片, 在瀏覽器空閒時再進行 diff, 每次計算一片, 當瀏覽器需要計算時下次讓給瀏覽器, 解決卡頓
  • vue2 引入了虛擬 dom, 取 react 之長, 對於組件之間的變化, 才通過 watcher 通知, 對於組件內的變化, 通過虛擬 dom 來更新, 組件數量不會很多, 解決了 watcher 多導致的性能問題, 同時, 每個組件都單獨的虛擬 dom, 也避免虛擬 dom 大導致的性能問題
  • react 將 jsx 編譯成 js 執行, 所以語法都是 js 本身的語法和特徵
  • vue 是自己的語法, vue 通過語法檢測數據是否是需要監聽的, 在 vue3中做到了極致, vue3對代碼進行精準的分類, 只有需要監聽的數據更改才進行虛擬 dom 的修改, 不需要監聽的數據, 越過了虛擬 dom 檢測, 速度更快

小知識

Q: vue 在引入虛擬 dom 後, 需不需要 react 的分片來提高性能?
A: 不需要, vue 的虛擬 dom 是組件級別, 所以虛擬 dom 不大, 進行 diff 不會有性能問題

清單應用

編寫一個清單應用, 類似於各種 TODO 程序, 用戶在輸入框中寫要做的事情, 回車會加入到下面的若干 TODO 列表中, 同時列表某一條可以點擊完成, 呈現出不同的效果

思想的轉變

接觸過傳統的 JS+BootStrop 的開發者來講, 可能對於這種需求, 思路是找到輸入框並進行監聽, 當用戶輸入後, 先在 dom 中找到對應元素, 然後進行修改dom, 這種思路需要轉變
對於 MVVM 方式的框架來講, 我們只需要關注數據是怎麼變化, 而不是 dom 怎麼操作

列表的加載

我們先進行數據的展示, 之前說過, 我們只關注數據, 在 vue 中, 我們可以在 dom 中使用 {{}} 來進行已定義的數據展示, 使用 v-for可以循環列表類型的數據

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos"> {{todo}} </li>
        </ul>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                todos: ["喫飯", "睡覺"],
            }
        },
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>

</html>

數據的追加

MVVM 中, 數據的修改, 框架會自己監聽並重新渲染 dom
使用@keydown.enter監聽鍵盤迴車事件, 執行方法addTodo
在這裏, 你會發現, 當數據 todos 進行變化後, 相對應的 dom 會自己發生變化, 這就是 MVVM 的奇妙之處, 數據驅動頁面

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos"> {{todo}} </li>
        </ul>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: ["喫飯", "睡覺"],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === ""){
                    return
                }
                this.todos.push(this.title)  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>

</html>

添加完成按鈕和效果

TODO 都是可以進行選中完成的, 我們也要實現, 爲了給每個 todo 添加是否完成的標識, 我們將 todo 的類型從字符串變成對象
還是那句話, 數據驅動頁面, 當我們的 checkbox 的 checked 屬性發生變化時, 也會重新渲染對應的 dom, 導致 class 也動態的增減

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

添加統計

在頁面添加未完成的數量和全部數量

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
        <!-- 在 html 中寫代碼的方式雖然可行, 但是不推薦!, 並且性能不好! -->
        <div>
            <!-- .filter 對 todos 進行篩選, () 內是篩選規則, v 是每個 todo, 如果爲 true 則此 v 篩選通過 -->
            <!-- 這裏統計未完成數量, 因此對 v.done 進行反轉, 如果 done 爲 true, 反轉爲 false, 篩選失敗, 反之成功 -->
            {{todos.filter(v=>!v.done).length}}
            /
            <!-- .length 計算列表長度 -->
            {{todos.length}}
        </div>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

如同註釋所說, 雖然在html 中編寫代碼的方式可以實現, 但是並不推薦, 有如下問題:

  • 把 JS 代碼放置進了 body 中, 不美觀
  • 使用 vue 的計算屬性功能, 會將結果進行緩存優化(當這個屬性在多地方使用時, 只計算一次), 避免每次的重複計算導致性能問題
    下面, 我們使用計算屬性功能來完成統計
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
        <div>
            <!-- 直接調用計算屬性 -->
            {{active}}/{{all}}
        </div>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            }
        },
        // computed 設置 vue 的計算屬性
        computed: {
            // active 屬性, 返回 done 爲 false 的長度
            active() {
                return this.todos.filter(v => !v.done).length
            },
            // all 屬性, 返回總長度
            all() {
                return this.todos.length
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

全選

我們需要加入一個全選框, 當點擊全選時, 所有todo 都選中, 並且, 當手動將所有 todo 選中時, 全選框默認變爲勾選狀態
計算屬性, 不止可以用來做數據統計, 也可以修改對應的數據, 例如

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <div>
            <!-- checkbox 綁定 allDone 計算屬性, 生成時調用 get 方法, 返回 bool, 作爲 checkbox 的 checked 使用 -->
            <!-- 當點擊 checkbox 時, 計算屬性發生變化, 將 checked 作爲參數調用 allDone 的 set 方法 -->
            全選<input type="checkbox" v-model="allDone">
            <span>{{active}}/{{all}}</span>
        </div>
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
        <div>
            <!-- 直接調用計算屬性 -->
            {{active}}/{{all}}
        </div>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            }
        },
        // computed 設置 vue 的計算屬性
        computed: {
            // active 屬性, 返回 done 爲 false 的長度
            active() {
                return this.todos.filter(v => !v.done).length
            },
            // all 屬性, 返回總長度
            all() {
                return this.todos.length
            },
            // 計算屬性爲對象時, 可以通過定義 get 和 set 方法來修改屬性
            allDone: {
                // get 在獲取計算屬性時觸發
                get() {
                    // 調用 this.active 計算屬性, active 返回的是 done 爲 false 的長度, 這裏與0進行比較, 爲0代表全部勾選, 返回 true
                    return this.active === 0
                },
                // set 在計算屬性發生變化時觸發
                set(val) {
                    console.log(val)
                    // forEach 是循環
                    // 循環 todos , 將每個 done 設置爲 val
                    this.todos.forEach(todo => {
                        todo.done = val
                    });
                }
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

清空按鈕

快完成了, 我們添加一個清空按鈕在輸入框之後, 要求點擊清空則將所有已完成條目刪除, 而當沒有已完成條目時, 按鈕不展示
使用 v-if 來進行條件篩選渲染

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <!-- v-if 作爲條件篩選, 當值爲 true 才顯示該 dom -->
        <!-- @click 在鼠標點擊時觸發, 這裏調用 clear 方法 -->
        <button v-if="active<all" @click="clear">清理</button>
        <div>
            <!-- checkbox 綁定 allDone 計算屬性, 生成時調用 get 方法, 返回 bool, 作爲 checkbox 的 checked 使用 -->
            <!-- 當點擊 checkbox 時, 計算屬性發生變化, 將 checked 作爲參數調用 allDone 的 set 方法 -->
            全選<input type="checkbox" v-model="allDone">
            <span>{{active}}/{{all}}</span>
        </div>
        <ul>
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
        <div>
            <!-- 直接調用計算屬性 -->
            {{active}}/{{all}}
        </div>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            },
            clear() {
                // 將 todos 賦值爲老的 todos 中 done 爲 false 的部分
                // 去除了已經完成的條目
                this.todos = this.todos.filter(v => !v.done)
            }
        },
        // computed 設置 vue 的計算屬性
        computed: {
            // active 屬性, 返回 done 爲 false 的長度
            active() {
                return this.todos.filter(v => !v.done).length
            },
            // all 屬性, 返回總長度
            all() {
                return this.todos.length
            },
            // 計算屬性爲對象時, 可以通過定義 get 和 set 方法來修改屬性
            allDone: {
                // get 在獲取計算屬性時觸發
                get() {
                    // 調用 this.active 計算屬性, active 返回的是 done 爲 false 的長度, 這裏與0進行比較, 爲0代表全部勾選, 返回 true
                    return this.active === 0
                },
                // set 在計算屬性發生變化時觸發
                set(val) {
                    console.log(val)
                    // forEach 是循環
                    // 循環 todos , 將每個 done 設置爲 val
                    this.todos.forEach(todo => {
                        todo.done = val
                    });
                }
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

添加空置提示

最後一步!, 我們爲這個清單應用添加最後一個功能: 當清單爲空時, 展示目前沒有數據
我們這裏搭配使用 v-ifv-else, 來做分支處理, 當然還可以搭配 v-else-if 針對多分支處理

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 監聽變量 title 的值-->
        <h2>{{title}}</h2>
        <!-- v-model 將 input 值綁定到變量 title -->
        <!-- @keydown.enter 在回車鍵時觸發方法 addTodo -->
        <input type="text" v-model="title" @keydown.enter="addTodo">
        <!-- v-if 作爲條件篩選, 當值爲 true 才顯示該 dom -->
        <!-- @click 在鼠標點擊時觸發, 這裏調用 clear 方法 -->
        <button v-if="active<all" @click="clear">清理</button>
        <div>
            <!-- checkbox 綁定 allDone 計算屬性, 生成時調用 get 方法, 返回 bool, 作爲 checkbox 的 checked 使用 -->
            <!-- 當點擊 checkbox 時, 計算屬性發生變化, 將 checked 作爲參數調用 allDone 的 set 方法 -->
            全選<input type="checkbox" v-model="allDone">
            <span>{{active}}/{{all}}</span>
        </div>
        <!-- 如果長度不爲0代表目前有數據, 展示數據 -->
        <ul v-if="all!==0">
            <!-- v-for 循環 todos, 每個值叫 todo, li 的值爲每個 todo -->
            <li v-for="todo in todos">
                <!-- 當 type 爲 checkbox 時, v-mode 綁定的變量如果是數組時, 作爲 checkbox 的 value 使用, 當爲 bool 時, 作爲 checkbox 的 checked 值使用 -->
                <!-- 使用 todo.done 屬性標識是否選中 -->
                <input type="checkbox" v-model="todo.done">
                <!-- vue 中, : 用來傳遞數據, 這裏的意思是, 如果 todo.done 爲真, 則將 css done 傳遞給 class -->
                <span :class="{done:todo.done}">{{todo.text}}</span>
            </li>
        </ul>
        <!-- v-else 必須緊跟在 v-if 的 dom 之後, 當 if 不通過執行渲染 else 內容 -->
        <div v-else>
            暫無數據
        </div>
        <div>
            <!-- 直接調用計算屬性 -->
            {{active}}/{{all}}
        </div>
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        // data() 返回監聽的變量
        data() {
            return {
                title: "",
                todos: [
                    { done: false, text: "喫飯" },
                    { done: true, text: "睡覺" }
                ],
            }
        },
        // methods 設置方法
        methods: {
            // 設置 addTodo 方法
            addTodo() {
                // this 指的是自己, 也就是 App
                // 如果 title 爲空, 不添加, 目的是防止無輸入直接回車
                if (this.title === "") {
                    return
                }
                this.todos.push({
                    done: false,
                    text: this.title
                })  // 給 todos 列表新增一個值爲用戶輸入的 title 值
                this.title = ""  // 將 title 設置爲空
            },
            clear() {
                // 將 todos 賦值爲老的 todos 中 done 爲 false 的部分
                // 去除了已經完成的條目
                this.todos = this.todos.filter(v => !v.done)
            }
        },
        // computed 設置 vue 的計算屬性
        computed: {
            // active 屬性, 返回 done 爲 false 的長度
            active() {
                return this.todos.filter(v => !v.done).length
            },
            // all 屬性, 返回總長度
            all() {
                return this.todos.length
            },
            // 計算屬性爲對象時, 可以通過定義 get 和 set 方法來修改屬性
            allDone: {
                // get 在獲取計算屬性時觸發
                get() {
                    // 調用 this.active 計算屬性, active 返回的是 done 爲 false 的長度, 這裏與0進行比較, 爲0代表全部勾選, 返回 true
                    return this.active === 0
                },
                // set 在計算屬性發生變化時觸發
                set(val) {
                    console.log(val)
                    // forEach 是循環
                    // 循環 todos , 將每個 done 設置爲 val
                    this.todos.forEach(todo => {
                        todo.done = val
                    });
                }
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>
<style>
    /* 顏色變灰, 中間加劃線 */
    .done {
        color: gray;
        text-decoration: line-through;
    }
</style>

</html>

初探 vue3 新特性

相比於 vue2, vue3 的優勢是什麼?

vue2 的歷史遺留問題

  • vue2 使用 Flow.js 作爲類型校驗, 但是Flow.js已經停止維護, 整個社區都在使用TypeScript 來構建基礎庫, vue 也需要這樣
  • vue2 內部運行時, 直接執行瀏覽器的 API, 這樣在跨端時, 就需要適配多端,的否則會出現問題
  • vue2 並不是真正意義上的代理(響應式), 而是基於 Object.defineProperty()實現的, 這是對某個屬性進行攔截, 有一些缺陷, 比如無法監聽刪除(vue 使用 $delete 輔助才能達到效果)
  • vue2 使用 OptionApi, 在代碼比較多時, 對於功能的修改, 需要兼顧data, methods代碼塊, 比較麻煩

vue3的新特性

RFC 機制
這與代碼無關, 這是 vue 團隊的開發工作方式, 對於新的功能和語法, 先放置在 github 徵求意見, 任何人都可以討論和嘗試實現
響應式系統
之前說過, vue2使用Object.defineProperty()實現響應式, 而開發者必須將defineProperty監聽的數據明確的寫在代碼中, 這是因爲, defineProperty 對於不存在的屬性無法攔截, 因此必須在data中聲明監聽變量
而 vue3 可以使用 proxy , 他的代碼類似於

    new Proxy(obj, {
        get(){},
        set(){}
    })

proxy對 obj 是什麼屬性不做關心, 統一攔截, 還可以監聽更多的格式數據, 例如Set/Map
需要注意的是, Proxy不支持 IE11 以下的瀏覽器
Proxy是瀏覽器的新特性, 這代表着, 框架隨着更新會和瀏覽器相輔相成, 一起爲前端提供更多可能.
自定義渲染
vue2 內部的模塊都是揉和在一起的, 導致擴展比較困難, 而 vue3 使用流行的 monorepo 的拆包管理方式, 將模塊剝離, 進行解耦.
全部模塊使用 TS 進行重構
TS(TypeScript)帶來了系統類型, 能讓代碼的提示更爲智能, 同時提高代碼的健壯性
這裏博主也推薦大家有空學習一下 TS, 類型註解可以讓代碼的編寫更爲得心應手.
並且 TypeScript 也是目前前端流行的技術, 很多框架都已經使用 TS 來進行底層的編寫
Compostion API
Composition API 作者將他稱之爲組合 API, 從 DEMO 來了解他們的不同
比如我們使用 vue2 來編寫一個累加器, 並且有一個計算屬性顯示累加器 x2的結果

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 點擊觸發 add 函數 count*2, double 計算*2結果 -->
        <h1 @click="add">{{count}} x 2 = {{double}}</h1>  
    </div>
</body>
<script>
    // 建立變量 App
    const App = {
        data(){
            return{
                count: 1
            }
        },
        methods: {
            add(){
                this.count += 1
            }
        },
        computed:{
            double(){
                return this.count * 2
            }
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>


</html>

而在 vue3 裏, 我們可以使用 setup 來寫

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <!-- 點擊觸發 add 函數 count*2, double 計算*2結果 -->
        <h1 @click="add">{{state.count}} x 2 = {{double}}</h1>  
    </div>
</body>
<script>
    // 導入所需的模塊
    const {reactive, computed} = Vue
    // 建立變量 App
    let App = {
        // setup 這裏返回 App 的一些方法和變量等
        setup(){
            // 新建 state 對象
            const state = reactive({
                count: 1  // count 屬性
            })
            // 新建 add 方法
            function add(){
                // count 屬性+1
                state.count += 1
            }
            // 創建 double 的計算屬性
            const double = computed(()=>state.count * 2)
            return {state, add, double}  // 返回到外層
        }
    }
    // 創建 App, 綁定到 id app
    Vue.createApp(App).mount("#app")
</script>


</html>

使用新版的組合 api 之後, 表面看代碼反而繁瑣了, 但是之前的 OptionsAPI 有幾個嚴重的問題:

  • 所有的數據都掛載在 this 裏, 因此對於類型推倒很不友好, 並且在清理和代碼分塊時很難受
  • 新增功能都要修改 data, method 塊, 維護困難
  • 代碼難以複用, 因爲代碼都糅雜在一起, 還可能會出現命名衝突
    而使用組合 API 之後, 好處多多:
  • api 都是通過 import 引入, 不需要的模塊無需引入
  • 將某塊功能所有的 methods/data 封裝在一個獨立的函數中, 複用很容易, 也沒有衝突問題
  • 組合 api 新增的 return 等語句, 在實際項目中可清除
    新增組件
    vue3新增了若干組件, 比如:
  • Fragment: 不再要求有一個唯一的根節點, 清理無用的 div 標籤
  • Teleport: 允許組件渲染在別的元素內, 在開發彈窗組件時特別有用
  • Suspense: 異步請求組件
    Vite
    Vite 跟 vue3 並不是綁定關係, 和 vue 也不是強制綁定, Vite 的競品是 Webpack, 主要提升開發的體驗
    傳統的 webpack, 在打包時, 是將所有的代碼和頁面打包完成再啓動, 可能需要幾分鐘, 而 Vite 是階梯式打包, 按需加載, 這樣大大提升了開發體驗

vue2 如何升級到 vue3

原作者說了很多, 大致是使用一些工具來進行升級
但是我個人認爲, 如果是已經完成或者開發中的 vue2 項目, 不建議升級到 vue3, 我做過一些項目, 我認爲前端組件的版本升級帶來的問題很多, 尤其是不兼容問題. 所以我不建議對已有的項目進行升級
而對於新的項目, 可以使用 vue3來從頭開始

搭建 vue3 項目的第一步

環境準備

你必須首先安裝以下軟件:

  • NodeJs

初始化代碼

找到你的工作目錄, 命令行輸入

npm init @vitejs/app

初始化會讓你輸入項目的名字, 這裏我們輸入 student

➜  Vue npm init @vitejs/app

@vitejs/create-app is deprecated, use npm init vite instead

? Project name: › student

之後選擇 vue 的項目

? Select a framework: › - Use arrow-keys. Return to submit.
    vanilla
❯   vue
    react
    preact
    lit
    svelte

回車後再選擇 vue(javaScript) 或者 vue-ts(typeScript語言), 這裏我們先選擇普通的 vue, 方便學習, 之後學習了 TS 之後, 可以選擇 vue-ts

? Select a variant: › - Use arrow-keys. Return to submit.
❯   vue
    vue-ts

之後就創建成功了項目

Scaffolding project in /Users/chenming/Work/Code/Vue/student...

Done. Now run:

  cd student
  npm install
  npm run dev

而後在當前文件夾就生成了一個新的文件夾 student
我們使用 vscode 打開 student 目錄
現在目錄下就會生成若干文件

➜  student tree
.
├── README.md
├── index.html     入口文件
├── package.json
├── public   資源文件夾
│   └── favicon.ico
├── src   源碼
│   ├── App.vue     組件
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── main.js   入口
└── vite.config.js   vite 配置

4 directories, 9 files

運行

此時腳手架就算搭建完成, 然後我們執行命令來安裝依賴

npm install

執行完命令後, 會在當前路徑下生成新的文件夾node_modules, 裏面存放下載的依賴文件, 通常, 該文件夾不應上傳到代碼倉庫, 而是由開發者本地生成
再執行命令, 在本地啓動 dev 環境

npm run dev

> [email protected] dev
> vite


  vite v2.9.14 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 177ms.

而後瀏覽器打開網址 http://localhost:3000/, 會出現歡迎頁面代表成功
同時, 我們修改文件 src/APP.vue 的內容, 會發現網頁會同步刷新, 這樣就給我們的開發提供了很大的便利
同時, 因爲我們的開發是多頁面, 同時要和後端進行交互, 因此我們還需要安裝兩個組件來幫助我們完成需求, 分別是vuex(管理數據)vue-router(管理路由)

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