Vue 生命周期钩子

Vue 组件生命周期事件包括以下内容:

  • beforeCreate:在组件初始化时运行。数据(data)尚未变为响应式,并且事件未在 DOM 中设置。

  • created:您将能够访问响应式数据和事件,但模板和 DOM 不会挂载或渲染。当从服务器请求异步数据时,这个钩子通常很好用,因为您很可能会在安装虚拟 DOM 之前尽早需要这些信息。

  • beforeMount:一个非常不常见的挂钩,因为它直接在组件的第一次渲染之前运行,并且不会在服务器端渲染中调用。

  • Mounted:挂载钩子是最常用的钩子之一,因为它们允许您访问 DOM 元素,以便集成非 Vue 库。

  • beforeUpdate:在组件发生更改之后、重新渲染之前立即运行。对于在渲染之前获取反应数据的状态很有用。

  • updated:在 beforeUpdate 挂钩之后立即运行,并使用新的数据更改重新渲染组件。

  • beforeDestroy:在销毁组件实例之前直接触发。该组件仍将发挥作用,直到调用已销毁的挂钩为止,从而允许您停止事件侦听器和数据订阅以避免内存泄漏。

  • destroyed:所有虚拟 DOM 元素和事件监听器都已从 Vue 实例中清除。通过此挂钩,您可以将这一情况传达给需要知道此操作已完成的任何人或任何元素。

练习 1.12:使用 Vue 生命周期来控制数据

在本练习中,我们将学习如何以及何时使用 Vue 的生命周期挂钩,以及何时使用 JavaScript alert 触发它们。练习结束时,我们将能够理解和使用多个 Vue 生命周期挂钩。

要访问本练习的代码文件,请参阅 https://packt.live/36N42nT

  1. 打开命令行终端,导航到 Exercise1.12 文件夹,然后按顺序运行以下命令:

    > cd Exercise1.12/
    > code .
    > yarn
    > yarn serve

    请随意将 alert 替换为 console.log()。

  2. 首先创建一个要在列表元素中迭代的数据数组,将键设置为 n,并使用花括号输出 <li> 元素内的值 {{item}}

    <template>
        <div>
            <h1>Vue Lifecycle hooks</h1>
            <ul>
                <li v-for="(item, n) in list" :key="n">
                    {{ item }}
                </li>
            </ul>
        </div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                list: [
                    'Apex Legends',
                    'A Plague Tale: Innocence',
                    'ART SQOOL',
                    'Baba Is You',
                    'Devil May Cry 5',
                    'The Division 2',
                    'Hypnospace Outlaw',
                    'Katana ZERO',
                ],
            }
        }
    }
    </script>
  3. beforeCreated()created() 添加为 data() 函数下方的函数。在这些挂钩中设置 alert 或控制台日志,以便您可以看到它们何时被触发:

    <script>
        export default {
            ...
            beforeCreate() {
                alert('beforeCreate: data is static, thats it')
            },
            created() {
                alert('created: data and events ready, but no DOM')
            },
        }
    </script>

    当您刷新浏览器时,您应该会看到两个警报,然后才能看到页面上加载的列表:

    image 2023 10 11 20 25 43 911
    Figure 1. Figure 1.37: Observe the beforeCreate() hook alert first

    以下屏幕截图显示了 beforeCreate() 挂钩之后的 created() 挂钩警报:

    image 2023 10 11 20 26 38 464
    Figure 2. Figure 1.38: Observe the before() hook alert after the beforeCreate() hook
  4. beforeMount()Mounted() 添加为 created() 挂钩下方的函数。在这些挂钩内设置警报或控制台日志,以便您可以看到它们何时被触发:

    <script>
        export default {
            ...
            beforeMount() {
                alert('beforeMount: $el not ready')
            },
            mounted() {
                alert('mounted: DOM ready to use')
            },
        }
    </script>

    当您刷新浏览器时,您还应该看到这些警报,然后才能看到列表加载到页面上:

    image 2023 10 11 20 28 45 935
    Figure 3. Figure 1.39: Observe the beforeMount() hook alert after the create() hook

    以下屏幕截图显示了 beforeMount() 挂钩之后的 Mounted() 挂钩警报:

    image 2023 10 11 20 29 39 434
    Figure 4. Figure 1.40: Observe alert mounted() hook alert after the beforeMount() hook
  5. 在位于项目输出旁边的 <li> 元素内添加一个新的锚元素。使用 @click 指令将此按钮绑定到名为 deleteItem 的方法,并将 item 值作为参数传递:

    <template>
        <div>
            <h1>Vue Lifecycle hooks</h1>
            <ul>
                <li v-for="(item, n) in list" :key="n">
                    {{ item }} <a @click="deleteItem(item)">Delete</a>
                </li>
            </ul>
        </div>
    </template>
  6. 将名为 deleteItem 的方法添加到挂钩上方、data() 函数下方的 methods 对象中。在此函数内,将 value 作为参数传递,并从列表数组中过滤掉与该值不匹配的项目,然后用新列表替换现有列表:

    Exercise1-12.vue
    <script>
    export default {
      data() {
        return {
          list: [
            'Apex Legends',
            'A Plague Tale: Innocence',
            'ART SQOOL',
            'Baba Is You',
            'Devil May Cry 5',
            'The Division 2',
            'Hypnospace Outlaw',
            'Katana ZERO',
          ],
        }
      },
      methods: {
        deleteItem(value) {
          this.list = this.list.filter(item => item !== value)
        },
      },
  7. 在组件底部的 <style> 标记内添加样式,并将 lang 属性设置为 scss

    <style lang="scss" scoped>
    ul {
      padding-left: 0;
    }
    li {
      display: block;
      list-style: none;
    
      + li {
        margin-top: 10px;
      }
    }
    
    a {
      display: inline-block;
      background: rgb(235, 50, 50);
      padding: 5px 10px;
      border-radius: 10px;
      font-size: 10px;
      color: white;
      text-transform: uppercase;
      text-decoration: none;
    }
    </style>
  8. 添加 beforeUpdate()Updated() 作为 Mounted() 挂钩下方的函数,并在这些挂钩内设置警报或控制台日志,以便您可以看到它们何时被触发:

    <script>
        export default {
            ...
            beforeUpdate() {
                alert('beforeUpdate: we know an update is about to happen, and have the data')
            },
            updated() {
                alert('updated: virtual DOM will update after you click OK')
            },
        }
    </script>

    当您通过单击浏览器中的删除按钮删除列表项时,您应该会看到这些警报。

  9. beforeDestroy()destroyed() 添加为 updated() 挂钩下方的函数。在这些挂钩中设置警报或控制台日志,以便您可以看到它们何时被触发:

    <script>
        export default {
            ...
            beforeDestroy() {
                alert('beforeDestroy: about to blow up this component')
            },
            destroyed() {
                alert('destroyed: this component has been destroyed')
            },
        }
    </script>
  10. 将新项目添加到列表(list)数组中:

    <script>
    export default {
        data() {
            return {
                list: [
                    'Apex Legends',
                    'A Plague Tale: Innocence',
                    'ART SQOOL',
                    'Baba Is You',
                    'Devil May Cry 5',
                    'The Division 2',
                    'Hypnospace Outlaw',
                    'Katana ZERO',
                ],
            }
        },

    在运行 localhost 的情况下保存此更改后,您还应该在浏览器中显示更新警报后看到销毁警报。这将生成以下输出:

    image 2023 10 11 20 42 19 081
    Figure 5. Figure 1.41: Output displaying Vue Lifecycle hooks
  11. 警报将在每个生命周期挂钩处运行。尝试删除元素,在列表数组中添加新元素,然后刷新页面以查看每个钩子何时发生。这将生成如下输出:

image 2023 10 11 20 43 14 166
Figure 6. Figure 1.42: Displaying a message on every trigger

每次操作页面上的某些内容时都会触发警报,展示每个可用的 Vue 生命周期。

Mountedcreated 的生命周期挂钩将在每次组件加载时运行。如果这不是所需的效果,请考虑从父组件或视图中运行您想要运行一次的代码,例如 App.vue 文件。

在本练习中,我们了解了 Vue 生命周期挂钩是什么以及它们何时触发。这与 Vue 组件中的触发方法和控制数据结合起来非常有用。

活动 1.01:使用 Vue.js 构建动态购物清单应用程序

在本活动中,我们将构建一个动态购物清单应用程序,该应用程序将使用 SFC 的所有基本功能(例如表达式、循环、双向绑定和事件处理)来测试您对 Vue 的了解。

该应用程序应允许用户创建和删除单个列表项,并一键清除整个列表。

以下步骤将帮助您完成该活动:

  1. 使用绑定到 v-model 的输入在一个组件中构建交互式表单。

  2. 添加一个可添加购物清单项目的输入字段。通过将方法绑定到 @keyup.enter 事件,允许用户使用 Enter 键添加项目。

  3. 用户可以通过删除所有项目或一次删除一项来清除列表。为此,您可以使用删除方法,该方法可以将数组位置作为参数传递,或者简单地将整个购物清单数据属性覆盖为空数组 []

预期结果如下:

image 2023 10 11 20 47 18 013
Figure 7. Figure 1.43: Final output

解决:

要访问此活动的代码文件,请参阅 https://packt.live/35Tkzau

  1. 通过运行 vue create new-activity-app 命令,使用 Vue CLI 创建一个新的 Vue 项目。通过命令提示符手动选择 dart-sass、babel 和 eslint 的功能。

  2. 用占位符搭建一个输入字段,按 Enter 添加新项,该项有一个绑定到名为 input 的数据对象的 v-model 和一个带有输入值的 ref 属性。通过使用 @keyup.enter 并引用 addItem 方法,将 Enter 键绑定到 addItem 方法,该方法将在下一步中创建:

    <template>
        <div class="container">
            <h2>Shopping list</h2>
            <div class="user-input">
            <input
                placeholder="Press enter to add new item"
                v-model="input"
                @keyup.enter="addItem"
                ref="input"
            />
            </div>
        </div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                input: '',
            }
        },
    }
    </script>
    <style lang="scss">
    @import 'styles/global';
    
    $color-green: #4fc08d;
    $color-grey: #2c3e50;
    
    .container {
        max-width: 600px;
        margin: 80px auto;
    }
    // Type
    .h2 {
        font-size: 21px;
    }
    
    .user-input {
        display: flex;
        align-items: center;
        padding-bottom: 20px;
        input {
            width: 100%;
            padding: 10px 6px;
            margin-right: 10px;
        }
    }
    </style>
  3. 引入一个绑定单击事件的按钮来 addItem,并在 methods 对象中包含相应的方法 addItem()。在 addItem() 方法中,将 data prop 输入字符串推送到 shoppingList 并添加检查以确保 input prop 存在。(可选)为按钮添加一些样式:

    <template>
        <div class="container">
            <h2>Shopping list</h2>
            <div class="user-input">
                <input
                placeholder="Press enter to add new item"
                v-model="input"
                @keyup.enter="addItem"
                ref="input"
                /><button @click="addItem">Add item</button>
            </div>
        </div>
    </template>
    <script>
    export default {
        data() {
            return {
                input: '',
                shoppingList: [],
            }
        },
        methods: {
            addItem() {
                // Don't allow adding to the list if empty
                if (!this.input) return
                this.shoppingList.push(this.input)
                // Clear the input after adding
                this.input = ''
                // Focus the input element again for quick typing!
                this.$refs.input.focus()
            },
        },
    }
    </script>
    <style lang="scss">
    ...
    // Buttons
    button {
        appearance: none;
        padding: 10px;
        font-weight: bold;
        border-radius: 10px;
        border: none;
        background: $color-grey;
        color: white;
        white-space: nowrap;
        + button {
            margin-left: 10px;
        }
    }
    </style>
  4. 输出 DOM 中的购物清单项目。当您单击 “添加商品” 按钮时,它应该被添加到 shoppingList 并显示:

    <template>
        <div class="container">
        ...
        <ul v-if="shoppingList">
            <li v-for="(item, i) in shoppingList" :key="i" class="item"
            ><span>{{ item }}</span>
            </li>
        </ul>
        </div>
    </template>
    
    <style lang="scss">
    .item {
        display: flex;
        align-items: center;
    }
    ul {
        display: block;
        margin: 0 auto;
        padding: 30px;
        border: 1px solid rgba(0, 0, 0, 0.25);
        > li {
            color: $color-grey;
            margin-bottom: 4px;
        }
    }
    </style>

    以下屏幕截图显示了购物清单:

    image 2023 10 17 23 02 04 805
    Figure 8. Figure 1.44: The shopping list should be displayed based on user input
  5. 为了满足从列表中删除项目的最后一个要求,请创建一个名为 deleteItem 的新方法,并允许传入一个名为 i 的参数。如果有参数传递到该方法中,则过滤掉该数组项并更新 shoppingList 属性;否则用空数组替换 data 属性:

    ...
    <script>
    export default {
        data() {
            return {
                input: '',
                shoppingList: [],
            }
        },
        methods: {
            addItem() {
                // Don't allow adding to the list if empty
                if (!this.input) return
                this.shoppingList.push(this.input)
                // Clear the input after adding
                this.input = ''
                // Focus the input element again for quick typing!
                this.$refs.input.focus()
            },
            deleteItem(i) {
                this.shoppingList = i
                ? this.shoppingList.filter((item, x) => x !== i)
                : []
            },
        },
    }
    </script>
  6. 创建一个删除所有按钮元素并使用单击事件 @click 将其绑定到 deleteItem 方法:

    <button class="button--delete" @click="deleteItem()">
    Delete all</button>
    ...
    <style lang="scss">
    ...
    .button--delete {
        display: block;
        margin: 0 auto;
        background: red;
    }
    </style>
  7. 在列表循环中添加一个删除按钮,该按钮将通过传入 v-for prop i 来删除单个购物清单项目:

    Figure 1.45 displays the final output with all the details for the shopping list before adding the items:
    <template>
    <div class="container">
        ...
        <ul v-if="shoppingList">
            <li v-for="(item, i) in shoppingList" :key="i" class="item"
            ><span>{{ item }}</span>
            <button class="button--remove"
            @click="deleteItem(i)">Remove</button>
            </li>
        </ul>
        <br />
        <button class="button--delete" @click="deleteItem()">
        Delete all</button>
    </div>
    </template>
    ...
    <style lang="scss">
    ...
    .button--remove {
        background: none;
        color: red;
        text-transform: uppercase;
        font-size: 11px;
        align-self: flex-end;
    }
    </style>
image 2023 10 17 23 10 54 060
Figure 9. Figure 1.45: Final output

以下屏幕截图显示将商品添加到购物清单后的输出:

image 2023 10 17 23 11 33 253
Figure 10. Figure 1.46: Final output with items added to the shopping list

在本活动中,您通过使用 SFC 的所有基本功能(例如表达式、循环、双向绑定和事件处理)来测试您对 Vue 的了解。您构建了一个购物清单应用程序,可以让用户使用 Vue 方法一键添加和删除单个列表项或清除整个列表。

总结

在本章中,您学习了如何使用命令提示符运行 Vue 项目以及创建基本的 Vue 组件。 在这些 Vue 组件中,您可以使用 Vue 独特的指令和 HTML 语法糖构建模板来循环数据或使用条件语句控制 DOM 状态。通过使用 data props 和 v-model 绑定来探索响应式数据的关键概念,并在利用 Vue.js 方法和生命周期的现实示例中发挥作用。

在下一章中,我们将学习基于第一章的更高级的响应式数据概念:使用计算属性和观察者(watchers)以及从外部源获取异步数据。