🎈 九年了,感谢大家 🎏 订阅全年送一年,续订或重订送两年。 立即订阅

Vue 3 快速上手指南

先准备一下,搭建一个 Vue 应用的开发环境,创建并且运行一个 Vue 项目,然后理解 Vue 的组件。接着我们就准备一个服务端应用接口,学会在 Vue 应用里请求使用服务端应用接口获取应用需要的数据。再去了解怎么在 Vue 里创建路由器,定义应用的路由(Vue Router),最后要学会一套应用状态的管理方法(Vuex ),最终我们会准备好一个能够应对复杂应用的应用结构。

第一章:启动与预热

前置知识

学习使用 Vue 开发前端应用或者小程序,前置知识就是最基本的 Web 老三样:HTML,CSS 与 JavaScript。如果您打算成为一名开始者,强烈推荐先学它们仨,最长久,最实用,可以用它们做很多事情。只需要学会基础即可,不用学太多,理解了核心概念以后,可以随用随学。

Vue 做的就是应用的界面,界面的内容结构用的就是 HTML  组织出来的。你要知道一块界面如何用 HTML 组织好。HTML 提供了很多标签(元素),不同的标签在浏览器上会呈现出不同的样子,标签也可以称为元素。用 CSS 可以设计界面的样式,比如元素的定位,长度,宽度,排版,背景,边框,字号,文字颜色等等。

你要做到,给你一张设计图,或者看到一个页面的时候,你大概知道如何用 HTML 与 CSS 做出来。也就是知道如何使用 HTML 把界面结构组织好,然后用 CSS  设置这个界面的样式。

JavaScript 是一种程序语言,只需要学会基本语法,知道基本的数据类型(数字,字符串,数组,对象),数据的基本运算,逻辑判断,声明变量,定义与执行函数。一开始不用学太多,我准备了一个 JavaScript 核心课程,你只需要理解这个课程里所有的语法就行了。https://ninghao.net/course/8115 

很多东西我都记不住,都是现用现查,在开发应用的时候,我会随时搜索。关于 JavaScript 语言,最好的文档库就是 MDN ,几乎可以解决你所有的 JavaScript 问题。别学太多,语言基础最多用半个月到一个月的时候搞定。我们不需要知道所有才能去干活,不知道的随时查就行。

开发工具

开发 Vue 应用,你只需要这几样工具:

  1. 编辑器:写代码用
  2. 终端:执行命令,运行服务
  3. Git:源代码管理
  4. 浏览器:测试开发的 Vue 应用

编辑器可以使用 VSCode(有自己喜欢的就用自己喜欢的)。终端是执行命令的地方,Windows 用户可以下载安装一个完整版的 Cmder,里面包含了很多常用的工具,比如 Git。macOS 用户直接使用系统自带的那个 Terminal 就行。在系统上都把 Git 安装配置好,用它管理项目的源代码。浏览器用来测试预览正在开发的 Vue 应用。

编辑器配置

如果你选择使用 VSCode 编写 Vue 应用,可以在编辑器里安装一个 Vetur 插件,它可以高亮显示 Vue 组件的代码,还提供了一些其它的功能。再安装一个 Prettier 插件,用它自动格式化应用的代码,装好以后可以勾选一下编辑器的 Format on save 这个配置选项,保存时自动格式化。

如果你发现不能自动格式化,可以打开编辑器的命令面板(command + shift + P),搜索并执行 Format Document 命令,这样会弹出提示让我们选择一种格式化器,可以选择使用 Prettier 作为代码的格式化器。

开发环境

Node.js:JavaScript 运行环境

下载一个长期支持版本(LTS)的 Node.js  ,把它安装在自己的本地电脑上。用 Vue CLI 创建 Vue 项目,运行项目的开发服务,还有在编译项目的时候都会用到 Node.js。装好以后会自带一个包管理工具叫 NPM,你也可以额外再安装一个叫 Yarn 的包管理工具,这两个东西用谁都行,不过从 Vue 官方文档的说明来看,官方更推荐用 Yarn,选择用谁都无所谓,因为随时都可以切换。

Vue CLI

Vue CLI 是 Vue 提供的一个命令行工具,用它可以创建  Vue 项目,可以运行与编译项目。使用 Node.js 的包管理工具,在电脑的全局范围安装一下这个工具,就可以使用 vue 这个命令了。

npm install @vue/cli --global

或者用 Yarn:

yarn global add @vue/cli

启动项目

如果你打算用 Vue 创建一个独立的前端应用,而不只是想把 Vue 集成到现有的网站或者应用里,那在创建这个 Vue 项目的时候就要使用 Vue CLI。用命令创建一个 Vue 项目,然后再用命令启动这个项目在本地的开发服务,在浏览器上可以打开预览正在开发的项目,在编辑器里修改项目然后保存文件以后,无需手动刷新,就可以立即在浏览器上看到修改之后的结果。

这种独立的 Vue 应用,一般都会使用单文件组件,也就是应用里的每个组件可以放在各自独立的带 .vue 后缀的文件里。使用这种独立文件的方法创建组件跟你在官方文档里看到的例子不太一样,所以要注意一下。

创建一个 Vue 项目

cd ~/desktop
vue create ninghao-vue

进入到你想要保存项目的地方,然后使用 vue 命令 create 一个 Vue 项目,放在 ninghao-vue 这个目录的下面。在创建项目的时候会让我们选择一个项目的预设,也就是它会根据我们的选择创建一个 Vue 项目。选择创建一个 Vue 3 项目。

这里你可以选择默认的预设,也可以选择手工设置,如果是手工设置会问你很多问题,让你做出选择,然后根据你的选择给你创建一个 Vue 项目。这也无所谓,因为随时都可以修改,也可以重新创建一个项目。

运行项目的开发服务

cd ~/desktop/ninghao-vue
yarn serve

或者用 NPM:

npm run serve

在创建的 Vue 项目里定义了几个命令,比如 serve 这个命令可以启动本地的开发服务,用 build 命令可以编译在生产环境上使用的应用。 启动了本地开发服务以后,可以通过一个地址访问到我们的 Vue 项目,在本地电脑上访问项目的地址默认是 http://localhost:3000 ,你在局域网的其它设备上也可以通过电脑的本地 IP  地址访问到这个 Vue 项目。比如 http://192.168.31.140:3000192.168.31.140 是我的运行 Vue 开发服务的这台电脑在我的局域网内部的 IP  地址。

成功创建了 Vue 项目,启动了项目的本地开发服务,就可以开练了。

项目结构

用你的编辑器打开新创建的这个 Vue 项目,先观察一下项目的文件与目录结构。在项目根目录下面会有一些文件,这些文件一般就是项目里用的一些开发工具的配置文件,如果你不知道它们是什么,可以暂时忽略掉它们。

├── README.md
├── babel.config.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── main.js
└── yarn.lock

package.json

文件里描述了当前这个项目,项目的名字,介绍,项目依赖的包,自定义的命令等等。

node_modules

项目依赖的包都会下载到这个目录的下面。

public

public 目录里面有个 index.html 文档,我们开发的 Vue 应用会挂载到这个网页上。

src

应用的源代码文件都会放在这个目录的下面,这个目录里的结构我们可以自己定义。

src/main.js

这个 main.js 是应用的入口文件,在这个文件里要创建一个 Vue 应用,创建这个应用的时候要指定使用一个 Root 组件,也可以称为主组件。

dist

在终端项目所在目录的下面执行  npm run build  或者 yarn build ,就会编译生成适合在生产环境上运行的应用,生成的应用会放在这个 dist 目录的下面。最终我们开发之后编译生成的前端应用,其实就是一个网页,一些 CSS  样式表,还有一些 JavaScript 文件。把这个 dist 目录里的静态文件资源放到某个 Web 服务器上就算是完成部署了。

组件

在 Vue 框架里有个关键概念就是 “组件 ”(Component),一个组件你可以把它想成是应用的某一块儿界面,组件可以组合到一块儿使用,最终构成应用的整体的界面。开发 Vue 应用,就是去创建一些组件,设计组件要展示的内容,样式,以及行为。

创建一个组件,你可以决定这个组件里边儿要展示应用的哪部分内容,组件可以做什么事情。在组件里你可以使用 HTML 与 CSS 来设计组件的内容结构与样式。在组件里可以定义一些方法,比如获取组件需要的数据用的方法,处理在组件的元素上发生的事件的方法。

组件可以组合到一块儿使用,比如你定义了一个 PostList(内容列表)组件,又定义了一个 PostListItem (内容列表项目)组件。你在内容列表组件(PostList)里可以使用内容列表项目组件(PostListItem)来展示每个内容项目。这样我们就可以说 PostList 组件是 PostListItem 组件的父组件,PostListItem 是 PostList 的子组件。在内容列表项目组件里需要用的数据,可以在它的父组件(PostList)那里,通过属性(props)的方式传递过来。

PostList 示例:

<div class="post-list">
  <PostListFilter />
  <PostListItem 
    v-for="post in posts"
    :item="post"
    :key="post.id"
  />
</div>

上面这个例子是 PostList 这个组件的模板可能的样子,在这个组件里使用了 PostListFilter 组件,还有一个 PostListItem 组件。在这个组件上,我们使用了 Vue 框架提供的 v-for 指令,循环了一下在 PostList 组件里的一个叫 posts 的数据,每次循环当前项目的名字叫 post,然后我们通过绑定 PostListItem 组件的 item 属性,把 post 这个数据交给了 PostListItem 组件使用。

这样这个 PostListItem 组件的模板,可以像这样:

<div class="post-list-list">
  <h3>{{ item.title }} - {{ item.user.name }}</h3>
</div>

假设上面是 PostListItem 组件的模板,这里绑定输出了 item 这个属性里的两个数据,item.title 是内容的标题,item.user.name 是内容作者的名字。 这个 PostList 组件最终在浏览器上展示的时候,界面的源代码大概会像这样:

<div class="post-list">
  <div class="post-list-filter">...</div>
  <div class="post-list">
    <h3>十年生死两茫茫,不思量,自难忘 - 苏轼</h3>
  </div>
  <div class="post-list">
    <h3>曾经沧海难为水,除却巫山不是云 - 元稹</h3>
  </div>
  ...
</div>

单文件组件的结构

在 Vue 应用里可以创建单文件组件,这些文件的后缀是 .vue,也就是一个这样的文件就定义了一个 Vue 组件。在组件文件里分成了三个部分,组件的模板(tempalte),组件的脚本(script),还有组件的样式(style)

<template>
</template>

<script>
</script>

<style>
</style>

模板

组件的模板会放在 .vue 文件的 <template> 标签里面,你可以在这里使用 HTML 设计组件的内容结构。

<template>
  <div>宁皓网</div>
</template>

脚本

组件的脚本放在 script 标签里面,在这里要默认导出一个对象,这个对象里面的东西就是组件的脚本,在里面你可以定义组件需要的东西,比如组件的数据,属性,方法等等。

<script>
export default {
  // 数据
  data() {
    return {
      posts: []
    }
  },
  
  // 方法
  methods: {
    async getPosts() {
      // ...
    }
  }
}
</script>

样式

组件模板需要的样式可以放在一组 style 标签里面。

<style>
  .post-list {
    max-width: 750px;
  }
</style>

你也可以单独给组件创建一些样式表,然后在 style 标签里面,用 @import 导入这些样式表,像这样:

<style>
@import './styles/post-list.css';
</style>

数据:data

在 Vue 组件里要展示的数据可以通过在这个组件内部定义的方法请求服务端应用接口获取到,也可以通过属性的方式传递过来。比如在上面那个例子里,PostList 组件循环处理了一个叫 posts 的数据,这个数据应该是一组内容列表。在这个组件里可以定义一个请求服务端内容列表接口用的方法,成功以后,把服务端应用响应回来的数据交给组件里的 posts 这个数据。这样在组件里就可以使用这个数据了。

在组件里可以添加一个 data 方法返回组件里面需要的数据,这里也可以理解成是在组件里声明一下组件里都需要哪些数据,这些数据可能暂时没有值,可以在执行了获取数据用的方法以后再设置这些数据的值。

PostList 组件里的数据:

data() {
  return {
    posts: []
  }
}

上面我们在 PostList 组件里面用 data 方法返回了组件需要的数据,这里有一个 posts 数据,暂时先让它等于一个空白的数组,等以后请求服务端内容列表接口获取到了数据,可以重新设置 posts 这个数据的值。当组件里的数据发生变化的时候,组件会重新被渲染显示更新之后的结果。

方法:methods

在 Vue 组件里可以定义一些行为,也就是组件可以执行的一些方法,比如获取数据用的方法,元素事件的处理器等等,这些都可以定义成组件的方法。在定义组件的时候,在组件对象里添加一个 methods 属性,然后在里面可以添加一些方法。

methods: {
  // 组件的方法
}

比如在 PostList 组件里面,需要一个请求服务端接口获取内容列表数据用的方法,可以把这个方法放在这个 methods 属性里面。

使用 HTTP 客户端可以请求服务端应用接口,比如 axios 就是一种可以用在 Vue 应用里的 HTTP  客户端。在我们的项目里安装一下这个东西,就可以在应用里导入使用它了。

下面是这个请求内容列表接口方法可能的样子:

  methods: {
    async getPosts() {
      try {
        const response = await axios.get('http://localhost:3000/posts');
        this.posts = response.data;
      } catch (error) {
        console.log(error.response);
      }
    }
  }

在上面这个 getPosts 方法里面,我们用 axios 上的 get 方法,发出一个 GET 类型的 HTTP 请求,这个请求的地址就是服务端应用的内容列表接口。请求成功以后可以设置一下组件里的 posts 这个数据,让它等于响应里的 data 这个属性:

this.posts = response.data;

一开始组件里的 posts 数据的值是一个空白的组件,执行 getPosts 获取到了内容列表数据,设置了一下这个 posts 数据的值,组件的数据有变化,它就会被重新渲染,所以我们会立即在应用的界面上看到更新之后的结果。

在这个方法里用了 axios 这个东西,所以你需要先安装并导入它,才能在组件里使用它。

import axios from 'axios';

生命周期:Life Cycle

Life Cycle 生命周期,指的就是组件的不同阶段。在组件里可以添加一些生命周期方法,这些方法会在组件的不同阶段被调用。比如组件被创建之后,被挂载以后,被更新之后,取消挂载之后。这些都有一个对应的生命周期方法。你需要在组件的哪个阶段去做一些事情,只需要在组件里添加一个对应的生命周期方法,在里面添加要做的事情就行了。

比如 created 这个方法会在组件被创建之后调用。你想在这个阶段执行一些任务,可以在组件里定义一个叫 created 的方法,然后在里面添加要做的事情。

在之前的例子我们提到过,PostList 组件可以通过 getPosts 方法获取到内容列表数据,但是这个方法要在什么时候被执行呢?比如可以让它在组件被创建之后执行,也就是在应用里创建了 PostList 组件之后,可以请求获取这个组件里需要的内容列表数据。

在 PostList 里定义 created 生命周期方法:

created() {
  // 请求获取内容列表数据
  this.getPosts();
}

组件属性:props

在 Vue 应用里定义组件的时候,可以声明一下这个组件支持的一些属性,这样在使用这个组件的时候可以设置组件的这些属性的值,在组件里面可以使用这些属性的值。组件支持的属性可以放在组件的 props 这个选项里。

比如在上面的例子里,PostList 组件在使用 PostListItem 组件的时候,就设置了这个组件的 item 属性,对应的值应该是一个内容项目。在 PostListItem 组件的模板里,绑定输出了这个内容项目里的内容的标题,还有内容作者的名字。

PostListItem 组件里声明属性:

props: {
  item: Object
}

props 里的东西就是组件支持使用的一些属性,在 PostListItem 组件里有一个 item 属性,它的值的类型是一个 Object(对象)。这样使用这个组件的时候,就可以设置这个 item 属性的值,像这样:

  <PostListItem 
    v-for="post in posts"
    :item="post"
    :key="post.id"
  />

上面是在 PostList 组件里使用的 PostListItem 组件,先在这个组件里循环了一下 posts 数据,每次循环的时候都会把当前这个内容项目交给 PostListItem 组件的 item 属性。

注意这里用了一个绑定的写法,绑定指的就是绑定组件里的某个数据的值, :item="post" 这是一种简单的写法,完整的写法像这样:v-bind:item="post" 。 这里绑定的数据叫 post,这个 post 应该是当前这个组件里的某个数据,这里就是循环组件的 posts 数据的时候,当前被循环的内容项目。

这样就会把一个内容项目数据传递到 PostListItem 这个组件里,在 PostListItem 组件的脚本里可以使用 this.item 得到组件的 item 这个属性。在这个组件的模板里可以绑定输出这个 item 属性里的数据:

<div class="post-list-list">
  <h3>{{ item.title }} - {{ item.user.name }}</h3>
</div>

计算属性:computed

在组件里可以定义一些计算属性,你可以在这些计算属性里使用组件里的数据,做一些加工,处理,转换等等这些操作计算出一个新的值。然后你在组件里就可以使用这个计算属性的值,比如在组件的方法里使用它,或者把它绑定到组件的模板上显示出来。当在计算属性里使用的组件数据发生变化的时候,这个计算属性会自动重新计算出新的值。

Vue 组件里的计算属性可以放在 computed 里面,它是一个对象,里面可以定义计算属性,每个计算属性都是一个方法,方法的名字就是这个计算属性的值,方法返回(return)的东西就是这个计算属性提供的值。你在组件里可以使用这个计算属性的名字引用这个计算属性提供的值。

在 PostList 组件里定义一个计算属性:

computed: {
  postImageURL () {
    return `${API_BASE_URL}/files/${this.item.file.id}/serve?size=large`
  }
}

上面定义了一个叫 postImageURL  的计算属性,在这个计算属性里面用到了组件里的一个属性的值(item) ,这样当这个属性的值发生变化的时候,这个计算属性也会重新计算出新的值。在组件的脚本里可以用 this.postImageURL 引用这个计算属性的值,在组件的模板里,也可以绑定输出这个计算属性的值:{{ postImageURL }},也可以把它的值绑定在某个元素的属性上:<img :src="https://ninghao.net/postImageURL" />

重构应用

之前我们用 Vue CLI 创建了一个 Version 3 版本的 Vue 应用,就是在执行 vue create ninghao-vue 命令创建应用的时候,可以选择 Vue 3 这个预设。下面把项目里的 src 下面的东西全部都删除掉,我们一块儿再重新创建一个 Vue 应用。

主组件

创建 Vue 应用的时候需要一个主组件,先去创建这个组件。

src/app/app.vue<新建>

<template>
  <h1>{{ appName }}</h1>
</template>

<script>
export default {
  data() {
    return {
      appName:'宁皓网',
    };
  },
};
</script>

这是一个非常简单的组件,在这个组件里声明一个叫 appName 的数据,它的值是宁皓网,我们把这个 appName 数据绑定在组件的模板里了,用一组 h1 元素包装了这个数据的值。

应用入口

Vue 应用的入口文件要放在 src 的根目录下面,文件的名字是 main.js

src/main.js<新建>

import { createApp } from 'vue';
import App from './app/app.vue';

/**
 * 创建应用
 */
const app = createApp(App);

/**
 * 挂载应用
 */
app.mount('#app');

创建 Vue 应用的时候用的是 vue 这个包里提供的 createApp,使用它的时候要提供应用的主组件,这里我们用的就是之前创建的 App 组件。新建的这个应用叫 app,然后执行 app 上面的 mount 方法,把应用挂载到页面里的带 #app 这个 ID 的元素上,这里说的页面指的是 public 目录下面的那个 index.html

在浏览器上预览这个项目,页面上显示的内容就是 App 组件里的 appName 数据的值,检查页面元素,你会发现这几个字用了一组 h1 元素包装了一下。这组用 h1 元素包装了 appName 这个数据的这块界面,就是 App 这个组件的模板。

暂时就先了解这些,后面我们会继续用几篇文章介绍更多开发 Vue 3 应用相关的东西,比如在应用里创建与使用组件,请求服务端应用的接口获取数据,应用的路由与状态管理等等。

参考课程Vue.js(v3)前端应用 #1:理解框架

Vue 3 系列课程正在陆续发布,以后每周都会有更新。另外基于 Node.js 开发服务端应用的课程已经全部放出,里面介绍了几种开发语言,工具,数据库,应用接口的设计与开发思路等等,你可以用这个系列课程作为应用的入门课程。在我们最后几集的 Vue 3 课程里,也会用到在这个 Node.js 课程里开发的部分接口。

现在订阅宁皓网,即刻在线学习。

第二章:请求服务端接口获取组件数据

在 Vue 应用上处理数据要请求服务端应用接口,比如在内容列表组件上请求服务端的内容列表接口获取到内容列表数据,然后在组件的模板里循环绑定输出这组数据。Vue 应用也可以把用户在应用界面上生产出来的数据交给服务端应用接口,把数据永久的存储在应用的数据仓库里。

应用的客户端(Vue 应用)与服务端交换数据的时候,一般就是通过 HTTP  协议。客户端对服务端发出 HTTP 请求,服务端根据客户端请求的地址(接口)做出不同的响应,如果客户端请求获取数据,服务端会把客户端需要的数据放在响应的数据里,客户端收到了响应就可以从响应里拿到它需要的数据了。

如果客户端想把用户生产出来的数据交给服务端去处理,可以在发送请求的时候,把用户数据放在请求里,这样服务端收到请求以后,就可以从请求里拿到它需要的数据,服务端可以处理这些数据,比如把它放到数据仓库里保存起来。

如果你想开发一个完整的应用,你需要同时开发应用的客户端与服务端。客户端可以基于 Vue 框架开发,服务端可以基于 Node.js 开发。在宁皓网上有个系列课程介绍了怎么基于 Node.js 开发一个服务端应用,你可以从零开始做出一个能发布内容,评论,用户登录,上传文件,打标签,点赞的服务端应用。

服务端

下面我们会练习在 Vue 应用里请求服务端应用提供的接口获取数据。你可以参考这个课程(https://ninghao.net/course/8606),在本地电脑上运行一个基于 Node.js 编写的服务端应用。

在本地准备好这个服务端应用,运行以后就可以使用 HTTP 客户端请求应用的服务接口了。下图展示了请求应用提供的内容列表接口返回的结果。一组内容,每个内容项目是一个对象,里面有内容的 id,title(标题),content(正文),user(内容作者) 等等。

如果你想学习怎么从头开发一个这样的服务端应用,可以学习这个系列课程:https://ninghao.net/package/xb2-node

HTTP 客户端(axios)

上图中展示了用一个 HTTP 客户端桌面软件,请求使用了内容列表接口。我们在自己的 Vue 应用里,也要准备一个 HTTP 客户端发送 HTTP 请求,axios 就是其中一种 HTTP 客户端,把它安装在 Vue 项目里,这样就可以导入使用它提供的方法来请求应用的服务端接口了。

安装

在终端, Vue 项目所在目录的下面,执行:

yarn add axios

或者

npm install axios

使用 Yarn 或者 NPM 都可以管理项目的包,任选其一即可。

使用 axios

装好以后,可以在 Vue 里面导入使用这个 axios,像这样:

import axios from 'axios'

然后使用 axios 上面提供的各种方法,发出使用不同方法的 HTTP 请求,比如内容列表接口要使用  GET 这种 HTTP  方法请求使用,所以在请求这个接口的时候可以使用  axios 上面提供的 get 方法 ,像这样:

const response = await axios.get('http://localhost:3000/posts');

axios 提供的这些请求方法会返回 Promise,所以在执行它的时候可以加一个 await ,然后把请求得到的响应起个名字,上面我们给它起的名字叫 response,这里面有很多信息,比如响应里的状态码,头部数据,还有响应里带的具体的数据。在内容列表这个接口做出的响应里,响应的数据就是一组内容列表数据。响应里的数据你可以在响应的 data 属性里面找到:response.data

创建 axios 实例

我们也可以在应用里创建一个 axios 实例,然后用这个实例上面的提供的方法发送 HTTP 请求。这样做的好处是你可以对不同的 axios 实例做不同的配置。比如在请求服务端接口的时候,每次都需要加上一个基本的地址,比如 http://localhost:3000 ,我们可以创建一个 axios 实例,配置一下实例的基本地址,这样请求接口的时候就不用加这个基本的地址了,直接写接口地址就可以了。

import axios from 'axios';

// axios 实例
const apiHttpClient = axios.create({
  baseURL: 'http://localhost:3000'
});

// 使用实例发送请求接口
const response = await apiHttpClient.get('/posts');

实践

在之前准备好的  Vue 项目的下面,新建一个文件,放在 src/app 的下面,名字是 app.service.js。

创建 axios 实例

src/app/app.service.js

import axios from 'axios';

/**
 * axios 实例
 */
export const apiHttpClient = axios.create({
  baseURL: 'http://localhost:3000'
});

在 app.service 里面创建并导出了一个 axios 实例,以后要在 Vue 应用里请求接口的时候就可以导入使用这个 axios 实例。

内容列表

我们的服务端应用已经运行了,请求服务端接口用的 HTTP 客户端也准备好了,下面可以在 Vue 应用里创建一个内容列表组件,请求内容列表接口得到内容列表数据,然后在组件里展示这些内容列表数据。

1:创建 PostList 组件

src/post/components/post-list.vue<新建>

<template>
  <div class="post-list">
    <div class="post-list-item" v-for="item in posts" :key="item.id">
      <h3>{{ item.title }}</h3>
      - <small>{{ item.user.name }}</small>
    </div>
  </div>
</template>

这个 PostList 就是我们要显示内容列表用的一个组件,上面是这个组件里的模板。在这个模板里,用 v-for 指令循环了组件里的一组内容列表数据(posts),在循环的时候可以给当前项目起个名字,这里我们给它起的名字叫 item,这样你在这个用了 v-for 元素的里面就可以使用这个 item 里的数据了,这里面有内容的 id,title,content,user 这些东西。我们暂时可以绑定输出 item.title(内容标题),还有 item.user.name(内容作者的名字)。

2:组件脚本

在 PostList 组件的模板里需要用到一个叫 posts 的数据,这组内容列表数据可以在组件的脚本里,使用 HTTP 客户端请求服务端的内容列表接口获取到。

<script>
import { apiHttpClient } from '@/app/app.service';

export default {
  data() {
    return {
      posts: [],
    };
  },

  created() {
    // 获取内容列表数据
    this.getPosts();
  },

  methods: {
    async getPosts() {
      try {
        // 请求内容列表接口
        const response = await apiHttpClient.get('/posts');

        // 设置组件的数据
        this.posts = response.data;
      } catch (error) {
        console.log(error.response);
      }
    },
  },
};
</script>

在组件的脚本里,先导入之前创建的那个 HTTP 客户端。然后默认导出一个组件对象,里面用 data 方法返回组件需要的数据,这里我们返回了一个叫 posts 的数据,默认它的值是个空白的数组。

在组件里添加一个 created 生命周期,组件被创建之后就会自动调用在组件里定义的这个方法,在这里可以执行一下组件里的 getPosts 方法请求内容列表接口获取数据。

在组件的 methods,也就是组件的方法里面定义了一个叫 getPosts 的方法,这个方法干的事儿就是用 HTTP 客户端,对内容列表接口发出请求,然后把成功请求回来的数据交给组件的 posts 这个数据。这样在组件的模板那里就可以使用这组内容列表数据了。

3:使用 PostList 组件

现在我们已经定义好了一个叫 PostList 的组件,下面得找个地方用一下这个组件。后面在介绍应用路由的时候,可以单独给这个内容列表创建一条路由,访问路由地址可以显示这组内容列表。暂时可以先在 App 组件里用一下这个内容列表。

src/app/app.vue<修改>

<template>
  <h1>{{ appName }}</h1>
  <PostList />
</template>

<script>
import PostList from '@/post/components/post-list.vue';

export default {
  data() {
    return {
      appName: '宁皓网',
    };
  },

  components: {
    PostList,
  },
};
</script>

在 App 组件的模板里,用了一下 <PostList /> 组件,意思就是让 PostList 组件里的东西在这里展示。想要在组件里使用其它的组件,需要先在组件的脚本里导入要使用的组件,这里就是使用 import 导入这个组件:

import PostList from '@/post/components/post-list.vue';

然后在组件对象里面,把导入的组件放在 components 选项里,这样才能在这个组件的模板里使用导入的组件:

 components: { 
   PostList, 
 },

4:预览

打开浏览器,访问 Vue 应用的开发服务地址,在页面上应该会显示一组内容列表,每个项目里有内容的标题还有内容的作者的名字。在浏览器的开发者工具的 Elements 选项卡里,再观察一下页面用的元素。

列表项目

在内容列表组件(PostList)的模板里会循环输出一组内容列表,列表项目这块东西可以单独再创建一个组件,然后在内容列表组件里使用这个内容列表项目组件(PostListItem)。

1:创建内容列表项目组件

src/post/components/post-list-item.vue<新建>

<template>
  <div class="post-list-item">
    <h3>{{ item.title }}</h3>
    - <small>{{ item.user.name }}</small>
  </div>
</template>

上面是这个内容列表项目组件的模板。这块模板内容跟你在 PostList 组件里循环输出内容的时候用的东西基本上是一样的。模板里绑定输出的数据是这个 PostListItem 组件的属性,这个 item 属性的值可以在使用这个 PostListItem 组件的时候设置好。

2:组件脚本

src/post/components/post-list-item.vue<修改>

<script>
export default {
  name: 'PostListItem',
  props: {
    item: Object
  },
};
</script>

在 PostListItem 组件的脚本里,用 name 可以设置一下这个组件的名字,组件支持的属性可以放在 props 这个选项里,这里声明了一个叫 item 的属性,它的类型的值是一个 Object(对象)。

3:在 PostList 组件里使用 PostListItem

src/post/components/post-list.vue<修改>

<template>
  <div class="post-list">
    <PostListItem v-for="item in posts" :key="item.id" :item="item">
  </div>
</template>

在组件的模板里循环 posts 数据的时候,现在可以使用 PostListItem 这个组件,使用它的时候给它的 item 属性绑定一个值,这个值就是循环的时候当前内容项目的值,这里我们给这个项目起的名字叫 item。

src/post/components/post-list.vue<修改>

<script>
// ...
import PostListItem from './post-list-item.vue';

export default {
  // ...

  components: {
    PostListItem,
  },
};
</script>

在 PostList 组件的脚本里,先导入 PostListItem 组件,然后在组件对象里面添加一个 components 选项,把在这个组件里要用的其它组件放在这个选项里,这里就是 PostListItem。

4:预览

在浏览器上再预览一下,你会看到 跟之前一样的内容列表,现在这个内容列表里的内容项目里的内容是在 PostListItem 组件里设计好的。

项目代码

https://github.com/ninghao/ninghao-vue-dev-3/tree/request-api

第三章:路由

访问某个地址,显示 Vue 应用里的某个特定的组件(页面),这个就是 Vue Router 提供的路由功能。这里说的组件,我们也可以理解成是一个页面,比如用户登录页面,内容列表页面,显示单个内容的页面,这些页面组件跟一般的组件没什么区别,对于 Vue 来说,它们都是组件(Component)。

Vue Router

安装 Vue Router

打开项目根目录下的 package.json ,在项目的依赖里添加一个 vue-router。

  "dependencies": {
    // ...
    "vue-router": "^4.0.0-0"
  },

在终端,Vue 项目所在目录的下面,执行 yarn 或者 npm install ,准备好项目依赖的东西,包管理工具会把新添加的 vue-router 下载安装到我们的项目里。

路由器

给 Vue 项目装好 vue-router 以后,可以先去创建一个路由器,然后告诉应用使用一下这个路由器就可以了。后面我们会再去定义一些路由,再把这些路由放到这个路由器里。

1:创建路由器

src/app/app.router.js<新建>

import { createRouter, createWebHistory } from 'vue-router';

/**
 * 创建路由器
 */
const router = createRouter({
  history: createWebHistory(),
  routes: [],
});

/**
 * 默认导出
 */
export default router;

在 src/app 的下面,新建一个叫 app.router.js 的文件,在里面可以创建并导出一个路由器。创建路由器用的就是 vue-router 里的 createRouter 这个函数。提供一个配置对象参数,里面可以设置一下路由的 history 模式。createWebHistory 这个函数可以创建一个 HTML5 的 history,一般单页面应用(SPA)都会使用这种 history,我们正在开发的这个 Vue 应用就是一个单页面应用。

在创建这个路由器的时候可以把路由器里的路由放在 routes 属性里面,暂时先使用一个空白的数组,在后面我们再定义一些路由,再把这些路由交给这个属性。

2:使用路由器

有了路由器以后还得再告诉 Vue 应用一声,让它使用这个路由器。

main.js<修改>

//...
import appRouter from './app/app.router';

//...

/**
 * 应用路由
 */
app.use(appRouter);

// ...

打开应用的入口文件 main.js,在创建了 app 的下面,可以找个地方用一下 app 上面的 use,使用在上面导入的 appRouter,这个东西就是之前我们在 app.router 里面,使用 createRouter 创建的一个路由器。现在应用就有路由器了。

路由

定义一条路由其实就是设置一下访问某个地址的时候对应要显示的是哪个 Vue 组件。

1:定义路由组件

src/app/components/app-home.vue

<template>
  <div>欢迎访问宁皓网!</div>
</template>

先创建一个组件,等会儿定义路由的时候会用到这个组件,这个组件可以作为应用的首页,也就是访问首页的时候会显示这个组件。

2:定义路由

src/app/app.routes.js<新建>

import AppHome from './components/app-home.vue';

/**
 * 定义路由
 */
const routes = [
  {
    name: 'home',
    path: '/',
    component: AppHome,
  },
];

/**
 * 默认导出
 */
export default routes;

创建一个新的文件,放在 src/app 的下面,文件的名字叫 app.routes.js ,注意这里用的是 routes 不是 router。应用整体相关的路由都可以放在这个路由文件里。

在 app.routes 里面,我们声明了一个叫 routes 的东西,它的值是一个数组,里面可以放一组路由对象。最后在这个文件里默认导出了 routes。

每一条路由就是一个对象,里面有一些特定的属性,比如 name 可以设置路由的名字,path 设置的是路由的地址,component 设置的是对应的组件。

上面这条路由它的名字叫 home,地址是 / ,表示应用的根,也就是应用的首页,component 设置成了 AppHome,也就是当访问 / 这个地址的时候,页面上就会显示 AppHome 这个组件。

3:把路由交给路由器

src/app/app.router.js<修改>

import { createRouter, createWebHistory } from 'vue-router';
import appRoutes from './app.routes';

/**
 * 创建路由器
 */
const router = createRouter({
  history: createWebHistory(),
  routes: [...appRoutes],
});

/**
 * 默认导出
 */
export default router;

定义的路由要放在路由器里,打开之前创建的 app.router 路由器,先在这个文件里导入 appRoutes,然后把它里面的东西交给路由器的 routes ,这里用了一个展开操作符(...)。

4:路由器视图

src/app.vue<修改>

<template>
  <h1>{{ appName }}</h1>
  <router-view></router-view>
</template>

<script>
export default {
  data() {
    return {
      appName: '宁皓网',
    };
  },
};
</script>

App 这个组件是应用的 Root Component(根组件 / 主组件),因为在应用的入口文件那里创建 Vue 应用的时候,提供了这个组件,所以它就是应用的主组件。在这个组件的模板里,要添加一组 router-view 组件,路由对应的组件会在这里显示。比如在访问 / 这个地址的时候,在这里就会显示 AppHome 这个组件的内容。

5:预览

访问应用的首页,你会看到在页面上会显示 AppHome 组件里的东西。

内容列表

我希望在访问 /posts 这个地址的时候,可以显示一组内容列表,也就是显示 PostList 这个组件。

1:调整组件结构

现在内容列表与列表项目组件是在 src/post/components 目录的下面,我们把这个 components 目录可以放在 src/post/index 目录的下面,跟内容列表相关的东西,都可以放在 src/post/index 这个目录里。

2:新建 PostIndex 组件

src/post/index/post-index.vue<新建>

<template>
  <PostList />
</template>

<script>
import PostList from './components/post-list';
export default {
  components: {
    PostList,
  },
};
</script>

新建一个组件,放在 src/post/index 的下面,名字是 post-index.vue。在这个组件的模板里用一下 PostList 组件。

3:新建内容相关路由

src/post/post.routes.js<新建>

import PostIndex from './index/post-index.vue';

/**
 * 定义路由
 */
const routes = [
  {
    name: 'postIndex',
    path: '/posts',
    component: PostIndex,
  },
];

/**
 * 默认导出
 */
export default routes;

跟内容相关的路由都可以放在这个 post.routes 文件里。先添加一个内容列表路由,名字叫 postIndex,地址是 /posts,对应的组件是 PostIndex。这样在访问 /posts 这个地址的时候,显示的就是 PostIndex 组件,在这个组件的模板里用了 PostList 组件,所以也就会显示一组内容列表。

4:把路由交给路由器

src/app/app.router.js<修改>

//...
import postRoutes from '@/post/post.routes';

/**
 * 创建路由器
 */
const router = createRouter({
  history: createWebHistory(),
  routes: [...appRoutes,  ...postRoutes],
});

// ...

导入 postRoutes,把它里面的东西放到路由器的 routes 属性里。

5:预览

现在访问 /posts 这个地址的时候,会在页面上显示一组内容列表。

内容页面

下面我们可以去给内容页面定义一条路由,访问某个具体地址的时候可以显示某个特定的内容。这个路由的地址里要支持使用地址参数,一般这个参数就是内容的 id,在路由对应的组件那里,可以从路由里面获取到当前路由地址里的内容 id 参数的值,组件(页面)可以根据这个 id 的值去请求某个特定的内容数据。

1:创建内容页面(组件)

src/post/show/post-show.vue<新建>

<template>
  <div class="post-show">
    <h3>{{ post.title }}</h3>
    - <small v-if="post.user">{{ post.user.name }}</small>
  </div>
</template>

<script>
import { apiHttpClient } from '@/app/app.service';
export default {
  props: {
    postId: String,
  },

  data() {
    return {
      post: {},
    };
  },

  created() {
    this.getPostById(this.postId);
  },

  methods: {
    async getPostById(postId) {
      try {
        const response = await apiHttpClient.get(`/posts/${postId}`);
        this.post = response.data;
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>

新建一个组件,名字是 post-show.vue,放在 src/post/show 目录的下面。这个组件支持一个 postId 属性,这个属性的值应该是某个内容的 id 。属性具体的值可以从路由那里得到。

在 data 方法里,组件返回了一个叫 post 的数据,这个数据的值就是要在组件里显示的某个内容数据。内容数据要请求服务端应用的内容接口获取到,这件事可以定义成一个方法,放在  methods 里面,方法的名字叫 getPostById,方法接收一个 postId 参数,参数的值应该是某个内容的 id。

在组件里添加了一个 created 生命周期,组件被创建以后会调用这个方法,在它里面我们执行了一下在组件里定义的 getPostById 这个方法,把组件的 postId 属性的值交给了这个方法,这个方法会从服务端那里得到某个特定的内容数据。在组件的模板里绑定输出了内容的标题,还有内容作者的名字。

2:定义路由

src/post/post.routes.js<修改>

//...
import PostShow from './show/post-show.vue';

/**
 * 定义路由
 */
const routes = [
  //...
  {
    name: 'postShow',
    path: '/posts/:postId',
    component: PostShow,
    props: true,
  },
];

//...

在内容相关的路由里先导入 PostShow 组件,然后定义一条新的路由,路由的名字叫 postShow,路由的地址是 /posts/:postId,这个地址里的 :postId 是一个地址参数,地址里带冒号前缀的都算是地址参数。/posts/1,/posts/16 这些都匹配这个路由。如果访问的是 /posts/16 ,那在路由里的 postId 这个地址参数的值就相当于是被设置成了 16 。

注意这条路由里我们把 props 设置成了 true,意思是把路由地址参数用属性的方式传递给路由组件。这条路由里有个 postId 参数,那在这个路由对应的组件那里就可以声明一个叫 postId 的属性。比如在访问 /posts/16 的时候,路由就会把路由组件里的 postId 这个参数的值设置成 16 。

3:路由链接

src/post/index/components/post-list-item.vue<修改>

<template>
  <div class="post-list-item">
    <h3>
      <router-link :to="{ name: 'postShow', params: { postId: item.id } }">
        {{ item.title }}
      </router-link>
    </h3>
    - <small>{{ item.user.name }}</small>
  </div>
</template>

修改一下内容列表项目组件的模板,用 router-link 组件包装一下内容的标题,然后给这个组件的 to 属性绑定一个值,它是一个对象,用 name 设置路由的名字,用 params 设置地址参数。

4:预览

先访问一下内容列表 /posts,你会发现列表项目标题现在会是一个链接,链接的地址就是当前这个内容项目所属的内容页面。

点击内容列表项目的标题,会打开对应的内容页面,注意观察浏览器的地址栏里的地址。

项目代码

https://github.com/ninghao/ninghao-vue-dev-3/tree/router

参考课程Vue.js(v3)前端应用

Vue 3 系列课程正在陆续发布。基于 Node.js 开发服务端应用的课程已经全部放出,里面介绍了几种开发语言,工具,数据库,应用接口的设计与开发思路等等,你可以用这个系列课程作为应用的入门课程。

现在订阅宁皓网,即刻在线学习。

第四章:状态管理

Vue 应用里的组件可以管理自己的状态,在组件内部可以声明它需要用的数据,通过组件内部的一些方法可以修改组件需要的数据,改变组件的状态,组件的状态发生变化以后它就会重新被渲染,显示更新之后的状态。在复杂的应用里,组件之间可能需要共享一些状态,比如当用户在评论组件里发表了一条新的评论,内容组件上的评论数量可能需要更新一下。这种情况就需要引入一套应用整体的状态管理解决方案。

对于一个简单的 Vue 应用来说,引入状态管理解决方案会让应用变得复杂一些,但是相信我,您只要打算正经用 Vue 开发个应用,就一定需要一套状态管理的解决方案,目前 Vue 官方提供的方案就是使用  Vuex 这个东西。这篇文章我们就一块儿了解一下基于 Vuex 的状态管理。

Vuex

Vuex 是 Vue 官方提供的一套应用状态管理解决方案。先把它安装在我们的 Vue 项目里。

安装  Vuex

package.json<修改>

  "dependencies": {
    // ...
    "vuex":  "^4.0.0-0"
  },

在项目的 package.json 文件的 dependencies 里面添加一个 vuex,设置一下版本号。然后在终端,项目所在目录的下面,执行 yarn 或者 npm install,安装好项目的依赖。

Store

有了 Vuex 以后,可以用它提供的功能去创建一个 Store,这个 Store 可以想成是 Vue 应用的数据仓库,它里面有组件需要的数据(state),更新数据(mutations)用的方法,还有处理数据用的一些动作(actions)。

比如应用的内容列表组件需要一组内容列表数据,就可以在 Store 里面,添加一个叫 posts 的数据,然后定义一个获取这组数据用的动作,一般就是请求服务端接口获取数据,得到了数据可以提交修改,设置 Store 里的 posts 这个数据。在内容列表组件里面,可以引用调用 Store 里的动作获取 posts 这个数据,然后在组件里面使用这个数据。

1:创建 Store

src/app/app.store.js<新建>

import { createStore } from 'vuex';

/**
 * 创建 Store
 */
const store = createStore({
  state: {
    appName: '宁皓网',
  },

  modules: {},
});

/**
 * 默认导出
 */
export default store;

创建一个叫 app.store.js 的文件,放在 src/app 的下面。在这个文件里用 vuex 提供的 createStore 创建一个 Store,起个名字叫 store,默认导出这个 store。使用 createStore 的时候可以提供一个对象参数,在这个对象里可以定义 Store 里的数据,修改,动作这些东西。等会我们再学习这些东西。

注意我在创建这个 Store 的时候,用了一个 modules ,它里面可以包含一些 Store 模块。等会我们会去创建 Store 模块,再把它放在这个 modules 里面。这样做可以把应用的 Store 分割成不同的模块,每个 Store 模块里都可以有自己的数据,修改还有动作。这么做是为了应付复杂的应用,可以让应用的结构更清晰。

2:应用 Store

main.js<修改>

// ...
import appStore from './app/app.store';

// ...

/**
 * 应用 Store
 */
app.use(appStore);

// ...

有了 Store 以后,要告诉应用一声使用这个 Store。在应用的入口文件里面,创建了 app 的下面,使用 app.use 方法,把上面导入的 appStore 交给这个方法。这样在应用所有的组件里就都可以使用这个 store 了,可以通过 this.$store 看到应用的 Store 里的东西。你可以找一个组件,添加一个 created 生命周期方法,把 this.$store 输出到控制台上检查一下,看看它里面都有什么东西。

Store 模块

我打算按应用里处理的资源,把 Store 分割成不同的模块,这样可以应对复杂的应用,让应用的结构更清晰。每个 Store  模块里都可以管理自己的数据,有自己的修改,动作,也可以继续包含其它的 Store 模块。

我要把内容资源相关的东西放在一个 Store 模块里,然后根据对内容的操作继续划分出一些 Store 模块。这样我就需要创建一个 post 模块,然后再给内容列表,单个内容,创建、更新内容再单独创建一个 Store 模块,把这些内容模块放在 post 这个模块里,也就是在 post 这个 Store 模块里会包含内容列表,创建,更新内容这些 Store 模块。再把这个 post 模块放在应用的 Store 里,作为 Store 的一个模块。

1:内容列表 Store 模块

src/post/index/post-index.store.js<新建>

export const postIndexStoreModule = {
  namespaced: true,

  state: {
    loading: false,
    posts: [],
  },
};

内容列表相关的东西我会放在 src/post/index 这个目录的下面,所在可以在这个目录下面新建一个 Store 模块,文件的名字是 post-index.store.js。

在这个文件里导出一个叫 postIndexStoreModule 的东西,它的值是一个对象,这个对象就是一个 Store 模块,里面可以添加数据、修改、动作之类的东西。暂时我们只添加一个 state,它的值是这个 Store 模块里的数据。

注意这里我把 namespaced 设置成了 true,意思是要使用命名空间。这样在访问这个 Store 的修改、动作这些东西的时候就需要加上模块的名字。等会儿我们会看到例子。

2:内容 Store 模块

src/post/post.store.js<新建>

import { postIndexStoreModule } from './index/post-index.store';

export const postStoreModule = {
  namespaced: true,

  modules: {
    index: postIndexStoreModule,
  },
};

跟内容资源相关的东西是在 src/post 目录的下面,在这个目录的下面创建一个 post.store.js。在文件里可以定义并导出一个 Store 模块,在这个 Store 模块里可以在 modules 里面设置它包含的模块,我们把之前创建的 postIndexStoreModule 添加到这里,重新起个名字叫 index。

注意这个 post 模块也把 namespaced 设置成了 true,就是要使用命名空间。这样假设在 index 模块里有个 getPosts 动作,访问这个动作的时候用的是 'post/index/getPosts' 。

3:修改应用的 Store 

src/app/app.store.js<修改>

// ...
import { postStoreModule } from '@/post/post.store';

/**
 * 创建 Store
 */
const store = createStore({
  // ...

  modules: {
    post: postStoreModule,
  },
});

// ...

现在我们要把 post 这个 Store 模块添加到应用的 Store 模块里,把它放在 modules 里面,起个名字叫 post,对应的 Store 模块就是 postStoreModule。

获取器

在 Store 模块里可以定义一些获取器(getters),它们的作用主要就是提供 Store 模块里的数据(state)。当然我们可以不用通过获取器直接就能获取到 Store 模块里的数据。用获取器的好处是可以对数据进行加工处理,然后返回处理之后的数据。你可以在 Store 模块里定义任意数量的获取器提供不同的数据。

1:定义获取器

src/post/index/post-index.store.js<修改>

export const postIndexStoreModule = {
  namespaced: true,

  state: {
    loading: false,
    posts: [],
  },

  getters: {
    loading(state) {
      return state.loading;
    },

    posts(state) {
      return state.posts;
    },
  },
}

在 postIndexStoreModule 这个模块里添加一个 getters 属性,里面可以定义一些获取器,每个获取器都是一个方法,方法接收一个 state 参数,这个参数的值就是这个 Store 模块里的 state。也就是在这个模块里你可以从 state 参数里面得到 loading 与 posts 这两个数据。

获取器 return 的东西就是它提供的值。在组件里可以从 Store 里面访问获取器,可以直接绑定输出它们的值。上面我们定义了两个获取器,loading 还有 posts,loading 提供的就是这个 Store 模块里的 loading 这个数据的值,posts 获取器提供的就是 posts  这个数据的值。

注意这个获取器的名字是可以随便定义的,不一定非要跟 state 里的数据的名字一致。在获取器里面你可以处理一下要提供的数据,然后再返回处理之后的结果。

2:使用获取器

在应用的组件里如果需要使用 postIndexStoreModule 这个模块里的 loading 这个获取器提供的值,可以访问 this.$store.getters['post/index/loading'] ,如果你想直接访问 Store 里的这个模块的 loading 数据,可以访问 this.$store.state.post.index.loading 。

通过 vuex 提供的 mapGetters 方法,可以直接把 Store 里的  Getter 映射成一个组件的计算属性,这样就可以直接使用这个计算属性了。

src/post/index/component/post-list.vue<修改>

<script>
import { mapGetters } from 'vuex';
// ...

export default {
  computed: {
    ...mapGetters({
      loading: 'post/index/loading',
      posts: 'post/index/posts',
    }),
  },
  
  // ...
};
</script>

在内容列表组件里可以使用 postIndexStoreModule 里的 loading 还有 posts 这两个获取器提供的数据。先把之前在这个组件里添加的 data 方法删除掉,因为组件需要的数据我们会在 Store 里获取到。

从 vuex 里面导入 mapGetters,然后在组件里添加一个 computed 选项,把执行 mapGetters 返回的结果入进来,给这个方法可以提供一个对象参数,里面分别设置一下在这个组件里的计算属性的名字,还有对应的是 Store 里的哪个获取器。比如 posts 这个计算属性对应的获取器是 post/index/posts。这样我们就可以直接在这个组件里使用 posts 这个数据了。

修改

要改变 Store 里的数据(state),不能直接设置 state 的值,要通过提交(commit)修改(mutations)来完成。也就是你要在 Store 里面定义一些 Mutations,它们的作用就是去修改特定的 state 的值。

src/post/index/post-index.store.js<修改>

export const postIndexStoreModule = {

  // ...

  mutations: {
    setLoading(state, data) {
      state.loading = data;
    },

    setPosts(state, data) {
      state.posts = data;
    },
  },
};

postIndexStoreModule 里面先添加一个 mutations 选项,里面定义两个修改,每个修改都是一个方法,方法接收一个 state 参数,还有一个 data 参数。这个 state 参数里的东西就是这个 Store 模块里的 state 的值。 data 参数的值就是在提交这个修改的时候设置的值。

上面定义了两个 Mutation,一个是 setLoading,一个是 setPosts ,setLoading 会修改 loading 这个 state,setPosts 可以修改 posts 这个 state。提交修改可以在 Store 模块里的动作里执行,也可以在应用的组件里去做。

在组件里提交修改可以这样:

this.$store.commit('post/index/setLoading', true);

上面这个例子就是在组件里用 Store 提供的  commit 这个方法提交了一个叫 post/index/setLoading 的修改,第二个参数的值是 true,这个值会交给 post/index/setLoading 这个修改的第二个参数,也就是那个 data 参数。 提交了这个修改,它会把模块里的 loading 这个 state 的值修改成 true 。

在组件里我们也可以通过 vuex 提供的 mapMutations,把 Store 模块里的修改映射成组件里的一个方法,这样就可以直接执行这个方法来修改 Store 里的数据了。

示例:

methods: {
  ...mapMutations({
    setLoading: 'post/index/setLoading'
  })
}

在组件里可以把执行 mapMutations 返回的结果放到组件的 methods 选项里,这样会在组件里然后在组件里可以这样修改提交:

this.setLoading(true);

动作

在 Store 里面可以定义一些动作,比如请求服务端接口获取数据用的动作(actions),向服务端接口提交数据的动作等等。在动作里面获取到数据以后可以提交修改(mutations),用这种东西去修改 Store 里的数据(state)。数据被修改之后,使用这个数据的组件会被重新渲染显示更新之后的内容。

1:定义动作

src/post/post-index.store.js<修改>

import { apiHttpClient } from '@/app/app.service';

export const postIndexStoreModule = {

  // ...
  actions: {
    async getPosts({ commit }) {
      commit('setLoading', true);

      try {
        const response = await apiHttpClient.get('/posts');
        commit('setLoading', false);
        commit('setPosts', response.data);

        return response;
      } catch (error) {
        commit('setLoading', false);

        throw error;
      }
    },
  },
};

在 postIndexStoreModule 这个 Store 模块里定义一个叫 getPosts 的动作,把它放在 actions 这个属性里。Store 模块里的动作支持一个 context 参数,在这个参数里,你可以得到当前这个 Store 模块里的 state,getters ,也就可以得到 Store 整体的 rootState 还有 rootGetters 这些东西。

这里我们用了一个解构的写法,把 context 里的 commit 这个方法解构出来,这样就可以直接在这个动作里使用 commit 提交修改了。

在这个 getPosts 动作里,我先 commit 了一下 setLoading 这个 mutation,把模块的 loading 这个 state 的值设置成了 true。然后用了一组 try,catch 区块。catch 到了错误以后可以 commit 一下 setLoading,把 loading 的值改成 false。然后可以 throw 这个错误。

在 try 里面,我们用 apiHttpClient 这个 HTTP 客户端请求了服务端应用的内容列表接口,地址就是 /posts,请求成功得到了数据以后 commit 一个 setLoading ,把 loading 的值改成 false,再 commit 一个 setPosts,把 posts 这个数据的值设置成响应里的 data,也就是服务端响应里的具体的数据。

2:使用动作

src/post/index/components/post-list.vue<修改>

<script>
import { mapGetters, mapActions } from 'vuex';
// ...

export default {
  // ...
  methods: {
    ...mapActions({
      getPosts: 'post/index/getPosts',
    }),
  },

  // ...
};
</script>

在内容列表组件(PostList) 里,我们可以执行在 postIndexStoreModule 里定义的那个 getPosts 动作,去请求获取内容列表数据。这里借助 vuex 提供的 mapActions 帮手方法,把 Store 模块里的动作映射成为一个组件里的方法。

之前我们在这个组件的 methods 里面定义过一个 getPosts,这里要把这个方法删除掉,还要把在这个组件里导入 apiHttpClient 的代码也删除掉。

现在这个内容列表组件的 created 生命周期里执行的 getPosts 方法,其实执行的就是 postIndexStoreModule 里的那个 getPosts 动作,这个动作会去请求内容列表接口,然后提交 setPosts 修改,设置这个  Store 模块里的 posts 数据。之前我们在这个组件里已经使用了 posts 这个获取器,它提供的值就是一组内容列表数据。

3:预览

在浏览器上访问一下内容列表页面,你会看到一组内容列表数据,现在这个页面上的 PostList 组件里要展示的数据会来自 postIndexStoreModule 这个 Store 模块。

项目代码

https://github.com/ninghao/ninghao-vue-dev-3/tree/vuex

相关文章



微信好友

用微信扫描二维码,
加我好友。



微信公众号

用微信扫描二维码,
订阅宁皓网公众号。



240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

12921
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点