Vue3-前端项目-二-

41 阅读9分钟

Vue3 前端项目(二)

原文:zh.annas-archive.org/md5/47dfd5e6e269be27634520147d259abf

译者:飞龙

协议:CC BY-NC-SA 4.0

第二章:与数据一起工作

在上一章中,您学习了 Vue API 的基本知识以及如何与单文件 Vue 组件一起工作。在这些基础之上,本章进一步探讨了在 Vue 组件中控制数据的不同方法。

您将学习如何通过计算属性利用 Vue 强大的数据响应性和缓存系统,以及如何设置高级监视器来观察组件的数据变化。您还将学习如何利用异步方法获取和处理 Vue 组件的数据。到本章结束时,您将能够监视、管理和操作 Vue.js 组件中的各种来源的数据。

因此,在本章中,我们将涵盖以下主题:

  • 理解计算属性

  • 理解计算属性设置器

  • 探索监视器

  • 监视嵌套属性

  • 探索异步方法和数据获取

  • 比较方法、监视器和计算属性

技术要求

在本章中,您需要按照第一章中“开始您的第一个 Vue 项目”的说明设置一个基本的 Vue 项目。您可以通过创建一个单文件 Vue 组件来轻松练习提到的示例和概念。

您可以在此处找到本章的源代码:github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02

理解计算属性

计算属性是独特的数据类型,只有当用于属性的源数据更新时,它们才会响应式地更新。通过将数据属性定义为计算属性,我们可以执行以下操作:

  • 在原始数据属性上应用自定义逻辑以计算计算属性的值

  • 跟踪原始数据属性的更改以计算计算属性的更新值

  • 在 Vue 组件的任何地方重用计算属性作为本地数据

默认情况下,Vue 引擎自动缓存计算属性,这使得它们在更新 UI 方面比使用data返回值的属性或使用 Vue 组件的方法更高效。

计算属性的语法类似于编写一个带有返回值的组件方法,嵌套在 Vue 组件的计算属性下:

export default {
  computed: {
    yourComputedProperty() {
      /* need to have return value */
    }
  }
}

在计算属性的逻辑中,您可以使用this实例访问任何组件的数据属性、方法或其他计算属性,this实例是对 Vue 组件实例本身的引用。使用this实例的示例如下:

export default {
  data() {
    return {
      yourData: "your data"
    }
  },
  computed: {
    yourComputedProperty() {
      return `${this.yourData}-computed`;
    }
  }
}

让我们看看一些应该考虑使用计算属性的示例:

  • input 字段,它附加到 name 数据属性上,而 error 是一个计算属性。如果 name 包含一个 falsy 值(这意味着 name 是一个空字符串、0undefinednullfalse),则 error 将被分配一个值为 "Name is required" 的值。否则,它将为空。组件随后根据 error 属性的值渲染相应的值:

    <template>
    
      <input v-model="name">
    
      <div>
    
        <span>{{ error }}</span>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          name: '',
    
        }
    
      },
    
      computed: {
    
        error() {
    
          return this.name ? '' : 'Name is required'
    
        }
    
      }
    
    }
    
    </script>
    

当用户修改 name 值时,错误计算属性会自动更新自己。因此,当 name 为空时,输出将如下所示:

图 2.1 – 错误计算属性的输出

图 2.1 – 错误计算属性的输出

name 有效时,输出将仅显示填充的输入字段:

图 2.2 – 当 name 包含有效值时的错误输出

图 2.2 – 当 name 包含有效值时的错误输出

  • titlesurname – 合并成一个计算字符串,formalName,并使用 template 渲染其值:

    <template>
    
        <div>{{ formalName }}</div>
    
    </template>
    
    <script>
    
        export default {
    
            data() {
    
                return {
    
                    title: 'Mr.',
    
                    surname: 'Smith'
    
                }
    
            },
    
            computed: {
    
                formalName() {
    
                    return `${this.title}
    
                      ${this.surname}`;
    
                }
    
            }
    
        }
    
    </script>
    

这将生成以下输出:

Mr. Smith
  • 计算和显示复杂信息:有时需要执行额外的计算或从一个大型的数据对象源中提取特定的信息。计算属性帮助我们实现这一目标,同时保持我们的代码可读。

取一个大的数据对象,例如 post。此数据对象有一个嵌套的 fields 属性,其中包含几个附加信息对象,例如 author 的全名和一个 entries 对象数组。entries 中的每个条目都包含进一步的信息,例如 titlecontent 和一个表示条目是否应被特色显示的 feature 标志:

data() {
  return {
    post: {
      fields: {
        author: {
          firstName: 'John',
          lastName: 'Doe'
        },
        entries: [{
          title: "Entry 1",
          content: "Entry 1's content",
          featured: true
        },
        {
          title: "Entry 2",
          content: "Entry 2's content",
          featured: false
        }]
      }
    }
  }
},

在此场景中,你需要执行以下步骤:

  1. 显示帖子的 author 的全名。

  2. 计算并显示包含的 entries 的总数。

  3. 显示具有开启 feature 标志的 entries 列表(feature: true)。

  4. 通过使用计算属性,我们可以将之前的 post 对象解耦成几个计算数据属性,同时保持原始的 post 对象不变,如下所示:

    • fullName 用于合并 post.fields.authorfirstNamelastName

      fullName() {
      
       const { firstName, lastName } =
      
         this.post.fields.author;
      
       return `${firstName} ${lastName}`
      
      },
      
    • totalEntries 包含 post.fields.entries 数组的长度:

      totalEntries () {
      
        return this.post.fields.entries.length
      
      },
      
    • featuredEntries 包含基于每个条目的 feature 属性的 post.fields.entries 过滤列表,通过使用内置的 filter 数组方法:

      featuredEntries() {
      
        const { entries } = this.post.fields;
      
        return entries.filter(entry => !!entry.featured)
      
      }
      

然后你使用简化和语义化的计算属性在你的组件模板中渲染信息。完整的代码如下所示:

<template>
  <div>
    <p>{{ fullName }}</p>
    <p>{{ totalEntries }}</p>
    <p>{{ featuredEntries }}</p>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        post: {
          fields: {
            author: {
              firstName: 'John',
              lastName: 'Doe'
            },
            entries: [{
              title: "Entry 1",
              content: "Entry 1's content",
              featured: true
            },
            {
              title: "Entry 2",
              content: "Entry 2's content",
              featured: false
            }]
          }
        }
      }
    },
    computed: {
      fullName() {
        const { firstName, lastName } =
          this.post.fields.author;
        return `${firstName} ${lastName}`
      },
      totalEntries () {
        return this.post.fields.entries.length
      },
      featuredEntries() {
        const { entries } = this.post.fields;
        return entries.filter(entry => !!entry.featured)
      }
    }
</script>

这将生成以下输出:

图 2.3 – 计算名称输出

图 2.3 – 计算名称输出

计算属性对于创建高性能组件的 Vue 开发者来说非常有价值。在下一个练习中,我们将探讨如何在 Vue 组件中使用它们。

练习 2.01 – 将计算数据实现到 Vue 组件中

在这个练习中,你将使用计算属性来帮助你减少在 Vue 模板中需要编写的代码量,通过简洁地输出基本数据。

要访问此练习的代码,请参阅github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.01

我们将实现一个组件,该组件接收用户的姓氏和名字输入,并相应地显示用户的完整姓名,通过以下步骤进行:

  1. 使用由npm init vue@3生成的应用程序作为起点,或者在你的代码仓库的根目录下,使用以下命令导航到Chapter02/Exercise2.01文件夹:

    > cd Chapter02/Exercise2.01/
    
    > yarn
    
  2. 在项目目录中打开练习项目(在code .命令中),或者在你的首选集成开发环境IDE)中打开。

  3. 让我们在./src/components/文件夹中添加一个名为Exercise2-01.vue的新 Vue 组件:

图 2.4 – 组件目录层次结构

图 2.4 – 组件目录层次结构

  1. 打开Exercise2-01.vue,让我们为 Vue 组件创建代码块结构,如下所示:

    <template>
    
    </template>
    
    <script>
    
    export default {
    
    }
    
    </script>
    
  2. <template>中,创建一个用于名字的input字段,并使用v-modeldata属性firstName绑定到该字段:

    <input v-model="firstName" placeholder="First name" />
    
  3. 创建一个用于姓氏的第二个input字段,并使用v-modeldata属性lastName绑定到该字段:

    <input v-model="lastName" placeholder="Last name" />
    
  4. 通过在data()函数中返回它们,将这些新的v-model数据属性包含在 Vue 实例中:

    data() {
    
        return {
    
          firstName: '',
    
          lastName: '',
    
        }
    
      },
    
  5. 创建一个名为fullName的计算数据变量:

    computed: {
    
        fullName() {
    
          return '${this.firstName} ${this.lastName}'
    
        },
    
      },
    
  6. 在你的input字段下方,使用h3标签输出计算数据:

    <h3 class="output">{{ fullName }}</h3>
    
  7. 最后,使用以下命令运行应用程序:

    yarn dev
    
  8. 在浏览器中访问http://localhost:3000,并输入John作为名字,Doe作为姓氏,页面将生成以下输出:

图 2.5 – 计算数据的输出将显示姓氏和名字

图 2.5 – 计算数据的输出将显示姓氏和名字

本练习演示了如何在计算数据属性中使用从v-model接收的数据编写表达式,然后使用fullName计算属性将名字和姓氏合并成一个可重用的输出变量。

我们现在理解了计算属性的工作原理以及如何编写声明式、可重用和响应的计算属性。接下来,我们将探讨如何拦截计算属性的突变过程,并使用计算属性设置器功能添加额外的逻辑。

理解计算属性设置器

默认情况下,计算数据仅是获取器,这意味着它只会输出你表达式的结果。在一些实际场景中,当计算属性被修改时,你可能需要触发外部 API 或在项目的其他地方修改原始数据。执行此功能的函数称为设置器

在计算属性中使用设置器允许你响应式地监听数据并触发一个包含从获取器返回的值的回调(设置器),这个值可以可选地用于设置器中。

但首先,让我们看看 JavaScript ES5 的获取器和设置器。从 ES5 开始,你可以使用内置的获取器和设置器来定义对象访问器,如下所示:

  • get用于将对象属性绑定到函数,每当该属性被查询时,该函数都会返回该属性的值,如下所示:

    const obj  = {
    
      get example() {
    
        return 'Getter'
    
      }
    
    }
    
    console.log(obj.example) //Getter
    
  • set用于将特定对象属性绑定到函数,每当该属性被修改时:

    const obj  = {
    
      set example(value) {
    
        this.information.push(value)
    
      },
    
      information: []
    
    }
    
    obj.example = 'hello'
    
    obj.example = 'world'
    
    console.log(obj.information) //['hello', 'world']
    

基于这些功能,Vue.js 为我们提供了类似的功能,get()作为获取器,set()作为设置器,用于特定的计算属性:

computed: {
  myComputedDataProp: {
    get() {}
    set(value) {}
  }
}

为了理解设置器和获取器是如何工作的,让我们执行以下步骤:

  1. 定义myComputedDataProp返回的值,每当myComputedDataProp被查询时,为this.count + 1

    myComputedDataProp: {
    
      get() {
    
        return this.count + 1
    
      }
    
    },
    
  2. 然后,每当myComputedDataProp被修改时,使用设置器来更新count数据属性到其新值,然后调用组件内的一个方法callAnotherApi,并使用这个新的this.count值:

    myComputedDataProp: {
    
      set(value) {
    
        this.count = value - 1
    
        this.callAnotherApi(this.count)
    
    },
    

countcallAnotherApi分别是组件的局部数据和方法的名称。

完整的示例代码如下:

data() {
  return {
    count: 0
  }
},
method: {
  callAnotherApi() { //do something }
},
computed: {
    myComputedDataProp: {
      get() {
        return this.count + 1
      },
      set(value) {
        this.count = value - 1
        this.callAnotherApi(this.count)
      },
    },
  },
}

在这里,计算属性myComputedDataProp将在你的 Vue 组件中输出1

你将在以下练习中找到如何使用计算数据作为获取器和设置器的确切方法。

练习 2.02 – 使用计算设置器

在这个练习中,你将使用一个计算属性作为设置器和获取器,这两个属性在用户输入触发时都会输出表达式并设置数据。

完整的代码可以在github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.02找到。

我们将实现一个组件,该组件包含一个input字段,用于接收用户输入的数字,计算输入的一半值,然后在 UI 上显示这两个值,通过以下步骤完成:

  1. 使用通过npm init vue@3生成的应用程序作为起点,或者在代码仓库的根目录中,使用以下命令导航到Chapter02/Exercise2.02文件夹:

    > cd Chapter02/Exercise2.02/
    
    > yarn
    
  2. 在你的 VS Code 中打开练习项目(在项目目录中使用code .命令),或者使用你喜欢的 IDE。

  3. 让我们创建一个新的 Vue 组件Exercise2-02,通过将Exercise2-02.vue文件添加到./src/components/文件夹中:

图 2.6 – 组件目录层次结构

图 2.6 – 组件目录层次结构

  1. 打开Exercise2-02.vue,让我们为 Vue 组件创建以下代码块结构:

    <template>
    
    </template>
    
    <script>
    
    export default {
    
    }
    
    </script>
    
  2. 创建一个input字段,其v-model值绑定到一个名为incrementOne的计算数据值,在 getter 中返回名为count的 Vue 数据变量的值,并在 setter 中设置count变量:

    <template>
    
      <div class="container">
    
        <input type="number" v-model="incrementOne" />
    
        <h3>Get input: {{ incrementOne }}</h3>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          count: -1,
    
        }
    
      },
    
      computed: {
    
        incrementOne: {
    
          // getter
    
          get() {
    
            return this.count + 1
    
          },
    
          // setter
    
          set(val) {
    
            this.count = val - 1
    
          },
    
        },
    
      },
    
    }
    
    </script>
    

上述代码的输出将如下所示:

图 2.7 – 计算 setter 和 getter 的第一步

图 2.7 – 计算 setter 和 getter 的第一步

  1. 接下来,让我们再次使用 setter。我们将把新的val参数除以2,并将其保存到一个新的数据变量divideByTwo中:

    <template>
    
      <div class="container">
    
        <input type="number" v-model="incrementOne" />
    
        <h3>Get input: {{ incrementOne }}</h3>
    
        <h5>Set division: {{ divideByTwo }}</h5>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          count: -1,
    
          divideByTwo: 0,
    
        }
    
      },
    
    //...
    
    </script>
    
    //...
    
  2. 更新 setter 以将val除以2,并将这个新值绑定到divideByTwo变量:

          set(val) {
    
            this.count = val - 1
    
            this.divideByTwo = val / 2
    
          },
    
  3. 最后,使用以下命令运行应用程序:

    yarn dev
    
  4. 在浏览器中访问http://localhost:3000,并键入输入1000divideByTwo值的输出应该生成从input字段中输入的值的输出,如下所示:

图 2.8 – divideByTwo 值的输出

图 2.8 – divideByTwo 值的输出

这个练习演示了我们可以如何使用计算数据通过将计算变量绑定到v-model来在我们的模板中反应性地获取和设置数据。在下一节中,我们将探讨我们可以如何使用观察者来积极监听组件数据或其属性的变化。

探索观察者

Vue oldValnewVal。这可以帮助您在写入或绑定新值之前编写表达式来比较数据。观察者可以观察对象以及其他类型,如stringnumberarray类型。

第一章《开始您的第一个 Vue 项目》中,我们介绍了在组件生命周期中特定时间运行的生存周期钩子。如果在一个观察者上设置了immediate键为true,那么当这个组件初始化时,它将在创建时运行这个观察者。您可以通过包含键和值deep: true(默认为false)来观察任何给定对象内的所有键。

为了清理您的观察者代码,您可以将一个handler参数分配给定义好的组件的方法,这在大型项目中被认为是最佳实践。

观察者补充了计算数据的用法,因为它们被动地观察值,不能用作正常的 Vue 数据变量,而计算数据必须始终返回一个值,并且可以被查询。记住不要使用箭头函数,如果您需要 Vue 上下文中的this

以下示例演示了immediatedeep可选键;如果myDataProperty对象中的任何键发生变化,它将触发控制台日志:

watch: {
    myDataProperty: {
        handler: function(newVal, oldVal) {
          console.log('myDataProperty changed:', newVal,
                       oldVal)
        },
        immediate: true,
        deep: true
    },
}

现在,让我们在观察者的帮助下设置一些新值。

练习 2.03 – 使用观察者设置新值

在这个练习中,您将使用观察者参数来观察数据属性的变化,然后使用此观察者通过方法设置变量。

您可以在github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.03找到此练习的完整代码。

我们创建了一个 Vue 组件,用于显示折扣前后的商店观察者价格,并提供更新折扣价格的功能,按照以下说明进行操作:

  1. 使用由npm init vue@3生成的应用程序作为起点,或者在代码仓库的根目录中,使用以下命令导航到Chapter02/Exercise 2.03文件夹:

    > cd Chapter02/Exercise 2.03./
    
    > yarn
    
  2. 在您的 VS Code 中打开练习项目(在项目目录中使用code .命令),或使用您首选的 IDE。

  3. 让我们创建一个新的 Vue 组件Exercise2-03,通过将Exercise2-03.vue文件添加到./src/components/文件夹中:

图 2.9 – 组件目录层次结构

图 2.9 – 组件目录层次结构

  1. 打开Exercise2-03.vue,让我们为 Vue 组件创建代码块结构,如下所示:

    <template>
    
    </template>
    
    <script>
    
    export default {
    
    }
    
    </script>
    
  2. 通过添加discountoldDiscount数据属性来设置文档:

    <template>
    
      <div class="container">
    
        <h1>Shop Watcher</h1>
    
        <div>
    
          Black Friday sale
    
          <strike>Was {{ oldDiscount }}%</strike>
    
          <strong> Now {{ discount }}% OFF</strong>
    
        </div>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          oldDiscount: 0,
    
          discount: 5,
    
        }
    
      },
    
    }
    
    </script>
    
  3. 我们想监听discount属性的变化。这可以通过将其添加到watch对象中,并手动将oldDiscount值更新为接收到的oldValue来实现:

    watch: {
    
        discount(newValue, oldValue) {
    
          this.oldDiscount = oldValue
    
        },
    
      },
    
  4. 现在,让我们添加一个名为updateDiscount的组件方法。在方法内部,将oldDiscount数据属性设置为this.discount + 5

    methods: {
    
        updateDiscount() {
    
          this.discount = this.discount + 5
    
        },
    
      },
    
  5. 然后使用@click指令将此方法绑定到button上,以便在用户点击按钮时触发此方法,并相应地触发观察者:

    <button @click="updateDiscount">Increase Discount!</button>
    
  6. 添加一些 CSS 样式,使我们的组件看起来更美观:

    <style scoped>
    
    .container {
    
      margin: 0 auto;
    
      padding: 30px;
    
      max-width: 600px;
    
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
    
      margin: 0;
    
    }
    
    button {
    
      display: inline-block;
    
      background: rgb(235, 50, 50);
    
      border-radius: 10px;
    
      font-size: 14px;
    
      color: white;
    
      padding: 10px 20px;
    
      text-decoration: none;
    
    }
    
    </style>
    
  7. 最后,使用以下命令运行应用程序:

    yarn dev
    
  8. 在浏览器中访问http://localhost:3000时,前面命令的输出将如下所示:

图 2.10 – 商店观察者页面示例输出

图 2.10 – 商店观察者页面示例输出

在这个练习中,我们探讨了如何使用观察者来观察和动态操作数据,当数据发生变化时通过触发 Vue 组件中的其他方法。

接下来,我们将学习如何通过深度观察来主动观察数据对象中的特定嵌套属性。

观察嵌套属性

当使用 Vue.js 观察数据属性时,您可以观察对象嵌套键的变化,而不是观察对象本身的变化。

这通过将可选的deep属性设置为true来完成:

data() {
  return {
      organization: {
        name: 'ABC',
        employees: [
            'Jack', 'Jill'
        ]
      }
  }
},
watch: {
    organization: {
      handler(v) {
        this.sendIntercomData()
      },
      deep: true,
      immediate: true,
    },
  },

此代码示例演示了如何观察organization数据对象内部的所有可用键的变化。如果organization中的name属性发生变化,organization观察者将触发。

如果你不需要观察对象内的每个键,通过指定 <object>.<key> 字符串语法来为特定键分配观察者会更高效。例如,你可能允许用户编辑他们的公司名称,并在该特定键的值被修改时触发 API 调用。

在以下示例中,观察者明确地观察了 organization 对象的 name 键:

data() {
  return {
      organization: {
        name: 'ABC',
        employees: [
            'Jack', 'Jill'
        ]
      }
  }
},
watch: {
    'organization.name': {
      handler: function(v) {
        this.sendIntercomData()
      },
      immediate: true,
    },
  },

我们已经看到了深度观察是如何工作的。现在,让我们尝试下一个练习,并观察数据对象的嵌套属性。

练习 2.04 – 观察数据对象的嵌套属性

在这个练习中,你将使用观察者来观察对象内的键,当用户在 UI 中触发方法时,这些键会更新。

练习的完整代码可以在 github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.04 找到。

按照说明创建一个组件,该组件显示产品的标签和价格,并动态修改折扣价格:

  1. 使用由 npm init vue@3 生成的应用程序作为起点,或者在你的代码仓库的根目录中,使用以下命令导航到 Chapter02/Exercise2.04 文件夹:

    > cd Chapter02/Exercise2.04/
    
    > yarn
    
  2. 在你的 VS Code 中打开练习项目(在项目目录中使用 code . 命令),或者使用你偏好的 IDE。

  3. 让我们通过将 Exercise2-04.vue 文件添加到 ./src/components/ 文件夹中,创建一个新的 Vue 组件,命名为 Exercise2-04

图 2.11 – 组件目录层次结构

图 2.11 – 组件目录层次结构

  1. Exercise2-04.vue 中,让我们首先定义一个包含 pricelabelproduct 对象,以及一个 discount 键。将这些值输出到模板中:

    <template>
    
      <div class="container">
    
        <h1>Deep Watcher</h1>
    
        <div>
    
            <h4>{{ product.label }}</h4>
    
            <h5>${{ product.price }} (${{ discount }}
    
              Off)</h5>
    
        </div>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          discount: 0,
    
          product: {
    
            price: 25,
    
            label: 'Blue juice',
    
          },
    
        }
    
      },
    
    }
    
    </script>
    
  2. 为我们的组件添加 CSS 样式:

    <style scoped>
    
    .container {
    
      margin: 0 auto;
    
      padding: 30px;
    
      max-width: 600px;
    
      font-family: 'Avenir', Helvetica, sans-serif;
    
      margin: 0;
    
    }
    
    button {
    
      display: inline-block;
    
      background: rgb(235, 50, 50);
    
      border-radius: 10px;
    
      font-size: 14px;
    
      color: white;
    
      padding: 10px 20px;
    
      text-decoration: none;
    
    }
    
    </style>
    
  3. 使用以下命令运行应用程序,并在浏览器中访问 http://localhost:3000 来查看渲染的组件。

    yarn dev
    
  4. 现在,让我们设置一个按钮,该按钮将修改产品的价格。我们通过添加一个 button 元素,并将其 click 事件绑定到一个 updatePrice 方法(该方法减少价格值)来实现这一点:

    <template>
    
    //…
    
        <button @click="updatePrice">Reduce Price!</button>
    
    //...
    
    </template>
    
    <script>
    
    //...
    
      methods: {
    
        updatePrice() {
    
          if (this.product.price < 1) return
    
          this.product.price--
    
        },
    
      },
    
    //...
    
    </script>
    

当你点击按钮时,它应该减少价格,如以下截图所示:

图 2.12 – 显示 Blue juice 减少价格的屏幕

图 2.12 – 显示 Blue juice 减少价格的屏幕

  1. 到了嵌套观察者的时间了。我们将观察 product 对象的 price 属性,并增加 discount 数据属性:

      watch: {
    
        'product.price'() {
    
          this.discount++
    
        },
    
      },
    

现在,当你减少 price 时,由于观察者的作用,discount 值将会上升:

图 2.13 – 显示增加折扣值的输出

图 2.13 – 显示增加折扣值的输出

在这个练习中,我们使用了观察者来观察对象内的一个键,然后使用或未使用观察者解析的可选参数设置新数据。

在下一节中,我们将探讨如何使用 Vue 组件的异步方法获取和处理数据。

探索异步方法和数据获取

JavaScript 中的异步函数由 async 语法定义,并返回一个 Promise。这些函数通过事件循环异步操作,使用隐式 Promise,这是一个可能在未来返回结果的对象。

作为 JavaScript 语言的一部分,你可以在 Vue 组件的方法中声明异步代码块,通过在方法前包含 async 关键字来实现。

你可以使用 Promise 链式方法,例如 then()catch() 函数,或者在 Vue 方法中使用 ES6 的 await 语法,并相应地返回结果。

以下是一个示例,使用内置的 fetch API 在组件方法中作为异步函数使用 async/await 关键字获取数据:

export default {
  methods: {
    async getAdvice() {
      const response =
        await fetch('https://api.adviceslip.com/advice')
      return response;
    },
  },
}

Axios 是一个流行的 JavaScript 库,它允许你使用 Node.js 发起外部数据请求。它具有广泛的浏览器支持,使其在制作 HTTP 或 API 请求时成为一个多才多艺的库。我们将在下一个练习中使用这个库。

练习 2.05 – 使用异步方法从 API 获取数据

在这个练习中,你将异步从外部 API 源获取数据,并使用计算属性在前端显示它。

你可以在 github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.05 找到这个练习的完整代码。

我们将创建一个组件,按照以下说明从外部数据源获取引言并在 UI 上显示:

  1. 使用由 npm init vue@3 生成的应用程序作为起点,或者在你的代码仓库的根目录中,使用以下命令导航到 Chapter02/Exercise2.05 文件夹:

    > cd Chapter02/Exercise2.05/
    
    > yarn
    
  2. 在你的 VS Code 中打开练习项目(在项目目录中使用 code . 命令),或者使用你偏好的 IDE。

  3. 让我们通过将 Exercise2-05.vue 文件添加到 ./src/components/ 文件夹来创建一个新的 Vue 组件 Exercise2-05

图 2.14 – 组件目录层次结构

图 2.14 – 组件目录层次结构

  1. Exercise2-05.vue 中,让我们首先将 axios 导入到我们的组件中,并创建一个名为 fetchAdvice() 的方法。我们使用 axios 调用 api.adviceslip.com/advice 的响应,然后使用 console.log 输出结果。同时,让我们包括一个按钮,该按钮将 click 事件绑定到 fetchAdvice() 调用:

    <template>
    
      <div class="container">
    
        <h1>Async fetch</h1>
    
        <button @click="fetchAdvice()">Learn something
    
          profound</button>
    
      </div>
    
    </template>
    
    <script>
    
    import axios from 'axios'
    
    export default {
    
      methods: {
    
        async fetchAdvice() {
    
          return   axios.get
    
            ('https://api.adviceslip.com/advice').
    
            then((response) => {
    
            console.log(response)
    
          })
    
        },
    
      },
    
    }
    
    </script>
    
    <style scoped>
    
    .container {
    
      margin: 0 auto;
    
      padding: 30px;
    
      max-width: 600px;
    
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
    
    }
    
    blockquote {
    
      position: relative;
    
      width: 100%;
    
      margin: 50px auto;
    
      padding: 1.2em 30px 1.2em 30px;
    
      background: #ededed;
    
      border-left: 8px solid #78c0a8;
    
      font-size: 24px;
    
      color: #555555;
    
      line-height: 1.6;
    
    }
    
    </style>
    
  2. 最后,使用以下命令运行应用程序:

    yarn dev
    

在浏览器中访问 http://localhost:3000 后,前面命令的输出将如下所示:

图 2.15 – 屏幕显示控制台中的一个非常大的对象

图 2.15 – 屏幕显示控制台中的一个非常大的对象

  1. 我们只对 response 对象中的数据对象感兴趣。将此数据对象分配给名为 response 的 Vue 数据属性,我们可以重用它:

    export default {
    
      data() {
    
        return {
    
          axiosResponse: {},
    
        }
    
      },
    
      methods: {
    
        async fetchAdvice() {
    
          return axios.get
    
            ('https://api.adviceslip.com/advice').
    
            then(response => {
    
            this.axiosResponse = response.data
    
          })
    
        },
    
      },
    
    }
    
  2. 使用计算属性从 response 属性对象中输出 quote,该计算属性将在 response 属性更改时更新。使用三元运算符执行条件语句以检查 response 属性是否包含 slip 对象,以避免错误:

    <template>
    
      <div class="container">
    
        <h1>Async fetch</h1>
    
        <button @click="fetchAdvice()">Learn something
    
          profound</button>
    
        <blockquote v-if="quote">{{ quote }}</blockquote>
    
      </div>
    
    </template>
    
    <script>
    
    import axios from 'axios'
    
    export default {
    
      data() {
    
        return {
    
          axiosResponse: {},
    
        }
    
      },
    
      computed: {
    
        quote() {
    
          return this.axiosResponse &&
    
            this.axiosResponse.slip
    
            ? this.axiosResponse.slip.advice
    
            : null
    
        },
    
      },
    
      methods: {
    
        async fetchAdvice() {
    
          return axios.get
    
            ('https://api.adviceslip.com/advice').
    
            then(response => {
    
            this.axiosResponse = response.data
    
          })
    
        },
    
      },
    
    }
    
    </script>
    

图 2.16 显示了前面代码生成的输出:

图 2.16 – 屏幕显示模板中引用输出

图 2.16 – 屏幕显示模板中引用输出

  1. 作为最后的润色,包括一个 loading 数据属性,以便用户可以看到 UI 是否正在加载。默认将 loading 设置为 false。在 fetchAdvice 方法内部,将 loading 设置为 true。当 GET 请求完成(解析/拒绝)时,在 finally() 链中,使用 setTimeout 函数在 4 秒后将它设置回 false。你可以使用三元运算符在加载状态和默认状态之间更改按钮文本:

    <template>
    
      <div class="container">
    
        <h1>Async fetch</h1>
    
        <button @click="fetchAdvice()">{{
    
          loading ? 'Loading...' : 'Learn something
    
          profound'
    
        }}</button>
    
        <blockquote v-if="quote">{{ quote }}</blockquote>
    
      </div>
    
    </template>
    
    <script>
    
    import axios from 'axios'
    
    export default {
    
      data() {
    
        return {
    
          loading: false,
    
          axiosResponse: {},
    
        }
    
      },
    
      computed: {
    
        quote() {
    
          return this.axiosResponse &&
    
            this.axiosResponse.slip
    
            ? this.axiosResponse.slip.advice
    
            : null
    
        },
    
      },
    
      methods: {
    
        async fetchAdvice() {
    
          this.loading = true
    
    try {
    
            const response = await axios.get
    
              (https://api.adviceslip.com/advice);
    
            this.axiosResponse = response.data;
    
          } catch (error) {
    
            console.log(error);
    
          } finally {
    
            setTimeout(() => {
    
              this.loading = false;
    
            }, 4000);
    
          }
    
        },
    
      },
    
    }
    
    </script>
    

前面代码的输出将如下所示:

图 2.17 – 屏幕显示模板中加载按钮状态输出

图 2.17 – 屏幕显示模板中加载按钮状态输出

在这个练习中,我们看到了如何从外部源获取数据,将其分配给计算属性,在模板中显示它,并应用加载状态到我们的内容上。

到目前为止,我们已经探讨了处理 Vue 组件本地数据的不同方法。在下一节中,我们将检查每种方法的优缺点。

比较方法、监视器和计算属性

方法最好用作 DOM 中发生的事件的处理程序,以及在需要调用函数或执行 API 调用的情况下,例如 Date.now()。所有由方法返回的值都不会被缓存。

例如,你可以组合一个由 @click 标记的动作,并引用一个方法:

<template>
    <button @click="getDate">Click me</button>
</template>
<script>
export default {
    methods: {
        getDate() {
            alert(Date.now())
        }
    }
}
</script>

当用户点击 点击我 按钮时,此代码块将显示一个带有当前 Unix 纪元时间的警告栏。不应使用方法来显示计算数据,因为与计算属性不同,方法的返回值不会被缓存,如果误用,可能会对你的应用程序产生性能影响。

如前所述,计算属性最好用于响应数据更新或在模板中组合复杂表达式。在以下示例中,如果 animalList 数据发生变化,animals 计算属性将通过从数组中切片第二个项目并返回新值来更新:

<template>
      <div>{{ animals }}</div>
</template>
<script>
export default {
    data() {
        return {
            animalList: ['dog', 'cat']
        }
    },
    computed: {
          animals() {
              return this.animalList.slice(1)
          }
    }
}
</script>

它们的响应性特性使得计算属性非常适合从现有数据中组合新的数据变量,例如当你引用更大的、更复杂对象的特定键时。

计算属性也有助于提高 Vue 组件模板和逻辑的可读性。在以下示例中,我们以两种不同的方式输出作者,但通过authorName计算属性,你可以干净地组合条件逻辑,而不会使 HTML 模板膨胀:

<template>
    <div>
        <p id="not-optimal">{{ authors[0].bio.name }}</p>
        <p id="optimal">{{ authorName }}</p>
    </div>
</template>
<script>
export default {
    data() {
      return {
          authors: [
              {
                bio: {
                    name: 'John',
                    title: 'Dr.',
                }
              }
          ]
      }
    },
    computed: {
        authorName () {
              return this.authors ?
                this.authors[0].bio.name :
                'No Name'
        }
    }
}
</script>

然而,在许多情况下,使用计算属性可能会过度使用,例如当你只想监视特定数据的嵌套属性而不是整个数据对象时。或者当你需要监听并执行数据属性或嵌套在数据属性对象中的特定属性键的任何变化,然后执行操作时。在这种情况下,应该使用数据监视器。

由于监视器的独特newValoldVal参数,你可以监视变量的变化,并且只有在达到某个特定值时才执行操作:

<template>
  <div>
    <button @click="getNewName()">
      Click to generate name
    </button>
    <p v-if="author">{{ author }}</p>
  </div>
</template>
<script>
    export default {
        data() {
            return {
                data: {},
                author: '',
            }
        },
        watch: {
            data: function(newVal, oldVal) {
                this.author = newVal.first
                alert('Name changed from ${oldVal.first} to
                  ${newVal.first}')
            }
        },
        methods: {
            async getNewName() {
                await fetch('https://randomuser.me/api/').
                  then(response =>
                  response.json()).then(data => {
                    this.data = data.results[0].name
                })
            },
        },
    }
</script>

基于这些示例,我们将构建一个简单的搜索功能,使用方法、计算属性和监视器来实现类似的结果,并展示每种方法的能力。

练习 2.06 – 使用 Vue 方法、监视器和计算属性处理搜索功能

在这个练习中,你将创建一个组件,允许用户使用 Vue 中的三种不同方法搜索数据数组。到练习结束时,你将能够看到每种不同方法是如何工作的。

你可以在github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Exercise2.06找到完整的练习。

我们将创建一个组件,根据三个input字段显示三个不同的过滤列表,每个列表使用本主题中讨论的不同方法,按照以下说明进行:

  1. 使用npm init vue@3生成的应用程序作为起点,或者在代码仓库的根目录下,使用以下命令导航到Chapter02/Exercise 2.06文件夹:

    > cd Chapter02/Exercise 2.06/
    
    > yarn
    
  2. 在你的 VS Code 中打开练习项目(在项目目录中使用code .命令),或者使用你偏好的 IDE。

  3. 让我们通过将Exercise2-06.vue文件添加到./src/components/文件夹中,创建一个新的 Vue 组件,名为Exercise2-06

图 2.18 – 组件目录层次结构

图 2.18 – 组件目录层次结构

  1. Exercise2-06.vue中,在data对象内,添加一个框架列表到数组中,并将其分配给frameworkList属性。同时声明一个空字符串的input属性和初始值为空数组的methodFilterList

    <script>
    
    export default {
    
      data() {
    
        return {
    
          // Shared
    
          frameworkList: [
    
            'Vue',
    
            'React',
    
            'Backbone',
    
            'Ember',
    
            'Knockout',
    
            'jQuery',
    
            'Angular',
    
          ],
    
          // Method
    
          input: '',
    
          methodFilterList: [],
    
        }
    
      },
    
    }
    
    </script>
    
  2. 在模板中,包括一个div容器、title和一个column容器。在这个column容器内部,创建一个绑定到v-model输入的输入框,并将keyup事件绑定到输入框的searchMethod方法:

    <template>
    
      <div class="container">
    
        <h1>Methods vs watchers vs computed props</h1>
    
        <div class="col">
    
          <input
    
            type="text"
    
            placeholder="Search with method"
    
            v-model="input"
    
            @keyup="searchMethod"
    
          />
    
          <ul>
    
            <li v-for="(item, i) in methodFilterList"
    
              :key="i">
    
              {{ item }}</li>
    
          </ul>
    
        </div>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          // Shared
    
          frameworkList: [
    
            'Vue',
    
            'React',
    
            'Backbone',
    
            'Ember',
    
            'Knockout',
    
            'jQuery',
    
            'Angular',
    
          ],
    
          // Method
    
          input: '',
    
          methodFilterList: [],
    
        }
    
      },
    
      methods: {
    
        searchMethod(e) {
    
        console.log(e)
    
        },
    
      },
    
    }
    
    </script>
    
  3. 然后添加一些 CSS 样式,使输出看起来更美观:

    <style scoped>
    
    .container {
    
      margin: 0 auto;
    
      padding: 30px;
    
      max-width: 600px;
    
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
    
    }
    
    .col {
    
      width: 33%;
    
      height: 100%;
    
      float: left;
    
    }
    
    input {
    
      padding: 10px 6px;
    
      margin: 20px 10px 10px 0;
    
    }
    
    </style>
    
  4. 在终端中,使用以下命令运行应用程序:

    yarn dev
    
  5. 在浏览器中访问http://localhost:3000后,前面命令的输出将如下所示:

图 2.19 – 键输入的控制台输出

图 2.19 – 键输入的控制台输出

  1. 在我们的searchMethod方法中,编写一个过滤表达式,将methodFilterList数据属性绑定到一个基于输入值的过滤后的frameworkList数组。在created()生命周期钩子上触发searchMethod,以便当组件加载时,列表就存在:

    <script>
    
    export default {
    
      ...
    
      created() {
    
        this.searchMethod()
    
      },
    
      methods: {
    
        searchMethod() {
    
          this.methodFilterList =
    
            this.frameworkList.filter(item =>
    
              item.toLowerCase().includes(this.input.
    
                toLowerCase())
    
            )
    
        },
    
      },
    
    }
    
    </script>
    

在运行前面的代码后,你将能够过滤列表,如图图 2:20*所示:

图 2.20 – 你应该能够使用 Vue 方法过滤列表

图 2.20 – 你应该能够使用 Vue 方法过滤列表

  1. 让我们使用计算属性来创建一个过滤器。包括一个新的数据属性input2,并创建一个名为computedList的计算属性,它返回与searchMethod相同的过滤器,但不需要绑定到另一个数据属性:

    <template>
    
      <div class="container">
    
      ...
    
        <div class="col">
    
          <input type="text" placeholder=
    
            "Search with computed"
    
            v-model="input2" />
    
          <ul>
    
            <li v-for="(item, i) in computedList"
    
              :key="i">
    
              {{ item }}</li>
    
          </ul>
    
        </div>
    
      ...
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          ...
    
          // Computed
    
          input2: '',
    
          ...
    
        }
    
      },
    
    ...
    
      computed: {
    
        computedList() {
    
          return this.frameworkList.filter(item => {
    
            return item.toLowerCase()
    
              .includes(this.input2.toLowerCase())
    
          })
    
        },
    
      },
    
    ...
    
    }
    
    </script>
    

现在你应该能够使用计算属性帮助过滤框架的第二列,如下面的屏幕截图所示:

图 2.21 – 使用计算属性过滤框架的第二列

图 2.21 – 使用计算属性过滤框架的第二列

  1. 最后,让我们使用监视器来过滤相同的列表。包括一个带有空字符串的input3属性和一个带有空数组的watchFilterList属性。还要创建一个第三个div列,其中包含一个绑定到input3 v-model的输入框,以及输出watchFilterList数组的列表:

    <template>
    
      <div class="container">
    
        …
    
        <div class="col">
    
          <input type="text" placeholder="Search with
    
            watcher"
    
            v-model="input3" />
    
          <ul>
    
            <li v-for="(item, i) in watchFilterList"
    
              :key="i">
    
              {{ item }}</li>
    
          </ul>
    
        </div>
    
      </div>
    
    </template>
    
    <script>
    
    export default {
    
      data() {
    
        return {
    
          ...
    
          // Watcher
    
          input3: '',
    
          watchFilterList: [],
    
        }
    
      },
    
     ...
    
    </script>
    
  2. 创建一个监视器,监视input3属性的变化,并将frameworkList过滤的结果绑定到watchFilterList数组上。将input3的立即键设置为true,以便在组件创建时运行:

    <script>
    
    export default {
    
    ...
    
      watch: {
    
        input3: {
    
          handler() {
    
            this.watchFilterList =
    
              this.frameworkList.filter(item =>
    
                item.toLowerCase()
    
                  .includes(this.input3.toLowerCase())
    
              )
    
          },
    
          immediate: true,
    
        },
    
      },
    
    ...
    
    }
    
    </script>
    

在使用监视器之后,你现在应该能够过滤第三列,如下面的屏幕截图所示:

图 2.22 – 在第三列使用监视器过滤列表

图 2.22 – 在第三列使用监视器过滤列表

在这个练习中,我们看到了如何使用方法、计算属性和监视器来实现过滤列表。

本节简要介绍了三种方法。每种方法都有其优缺点,选择最合适的方案或组合方案需要实践和进一步理解每个用例或项目目标。

在下一节中,我们将应用本章学到的知识,通过创建一个使用计算属性、方法和外部数据 API 查询的监视器的博客列表应用程序。

活动二.01 – 使用 Contentful API 创建博客列表

要访问此活动的代码文件,请参阅 github.com/PacktPublishing/Frontend-Development-Projects-with-Vue.js-3/tree/v2-edition/Chapter02/Activity2.01

本活动旨在通过构建一个列出文章的博客来利用您关于应用不同方法与外部数据 API 源工作的知识。此应用程序活动将通过使用所有基本的 async 方法从 API 获取远程数据并使用计算属性来组织深层嵌套的对象结构来测试您的 Vue 知识。

Contentful 是一个无头 内容管理系统CMS),允许您将内容与代码存储库分开管理。您可以使用 API 在所需的任何代码存储库中消费此内容。例如,您可能有一个作为信息主要来源的博客网站,但您的客户希望在另一个域上有一个独立的页面,该页面只拉取最近推出的文章。使用无头 CMS 本质上允许您开发这两个独立的代码库并使用相同的数据源。

本活动将使用 Contentful 无头 CMS。访问密钥和端点将列在解决方案中。

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

  1. 使用带有 Vite 作为打包管理工具的脚手架工具创建 Vue 项目。

  2. 使用 yarn add 命令将 Contentful 依赖项(www.npmjs.com/package/contentful)安装到您的项目中。

  3. 使用计算属性输出 API 响应中的深层嵌套数据。

  4. 使用数据属性输出用户的姓名、职位和描述。

  5. 使用 SCSS 为页面添加样式。

预期结果如下:

图 2.23 – 使用 Contentful 博客文章的预期结果

图 2.23 – 使用 Contentful 博客文章的预期结果

活动完成后,您应该能够使用 async 方法从 API 源中提取远程数据到您的 Vue 组件中。您会发现计算属性是将信息分解成更小的可重复数据块的一种复杂方式。

摘要

在本章中,你被介绍了 Vue.js 的计算和观察属性,这些属性允许你观察和控制响应式数据。你还探索了如何使用方法通过 axios 库异步从 API 获取数据。然后,你学习了如何使用计算属性在 Vue 模板中将接收到的数据动态组合成不同的输出。通过构建搜索功能,演示了使用方法和计算及观察属性之间的区别。

下一章将介绍 Vite 并展示如何使用 Vue DevTools 来管理和调试使用这些计算属性和事件的 Vue.js 应用程序。