Vue-SPA

SPA

什么是SPA?

SPA(Single Page Application):单页面应用程序,是一种前端的开发技术,是现在前、后端分离下的一种产物。

  • 传统的网站

​ 我们传统的网站是由很 多个独立的页面 组成的,当我们点击页面中的 a 标签时,就会向服务器发送一个新的请求,然后下载另一个页面显示,跳转时是页面之间的跳转。

  • SPA

    SPA(单页面应用程序),顾名思议,整个网站中只有一个页面,在这个页面中会加载很多个不同的组件,当我们点击按钮时,并不会打开一个新的页面,而是还在当前的页面中切换显示不同的组件

SPA 的优、缺点

  • 优点

1、减轻服务器的压力:一个网站只有一个页面,只需要从服务器加载一次

2、并且把大量操作都放到了浏览器中去完成

3、前、后端完成分离,使服务器只需要提供同一套 JSON 数据接口,就可以同时满足WEB端、桌面端、手机端等不同的前端设备

4、而且前端只关注前端、后端只操作数据,各司其职

  • 缺点

1、首屏加载速度可能会很长

2、SEO(搜索引擎优化)不友好,爬虫数据时什么也抓不到

3、页面复杂度提高、开发难度加大

使用 Vue 开发 SPA

我们可以使用 Vue 框架来开发 SPA,开发时使用的技术:

1、使用 Vue Cli 工具快速构建项目目录结构、开发环境、部署

2、使用 Vue-Router 实现路由,实现组件之间的切换

3、使用 Vuex 实现状态数据的管理

4、使用 axios 发送 AJAX 和服务器通信

.vue 文件

在使用 Vue 开发 SPA 时,SPA 是由很多个 Vue 的组件组成的,每个组件就是一个 .vue 文件。

每个 .vue 文件中都由三部分组件:HTML、CSS、JS,并且:

1、html:所有的 html 代码必须要写在 <template>...</template> 标签中

2、css:所有的 css 代码写在 style...<style> 标签中

3、js:所有 js 代码写在 <script>...</script> 标签中

SPA 中的 Vue

SPA 中的 Vue 和我们之前学习过的 Vue 的用法大体上是一致的,不过也有一些不一样的地方需要注意一下:

  • data 必须是一个函数

我们之前使用 vue 时,都是在 data 中定义数据,而 data 是一个属性类型,如:

new Vue({
    data:{
        name:'tom',
        age:10
    }
})

在 spa 中,每个 .vue 文件是一个 Vue 的组件,在组件中 data 必须是一个函数,而且不能使用 new Vue 创建新的 Vue 对象,因为整个 SPA 中只有一个 Vue 对象:

export default {
    data(){
        return {
            name:'tom'
        }
    }
}
  • 使用 import 引入组件

我们之前当要引入一个JS文件或者组件时需要使用 script 标签,如:

<script src='Pagination.js'></script>

而在 spa 中需要使用 es6 提供的 import 来引入:

import Pagination from "Pagination.vue"

使用 Vue CLI 构建项目

Vue 提供了一个 Vue CLI 的工具可以让我们快速的搭建起一个 SPA 的项目来。

官方网站

官方文档:https://cli.vuejs.org/zh/

image-20181117093433022

安装

首先要确定自己的电脑上安装了 Node.js 8.9 或更高版本。

然后,我们就可以使用 npm 来安装 vue/cli :

npm install -g @vue/cli

安装之后,我们可以在命令行中使用 vue 指令查看安装的版本:

vue --version

image-20181117093739940

创建一个项目

我们可以使用下面的命令来创建一个 SPA 的项目:

vue create 项目名称

创建项目时,会提示我们选择项目中需要使用的组件,我们可以使用默认的配置,也可以自己手动选择需要加载的组件。

手动选择组件

image-20181117095607650

勾选需要安装的组件:

image-20181117095700506

使用路由的 history 模式:

image-20181117095724832

把配置写到 package.json 文件中:

image-20181117095750342

不保存本次的配置:

image-20181117095810003

目录介绍

我们写的代码都在 src 目录中

启动项目

安装成功之后,我们可以进行使用以下指令启动项目:

cd hello-world   // 进入项目目录
npm run server   // 启动项目

image-20181117100041305

启动之后我们可以在浏览器中访问欢迎页面:

image-20181117100106775

初始组件 App.vue

项目启动之后会最先运行 main.js 文件:

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,                    // 装载 vue-router
  store,                     // 装载 vuex
  render: h => h(App)      // 渲染 App 组件
}).$mount('#app')

在 main.js 文件中会

1、引入 vue 、vue-router、vux 框架包

2、引入 App 组件

3、创建 Vue 对象并且装载 vue-router , vux

4、渲染第一个组件:App.vue

所以我们运行之后看到的页面就是 App.vue 组件的内容。

Vue-Router

在 SPA 中,网站内容的变换实际上的组件的切换,为了方便的实现组件间的切换,Vue 框架引入了 vue-router 的个工具来实现多个组件之间的切换。

官方文档:https://router.vuejs.org/zh/

image-20181117161356170

配置路由

在使用 vue-router 之前,我们需要先配置访问的路径与要显示的组件的对应关系。

我们需要在 router.js 文件中进行配置。

image-20181117161625914

router.js

默认配置了两个路由:

1、当访问 / 时,显示 Home 组件

2、当访问 /about 时,显示 about 组件

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'   // 加载组件
Vue.use(Router)
export default new Router({
  mode: 'history',              // 路由模式,history 和 hash 两种
  base: process.env.BASE_URL,
  routes: [                    // 配置路由的数组
    {
      path: '/',              // 访问路径
      name: 'home',           // 路由名称
      component: Home         // 对应的组件
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
  ]
})

说明:

1、about 组件的写法是延迟加载:在访问 /about 路径时才会加载该组件,这样可以提高首屏显示速度

2、/* webpackChunkName: "about" */ 的意思是将这个组件添加到 about 这个组中,当访问 about 这个组件时就会添加所有 about 这个组中的组件

路由切换按钮

当我们定义好路由之后,我们就可以在页面中添加按钮跳转到相应的路由,有两种跳转方法:

1、在 HTML 中使用 router-link 标签(相当于a标签)

2、在 JS 中使用 router-push 实现跳转(相当于 location.href )

我们可以在页面中使用 router-link 标签来制作可以跳转的按钮(相当于 a 标签):

<div id="nav">
  <router-link to="/">Home</router-link> |
  <router-link to="/about">About</router-link>
</div>

说明:to 属性用来指定点击按钮时要切换到的路由

image-20181117175229536

router.push

在 JS 中我们可以使用 this.$router.push 实现跳转。

login(){
  console.log( '登录成功' )
  // 跳转到 /
  this.$router.push('/')
}

组件容器

我们在使用 vue-router 时,除了要配置路由之后,最重要的是,我们还需要在页面中使用 router-view 标签来指定组件显示的位置:

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <!-- 组件显示的容器 -->
    <router-view/>
  </div>
</template>

这时所有的路由组件都会显示在 router-view 标签所在的位置上:

1、默认显示路由配置中的第一个路由,也就是 / 路由

2、当切换路由时,也是在这个标签的位置上切换路由

因为我们在 router.js 文件的路由是这样配置的:

routes: [                    // 配置路由的数组
    {
      path: '/',              // 访问路径
      name: 'home',           // 路由名称
      component: Home         // 对应的组件
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
]

第一个是 home 路由,所以默认显示的就是 Home 组件的内容:

image-20181117180842895

案例:添加一个页面

1、首先在 views 目录下创建 Hello.vue 组件

views/Hello.vue






注意:HTML的代码必须要写在一个根标签中。

2、配置路由

router.js 文件中的 routes 数组中添加一个 hello 路由

router.js

routes: [
    ...
    {
      path: '/hello',   // 路径
      name: 'hello',    // 名称
      component: () => import('./views/Hello.vue')    // 加载的组件
    }
]

3、添加切换按钮

App.vue 页面中添加跳转到 /hello 的按钮

App.vue


添加之后保存,保存之后页面会自动刷新:

image-20181117181622305

点击 hello 按钮,就会在 router-view 标签的位置上切换到 Hello 组件:

image-20181117181732185

页面组件与功能组件

组件分为两种:页面组件功能组件

  • 页面组件

用来构建独立页面的组件,保存在 views 目录中,页面组件中可以包含多个功能组件。

  • 功能组件

我们可以将页面中一些独立的小功能制作成组件,这些组件可以被页面组件引入使用,比如:翻页、时间插件等,功能组件保存在 components 目录中,这些组件不能独立显示,只能被包含在一个页面组件中使用。

页面组件和功能组件的关系:(每个页面就是一个页面组件,每个页面中可以包含多个功能组件)

image-20181117191120753

使用功能组件

系统中默认有一个 HelloWorld 组件:

image-20181117190241610

如果要在页面中使用这个组件需要先使用 import 引入该组件,然后在使用标签使用该组件。

修改 Hello.vue 页面:

views/Hello.vue

image-20181117190813376

效果:

image-20181117190856751

路由参数

我们在定义路由时经常需要为路由添加一些参数,比如当点击一件商品进入商品详情页时需要把商品的ID传到页面中。

我们可以使用以下语法定义带参数的路由:

{
  path: '/hello/:id',
  name: 'hello',
  component: () => import('./views/Hello.vue')
}

这时当我们访问 /hello/1 , /hello/tom 等路径时都会匹配 Hello 组件。

如果我们希望只匹配数字做为参数,这时可以使用正则表达式来限制匹配的类型:

id 参数必须是数字:

{
  path: '/hello/:id(\\d+)',
  name: 'hello',
  component: () => import('./views/Hello.vue')
}

在组件中可以使用 this.$router.params 来接收传过来的所有的参数。

比如在 Hello.vue 组件中可以这样接收 id 这个参数:

export default {
    ...
    created(){
        // 打印接收的 id 参数
        alert(this.$route.params.id)
    }
    ...
}

meta 数据

我们在定义路由时,有时我们可能还需要设置一些与这个路由相关的额外的数据,这时我们不可以通过 meta 属性来定义。

比如,我们可以通过 meta 为每个路由对应的页面设置页面标题:

store.js

routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
      meta:{
        title: '首页'
      }
    },
    {
      path: '/about',
      name: 'about',
      meta: {
        title:'关于我们'
      },
      component: () => import('./views/About.vue')
    },
    {
      path: '/hello/:id(\\d+)',
      name: 'hello',
      meta: {
        title:'hello'
      },
      component: () => import('./views/Hello.vue')
    }
]

设置了 meta 数据之后,在每个组件中,我们就可以通过 this.$route.meta 来获取对应的 meta 数据。

在 Hello.vue、About.vue、Home.vue 中都添加初始设置 title 的代码:

created(){
    document.title = this.$route.meta.title
},

效果是在显示每个页面时都会用 meta 中设置的 title 来初始化页面标题:

image-20181118104002573

导航守卫

导航守卫:指在每次路由将要发生变化时,都会被触发的一套钩子函数。

每当点击一个按钮或者在JS 中调用 this.$route.push 实现页面跳转时,首先要先执行导航守卫定义的函数,当函数中再次调用 next 函数时才可以跳转,否则不可以跳转:

image-20181118104639964

我们可创建 VueRouter 时使用 beforeEach 定义全局守卫:

const router = new VueRouter({ ... })

// 为路由绑定一个每次跳转时触发的函数
router.beforeEach((to, from, next) => {
  // ...
})

全局守卫需要三个参数:

to:即将要进入的 router 对象(比如:从 Index.vue 跳转到 Home.vue ,to 指的就是 Home.vue)

from:当前正在离开的 router 对象(比如:从 Index.vue 跳转到 Home.vue ,to 指的就是 Index.vue)

next:是一个函数,在守卫中的最后必须要调用这个函数,调用时分为以下几个情况:

  • next():通过,进入下一个环节
  • next(false):中断当前的导航(不允许跳转)。
  • next(‘/‘):跳转到一个指定的地址(这里是 / ,也可以是其它路径)。

我们是在 main.js 文件中引入的 router ,所以,我们可以在 main.js 中定义全局守卫:

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

router.beforeEach((to, from, next) => {
  // ...
})

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

全局守卫在项目中应用非常广泛,比如,有些页面是必须登录才可以访问的,我们就可以使用全局守卫来实现。

示例:登录的验证

1、在定义路由时使用 meta 来设置一个路由是否必须登录才能访问

router.js 中定义在 meta 中添加 isLogin ,表示必须登录才能访问

{
  path: '/about',
  name: 'about',
  meta: {
    title:'关于我们',
    isLogin: true
  },
  component: () => import('./views/About.vue')
},

2、定义全局守卫

main.js 中添加全局守卫:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

router.beforeEach((to, from, next) => {
  // 如果将要进入的跳转必须登录,那么就先判断用户有没有登录
  if(to.meta.isLogin)
  {
    if(localStorage.getItem('token'))
      next()
    else
      next('/')
  }
  else
  {
    next()
  }
})

嵌套路由

有时一个页面中可能还会嵌套子页面,比如在 hello 页面中,可能还会有 hello tom 和 hello jack 两个子页面,这时我们就可以通过在定义路由时使用 children 数组来定义嵌套路由。

router.js

{
  path: '/hello',
  name: 'hello',
  meta: {
    title:'hello'
  },
  component: () => import('./views/Hello.vue'),
  children: [
    // /hello/tom
    {
      path: "tom",
      name: "hello-tom",
      component: () => import('./views/HelloTom.vue')
    },
    // /hello/jack
    {
      path: "jack",
      name: "hello-jack",
      component: () => import('./views/HelloJack.vue')
    }
  ]
}

然后在 Hello.vue 页在中再添加两个按钮和一个 router-view 设置子页面的位置:

views/Hello.vue

<router-link to="/hello/tom">Hello Tom</router-link> |
<router-link to="/hello/jack">Hello Jac</router-link>
<hr>
<router-view></router-view>

效果:当点击 Hello Tom 和 Hello Jack 按钮时,会切换嵌套组件

image-20181118115827326

嵌套组件关系图:

image-20181118120205288

#Vuex

什么是 Vuex?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

Vue 是由数据驱动的,也就是说每个组件的状态都是由数据决定的,比如一个元素的显示与隐藏就是它的两种状态,这两种状态在 vue 中都会由一个数据来管理。

比如,下面的 img 标签的显示与隐藏由 isShow 这个数据决定,当 isShow 为 true 时显示,为 false 时隐藏:

<template>
    <div>
        <img v-if="isShow" src="../assets/logo.png">
    </div>
</template>

<script>
export default {
  data(){
    return {
      isShow: true
    }
  },
  ...
}
</script>

也就是说现在这个元素的状态由 isShow 这个数据来决定。

在实际工作中,我们有时会遇到这样一种情况:我们需要在一个组件中操作另一个组件中元素的状态,比如我们需要在 b.vue 这个组件中设置 a.vue 中元素的显示状态,但 vue 是不允许跨文件操作数据的:

image-20181118121712159

这时,我们就可以把需要同时被多个组件操作的数据提取出来,由 状态管理器 统一进行管理,在这种模式下,所以的组件都可以操作由 状态管理器 统一管理的数据:

image-20181118122039549

有了状态管理器,我们就可以在所有组件中管理 a.vue 中图片的显示状态。

总结:Vuex(状态管理器),就是可以把需要多个组件同时管理的数据管理起来,让所有组件可以共同操作以改变一个组件的状态 。

使用 Vuex

定义状态数据与方法

在使用 Vux 时,首先我们需要先定义状态数据和操作它的方法。

使用 Vuex 时,最重要的两部分是:statemutation

  • state:保存所有组件公共的数据。(数据部分)

  • mutation:保存操作公共数据的方法。(函数部分)

在最开始构建项目时,如果在安装时勾选了 vuex 组件,那么就已经安装好了 vuex。

store.js 文件中定义 statemutation

image-20181118122531875

store.js 文件中定义 state(数据)和mutations(操作数据的函数):

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
        isShow: false
  },
  mutations: {
      changeShow(){
          this.state.isShow = !this.state.isShow
      }
  },
  actions: {

  }
})

使用状态数据

在 store.js 文件中定义好状态数据之后,我们就可以在页面中使用 $store.state.状态数据名称 来读取状态数据的值了:

views/Home.vue

<template>
  <div class="home">
    <img v-if="$store.state.isShow" alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

调用mutation

当我们要修改状态数据时,我们可以使用 this.$store.commit('方法名') 来调用状态函数来修改状态数据:

views/Home.vue

<script>
...
export default {
...
  created(){
    ...
    // 每5秒改变一次状态
    setInterval(()=>{
      this.$store.commit('changeShow')
    }, 5000)
  }
}
</script>

案例:登录、退出

store.js 中定义登录、退出按钮的状态:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  // 要管理的公共的数据(数据)
  state: {
    isShow:true,
    isLogin: false
  },
  // 定义修改这个数据的方法(方法)
  mutations: {
    changeShow(){
      this.state.isShow = !this.state.isShow
    },
    // 如果要设置参数,第一个参数必须是 state ,代表 state 对象
    //              第二个参数才是我们自定义的参数
    setLogin(state, value){
      state.isLogin = value
    }
  },
  actions: {

  }
})

App.vue 中根据 isLogin 显示按钮:







Login.vue 在登录成功时修改 isLogin 的值




效果:登录、退出时按钮状态发生变化:

image-20181119120150414

vue-axios

官方推荐我们在 vue 中使用 axios 发送 AJAX 的请求。

在开发 SPA 时,我们需要把 axios 集成进来,我们可以使用 vue-axios 这个包。

文档地址:https://www.npmjs.com/package/vue-axios

image-20181118123652167

使用

使用的方法非常的简单:

1、先安装

npm install --save axios vue-axios

2、引入

安装之后需要修改 main.js ,在文件中引入并使用 axios 包:

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'
import VueAxios from 'vue-axios'

Vue.use(VueAxios, axios)

3、使用

引入了之后,我们就可以在项目中使用以下三种方法来使用 axios 了:

Vue.axios.get(api).then((response) => {
  console.log(response.data)
})

this.axios.get(api).then((response) => {
  console.log(response.data)
})

this.$http.get(api).then((response) => {
  console.log(response.data)
})

案例:通过 axios 请求 easy-mock

easy-mock 是一个第三方数据模拟平台,我们可以在该平台上编写接口、模拟数据,然后在项目中使用 axios 请求接口获取数据。

1、注册、登录 easy-mock 并添加一个新的项目

image-20181118124107279

输入项目名称和基础地址:

image-20181118124340180

添加成功之后,点击进入该项目:

image-20181118124411837

2、创建接口

image-20181118124446480

输入模拟数据的语法:

image-20181118125035410

3、在 main.js 中设置接口基地址

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'
import VueAxios from 'vue-axios'

Vue.use(VueAxios, axios)

Vue.axios.defaults.baseURL = 'https://www.easy-mock.com/mock/5bf0edf4643497494c87d25d/api/v1';
Vue.axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

4、在 Home.vue 中显示数据

Home.vue

在初始化时执行 AJAX 获取数据:

export default {
  ...
  data() {
    return {
      goods:[]
    }
  },
  created(){
    this.axios.get('/goods').then((res) => {
      this.goods = res.data.data
    })
    ...
  }
}

将数据绑定到页面:

<ul>
  <li v-for="(v,k) in goods" :key="k">
    {{v.goods_name}} ¥ {{v.price}} <hr>
  </li>
</ul>

效果:

image-20181118125807214


转载请注明: qqwBlog Vue-SPA

上一篇
常问的PHP面试题 常问的PHP面试题
PHP经典 面试题夯实PHP 中传值和传引用的区别传 值:将变量的值拷贝一份赋值给另一个变量,改变任何一个变量的值都不会影响另一个。传引用:将变量的值的内存地址传给另一个变量,新变量简单的引用了原始变量,两个变量指向同一个值,改动会相互影响
2018-12-19
下一篇
ThinkPHP5.1学习总结(持续学习中) ThinkPHP5.1学习总结(持续学习中)
ThinkPHP5.1 学习之路安装ThinkPHP5支持使用Composer安装 如果还没有安装 Composer,在 Windows 中,你需要下载并运行 Composer-Setup.exe。 由于众所周知的原因,国外的网站连接速度很
2018-01-11