Vue专题-chapter1

530 阅读6分钟

Vue-cli 脚手架工具

概念:Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。

基本使用

// 1、全局安装
npm install -g @vue/cli

// 2.1、创建一个项目 - 终端方式
vue create hello-world

// 2.2、创建一个项目 - 图形化方式
vue ui

// 3 如何快速原型开发(使用于针对单个vue文件做测试)

// 3.1 全局安装vue/cli-service-global
npm install -g @vue/cli-service-global

// 3.2 使用快速原型开发(假设我们当前处于test目录下,该目录下仅有一个test.vue文件,现在我们需要单独运行该文件)
vue serve test.vue

Note:有关快速原型开发,请参阅 Vue 快速原型开发

在 Vue 项目中使用vue-lazyload

1、安装vue-lazyload

npm i vue-lazyload -S

2、main.js 引入插件

import VueLazyLoad from 'vue-lazyload'

Vue.use(VueLazyLoad,{
    error:'./static/error.png',
    loading:'./static/loading.png'
})

3、.vue 文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy

<img class="item-pic" v-lazy="newItem.picUrl"/>

在 Nuxt 项目中使用vue-lazyload

1、安装vue-lazyload

npm i vue-lazyload -S

2、Plugin 目录增加 vue-lazyload.js,loading 图可以去 iconfont 下载

import Vue from 'vue'
import VueLazyLoad from 'vue-lazyload'

Vue.use(VueLazyLoad,{
    error:'./images/error.png',
   loading:'./images/loading.png'
})

3、.vue 文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy

plugins: [
    { src: '~/plugins/vue-lazyload', ssr: true },
]

4、.vue 文件中将需要懒加载的图片绑定 v-bind:src 修改为 v-lazy

<img class="item-pic" v-lazy="newItem.picUrl"/>

使用 v-show 和 v-if 的时机

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

处理数组内容变更

Vue 不能检测以下数组的变动:

1、当你利用索引直接设置一个数组项时,例如:this.list[3] = newValue

2、当你修改数组的长度时,例如:this.list.length = 5

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

// Vue.set 这是解决第一个问题的方案1
Vue.set(this.list, 3, newValue)

// Array.prototype.splice 这是解决第一个问题的方案2
this.list.splice(3, 1, newValue)
// 这是解决第二个问题的方案
this.list.splice(newLength)

就实际情况而言,尚未遇到过第二种情况

批量引入全局组件

随着开发时间越来越久,愈发感觉某些适用于公司项目的特殊组件(指无法在 UI 框架中找到的组件,大多由公司的 UI 设计师自行设计,只适用于公司项目)使用的频率越来越高,便想着将这些使用频率非常高的组件抽取成全局组件(之前都是通过模块系统注册成了局部组件,每次使用时都需引入并注册),方便使用

import Vue from 'vue'
// 如果项目没有安装lodash,可选择安装lodash,或者替换upperFirst和camelCase方法
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径,将需要注册的全局组件统一放这个Base文件夹即可(文件夹地址可根据项目需要修改)
  '../components/Base',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式,组件必须以Base开头,(文件名规则可根据项目需要修改)
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)

  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})

Note:记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

传入一个对象的所有 property

如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post:

post: {
  id: 1,
  title: 'My Journey with Vue'
}

下面的模板:

<blog-post v-bind="post"></blog-post>

等价于:

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

实现页面缓存

keep-alive抽象组件: 实现页面缓存的关键在于利用 keep-alive 标签,被 keep-alive 标签缓存的组件在非活动状态时不会被销毁,而是被缓存,当其再次处于活动状态时,不会触发 created、mounted 等生命周期函数,而是会触发额外增加的两个生命周期函数 activated 和 deactivated

keep-alive的include:keep-alive组件如果设置了 include ,就只有和 include 匹配的组件会被缓存,所以动态修改 include 数组来实现按需缓存,通常都是存储在 vuex 中

DS2eQ1.png

在 APP.vue 中使用 keep-alive 包裹需要被缓存的组件,这里是包裹一个router-view标签,因为被缓存组件是不确定的,某个路由规则下需要缓存,另一个可能就不需要缓存了,我们需要根据实际需求来动态修改 include 数组

// APP.vue
<template>
	<div id="app">
		<top></top>
		<div class="content" v-if="!$store.state.activity">
			<left-navbar class="content-left"></left-navbar>
			<div class="content-right">
				<keep-alive :include="$store.state.keeplive">
					<router-view v-if='isRouterAlive' />
				</keep-alive>
			</div>
		</div>
		// 活动页需要全屏
		<div v-if="$store.state.activity">
		<Login></Login>
		<sidebar></sidebar>
	state.show_upgrade.show"></upgrade>
	</div>
</template>

我们的 include 数组是存储在 vuex 中的,如下:

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

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		// 缓存页列表(name指路由的名称)
		keeplive: [],
	},
	mutations: {
		KEEPLIVE_ADD(state, name) {
			state.keeplive.indexOf(name) < 0 && state.keeplive.push(name)
		},
		KEEPLIVE_EEMOVE(state, name) {
			state.keeplive = state.keeplive.filter(v => {
				return v != name
			})
		},
		KEEPLIVE_CLEAN(state) {
			state.keeplive = []
		},
	},
	actions: {

	},
	modules: {

	}
})

局部缓存还是全局缓存:如果希望一个页面被打开后便被缓存,之后即使关闭它并再次打开也还是第一次的结果,除非用户在该页面进行某些操作才更新页面内容,那么这种场景非常适合全局缓存。

试想另一种情况,有以下四个页面:首页(A)、列表页(B)、详情页(C)、另一个和列表页同级的页面(D),我们希望从列表页 B 进入详情页 C 时,列表页 B 能够被缓存,从而在 C 页面回到 B 页面时能看到被缓存的 B 页面。但是,我们不希望从 B 页面进入 A 或 D 时,B 被缓存,也就是说,我希望 B 被缓存,但只有 B 》C 》B 这种情形才缓存,这种情形我愿意称之为局部缓存。

如何实现局部缓存和全局缓存呢,我的做法是分别在 B 页面和 C 页面手动调用 KEEPLIVE_ADD 和 KEEPLIVE_EEMOVE 实现:

// B 页面 contentManage 是一个文章列表页
 beforeRouteEnter(to, from, next) {
    next((vm) => {
        // 进入 B 页面则开启缓存,你也可以通过在路由元信息中给出标志,从而在APP.vue的beforeRouteEnter路由hook中根据标志添加,那样只需在APP.vue中添加一次(这里说的代码后面会给出)
        vm.$store.commit("KEEPLIVE_ADD", "contentManage");
    });
},
beforeRouteLeave(to, from, next) {
    // 如果要跳转去文章详情页和投保须知页(只在二次确购买保险弹窗打开时),给内容管理页面增加缓存
    if (
        !(
            to.name == "articledetail" ||
            (to.name == "insuranceInformation" &&
                this.modal_state_second_insurance == true)
        )
    ) {
        // 只要不是从B(contentManage)到C(articledetail)再返回B,就清除B的缓存,当然,这里还有一个insuranceInformation页面(暂且称为E),并且从E返回B时,B被缓存还有一个条件:this.modal_state_second_insurance == true
        this.$store.commit("KEEPLIVE_EEMOVE", "contentManage");
    }
    
    // 上面的代码的作用总结:当路由不是以下两种情况,清除除B(contentManage)的缓存
    1. B 》C 》B
    2. B 》E 》B 且仅当B页面符合this.modal_state_second_insurance == true这个条件
    
    // 跳转去投保须知时,关闭二次购买保险确认弹窗
    // 这里的代码与缓存无关,是业务代码,无需关注
    if (
        to.name == "insuranceInformation" &&
        this.modal_state_second_insurance == true
    ) {
        this.modal_state_second_insurance = false;
    }
    
    next();
},
activated() {
    if (this.$route.meta.returnRefresh) {
        this.getArticleList();
    }

    if (this.$route.meta.open_second_insurance) {
        this.modal_state_second_insurance = true;
    }
},

上面的代码中,实现了发生 B 》C 》B 这种路由跳转时,将 B 页面缓存的效果,同时,我们可以注意到,它甚至可以在 B 页面满足某些特殊情形(你的业务需求)时,才在这种路由跳转发生时缓存 B,如上面的 B 》E 》B

至于 C 页面,和缓存相关的代码如下:

// C 页面 articledetail 文章详情页
beforeRouteLeave(to, from, next) {
    // 其实上面你所看到的这种 B 》C 》B 才缓存 B 的代码时不完整的,这句判断控制了,当路由不是 B 》C 》B 这种关系时,清除 B 的缓存,由此,这里的判断加上上面B页面的代码才真正实现了局部缓存
    if (["contentManage"].indexOf(to.name) < 0) {
        this.$store.commit("KEEPLIVE_EEMOVE", "contentManage");
    }
    
    // 这里的代码实现了另一种场景
    // 试想,当我们正经历B 》C 》B这个路由关系的第二阶段,也就是位于C时,我修改了B的某些数据,
    // 但是,由于B是被缓存的,也就是说,当我回到B后看到的依然是旧数据,这不是我们期望的,
    // 因此,我们还需要一种机制来解决这种问题:也就是B 》C 》B大多数情况都是缓存B,
    // 但是当在C页面修改了B的数据时,更新B那部分被修改的数据
    // 下面这句判断,即是通过this.dataChange来判断B的数据是否被修改,至于怎么判断,那是你的业务代码,通常是执行某些修改操作后就将其修改为true
    // 可以看到,当符合上面所述场景后,我们修改了B路由元信息(meta对象)的returnRefresh字段
    if (["contentManage"].indexOf(to.name) >= 0 && this.dataChange) {
        to.meta.returnRefresh = true;
    }
    next();
},

以下是和这个例子相关的路由信息:

// 内容管理 - 内容管理页
{
  path: '/contentManage',
  name: 'contentManage',
  component: () => import( /* webpackChunkName: "contentManage" */ '../views/content/contentManage.vue'),
  meta: {
    // 当在C页面修改B的数据后,我们需将其修改为true
    returnRefresh: false,
    // 从内容管理跳转到投保须知后又返回回来时再次打开二次投保须知的弹窗
    open_second_insurance: false
  }
},
// 内容管理 - 内容管理页 - 文章详情
{
  path: '/contentManage/detail',
  name: 'articledetail',
  component: () => import( /* webpackChunkName: "contentManageDetail" */ '../views/content/detail.vue'),
},

那么如何使用这个 returnRefresh 字段呢,就如一开头所说的, 被 keep-alive 标签缓存的组件在非活动状态时不会被销毁,而是被缓存,当其再次处于活动状态时,不会触发 created、mounted 等生命周期函数,而是会触发额外增加的两个生命周期函数 activated 和 deactivated,也就是说,从 C 页面再次回到 B 页面时会触发 activated 钩子,我们只需在这个钩子里判断 returnRefresh 字段并执行更新函数,如下:

// B 页面 contentManage 是一个文章列表页
activated() {
    if (this.$route.meta.returnRefresh) {
        // 这里重新获取文章列表
        this.getArticleList();
    }

    if (this.$route.meta.open_second_insurance) {
        // 这里再次打开一个弹窗
        this.modal_state_second_insurance = true;
    }
},

Note:一个值得注意的地方是,被 keep-alive 标签包裹的组件必须有 name 属性,并且应该和路由的 name 一致,如下:

// B 页面 contentManage 是一个文章列表页
export default {
    // 实现页面缓存很重要的一步,和路由的name保持一致
    name: 'contentManage'
    data() {
        return {
            
        }
    },
    methods: {
        
    }
}

// B 页面的路由信息
// 内容管理 - 内容管理页
{
  path: '/contentManage',
  name: 'contentManage',
  component: () => import( /* webpackChunkName: "contentManage" */ '../views/content/contentManage.vue'),
  meta: {
    // 当在C页面修改B的数据后,我们需将其修改为true
    returnRefresh: false,
    // 从内容管理跳转到投保须知后又返回回来时再次打开二次投保须知的弹窗
    open_second_insurance: false
  }
},

其实上面一直说的如何实现局部缓存,那么如何实现全局缓存呢,只需在相应的组件(页面)的 beforeRouteEnter 钩子调用 KEEPLIVE_ADD 即可,也不需要移除了

看到这里,你或许会有疑问,通过多个 mutation 在多个路由钩子里如此操作,不是过于繁琐吗,事实上,的确如此,过于繁琐,但是也相应具有灵活性,就如上面所述的,你甚至可以在 B 页面满足 this.modal_state_second_insurance == true 时,才去缓存 B,你也可以在 C 页面修改了 B 页面数据时,选择不再缓存 B