回顾Vue2

226 阅读7分钟

Vue

指令

v-if v-else v-else-if v-show v-model v-text v-html v-cloak v-for v-on v-bind v-slot v-pre v-once

v-if和v-show是否可以连用

在vue2中v-show的优先级高于v-if,所以会在每一个节点上都出现v-if,造成性能上的浪费,不建议连用。

v-text和v-html的区别

v-text无法解析html。

v-show和v-if的区别

v-show和v-if的本质区别在于是否渲染了该节点,v-show是通过css的display来控制是否显示,而v-if直接不生成该节点。

v-model是如何实现双向绑定的

v-model可以拆解成props: value和 events: input(input为例子)

<base-input v-model="checkFlag">
等价于
<back-input v-bind="value" @input="(value)=>this.value=value">
指定
model: {
  prop: 'number',
  event: 'change'
},

生命周期

beforeCreate

new Vue()触发后的第一个钩子,当前阶段上计算属性,data等都不能访问

created

数据侦听、计算属性、方法、事件/侦听器的回调函数已被配置完毕,更改数据不会触发updated,如果想在当前生命周期中操作dom,可以通过vm.$nextTick

beforeMount

发生在挂载之前,虚拟dom已经创建完成,可以更改数据,即将开始渲染

mounted

真实dom挂载完毕,数据完成双向绑定,可以访问到dom节点

beforeUpdate

在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。

updated

发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

beforeDestroy

在当前周期可以清除定时器,移除事件监听。

destroyed

发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

监听第三方组件内部的生命周期

Hook Event 是 Vue的自定义事件结合生命周期钩子函数实现的一种从组件外部为组件注入额外的生命周期方法

// 使用
<base-select @hook:created="createdFunc">

生命周期执行顺序

组件调用顺序先父组件后子组件,渲染完成顺序先子组件后父组件,组件的销毁顺序先父组件后子组件,销毁完成顺序先子组件后父组件

父组件beforeCreate->父组件created->父组件beforeMount->子组件brforeCreate->子组件created->子组件beforeMount->子组件mounted->父组件mounted

数据

data为什么是一个函数

因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

定义响应式数据

在data中定义的数据都是响应式的,如果未在data中定义就需要使用vm.$set

删除属性

vm.$delete(obj,key)

Cannot read properties of undefined

相信大家应该都遇到过这个报错提示,当使用a.b.c(a.b未定义)就会看到这个,使用链式操作符解决

let a = {}
console.log(a.b.c) //Cannot read properties of undefined
console.log(a?.b?.c) // undefined

使用方法

  1. npm install --save-dev @babel/plugin-proposal-optional-chaining
  2. 在babel.config.js中添加plugins: ["@babel/plugin-proposal-optional-chaining"]
  3. 重启项目

props

父子组件传值常用

props:{
	count:['Number','String'],
	list:{
		type:Array,
        default:()=>[]	
    },
    deviceItem:{
        type:Object,
        default:()=>{}
    }
}

能否在子组件内修改props的值

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

当props传递的是基本类型时,在子组件中修改值会导致报错,而传递的时引用类型时,修改传递的值不会报错,但是不建议这么做,会破坏单向数据流

watch

监听数据的变化,可以在里面执行异步操作,监听对象内部属性时使用deep或者'obj.xx'

  watch: {
    list(newValue) {
      console.log(newValue);
    },
    deviceItem: {
      handler(newValue,oldVal) {
        console.log(newValue)
	this.getList()
      },
      immediate: true,
      deep: true,
    },
    'deviceItem.name'(newVal){
    }
  },

computed

computed:{
    price(){
      return this.count * 10
    }
},

watch和computed的区别

watch用来监听数据,并且执行异步操作的,数据变化会执行回调。computed本质是一个具有缓存的watcher,结果会被缓存,除非依赖的响应式 property 变化才会重新计算。

mixin

在不同的组件中可能存在部分代码是一样的,这时候可以通过将相同的东西抽离,使用mixin混入

// mixin.js 
export const mixin = {
    data(){
        return {
            lists:[]
        }
    },
    methods:{
        fetchData(){

        }
    },
    created(){
        console.log("-----")
    }
}

// 组件
import {mixin} from './mixin'
mixins:[mixin]

实例property

$parent 父组件实例

获得

子组件调用父组件的方法

// 通过$emit触发事件,父组件监听
// 父组件
<base-select @change="changeValue"/>
//子组件
this.$emits('change',data)


//子组件内直接使用$parents
this.$parents.changeValue()

$children

不保证子组件顺序

$refs

一个对象,持有注册过 refattribute 的所有 DOM 元素和组件实例。

<base-select ref="baseSelect"/>

mounted(){
	console.log(this.$refs.baseSelect)
}

$attrs

未在props中定义的,会在$attrs中

在二次封装第三方组件的时候可以通过$attrs将数据传递给内层的第三方组件,从而减少封装的组件内部props的体积

<el-table :data="list" v-on="$listeners" v-bind="$attrs" >
  <slot></slot>
</el-table>

$listeners

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

// 封装组件时
// base-input
<div>
<el-input v-on="$listeners" v-bind="$attrs">
</div>

<base-input @change="changeValue">

组件

动态组件

一个页面内要根据参数的不同渲染不同的组件

1.用v-if和v-else来控制渲染的组件
<base-select v-if="showName === 'select'">
<base-check v-else-if="showName === 'check">
<base-input v-else>

2.is  componentId 存放引入的组件
<div :is="componentId"></div>
<component :is="componentId">

组件批量全局注册

import baseButton from "./baseButton";
import baseCheck from "./baseCheck";
import baseDialog from "./baseDialog";
import baseFrom from "./baseFrom";
import baseIcon from "./baseIcon";
import baseInput from "./baseInput";
import linkItem from "./linkItem";
let componentList = {
    baseInput,
    baseIcon,
    baseFrom,
    baseDialog,
    baseCheck,
    baseButton,
    linkItem
}

export default {
    install(Vue){
        Object.keys(componentList).map(name=>{
            Vue.component(name,componentList[name])
        })
    }
}

父子组件传值

  1. props 和 emit
<base-select :select-list = "selectList" @changeValue="changeValue">

 // baseSelect
  props:{
  	selectList:{
  		type:Object,
  		default:()=>{}
		}
  }
  
  mounted(){
		this.$emit('changeValue','')
  }

2.$parent $children 获取父,子组件实例

3.refs获取对应的实例方法,属性

// 调用目标上的方法或属性
this.$refs.parent.xx

兄弟组件传值

eventBus

第一种

// 新建event.js
import Vue from 'vue';
export const EventBus = new Vue()

//A组件
import {EventBus} from './event'

mounted(){
	EventBus.$on('msg',msg=>{
		console.log(msg) // 快乐
	})
}

// B组件
import {EventBus} from './event'

mounted(){
	EventBus.$emit('msg','快乐')
}

// 移除监听
EventBus.$off('msg')

第二种全局注册

// main.js
Vue.prototype.$EventBus = new Vue();

//组件内
this.$EventBus.$on()
this.$EventBus.$emit()

跨级组件传值

  1. $attrs,$listeners
  2. Vuex
  3. Provide,inject

二次封装组件

对于后台管理系统,到处都是表格,输入框等,如果只是单单的使用饿了么组件会造成到处都是重复的代码,这个时候就需要统一封装,这样使用的时候就大大减少了代码量,并且后续改动时只需要改封装的组件,不需要定位到具体的文件。

插槽

slotDemo组件
<div>
    <slot name="header">
    <slot></slot>
</div>
使用
<slot-demo>
<template v-slot:header>
具名插槽
</template>
</slot-demo>

默认插槽
<slot-demo>
    <span>默认的内容</span>
</slot-demo>

作用域插槽
// 组件
<slot :slotValue="slotValue"></slot>
//data
slotValue:"作用域插槽"
// 使用
<slot-demo>
    <template #default="{slotValue}">
        <span>{{slotValue}}</span> 
    </template>
<slot-demo>

项目

跨域

浏览器同源策略
请求url的协议,域名,端口三者之间任意一个与当前页面的url不同即为跨域

vue中通过配置代理解决跨域的问题

// vue.config.js  
devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:5757/", //这里后台的地址模拟的;应该填写你们真实的后台接口
        changOrigin: true, //允许跨域
      },
    },
  },

deep样式穿透,修改第三方组件样式

cnpm i node-sass --save
cnpm i sass-loader --save
// 安装出错时 请检查node版本
<style lang="scc" scoped>
  /deep/.el-input__inner{
  	color:red
  }
  ::v-deep .el-input__inner{
    color:red
  }
</scoped>

国际化

// 下载插件
cnpm install vue-i18n --save

// main.js
import VueI18n from 'vue-i18n'
import zh from './lang/zh'
import en from './lang/en'
import enLocale from 'element-ui/lib/locale/lang/en'        //引入Element UI的英文包
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'     //引入Element UI的中文包
Vue.use(VueI18n)
const i18n = new VueI18n({
  locale: 'en-US',    // 语言标识
  messages: {
    'zh-CN': {...zh,...zhLocale},   //	解决element组件的国际化
    'en-US': en    // 英文语言包
  }
})

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

// zh.js
export  default {
    home:{
        title:'标题'
    }
}

//使用
<span>{{$t('home.title)}}</span>

修改elementUI源码

// 注意选择和项目中版本一致的
git clone https://github.com/ElemeFE/element.git 
cd element
npm install


找到packages文件夹下对应的文件 修改代码
// 生成lib
npm run dist

在生成后的lib文件夹中找到自己修改的 和项目中的进行替换(项目中没有其他修改的话可以直接替换整个)

数据处理

深拷贝

JSON.parse(JSON.stringify(this.list))

缺点:

  1. 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
  2. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
  1. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
  2. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
  1. JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
  2. 如果对象中存在循环引用的情况也无法正确实现深拷贝;

lodash

// 安装
cnpm i loadsh --save
//组件内
var _ = require('lodash')
let newList = _.cloneDeep(this.newList)

打包后白屏

// vue.config.js
publicPath: "./"

package.json

{
  "name": "west-po", // 项目名称
  "version": "0.1.0",		// 版本号
  "private": true, 
  // 命令行命令缩写
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  // 项目运行所依赖的模块 --save
  "dependencies": {
  },
  // 项目开发时所依赖的模块 --save-dev
  "devDependencies": {
  },
  // eslint配置
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {
      "vue/no-unused-components": "off",
      "no-console": "off"
    }
  },
  // 用以兼容各种浏览器
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

package.lock.json

锁定安装时的依赖包版本,以确保依赖版本统一

设置全局方法

可以将自定义函数挂载在Vue的原型上 Vue.prototype.$formatData = formatData

vue-router

// 安装
cnpm i vue-router --save
// index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

//main.js
import router from './router'
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

index

// route/index.js
import Vue from 'vue'
import VueRouter from "vue-router";
import Home from '../components/home'

Vue.use(VueRouter)

export default new VueRouter({
    routes:[
        {
            path:'/',
            name:'home',
            component:Home,
            meta: {title: '首页', icon: 'el-icon-house'}
        }
    ]
})

传参

// 第一种
// 页面刷新数据会丢失
this.$router.push({name:xx,params:{id:xx}})

// 第二种
this.$router.push({path:xx,query:{id:xx}})
//url
http://localhost:8080/#/technology?id=1

//第三种
this.$router.push({path:`xx/${id}`})
{
  path: '/technology/:id',
  name: 'technology',
  component: demo,
  meta: {title: 'xx', icon: 'el-icon-collection'}
},

//组件内获取
this.$route.params.xx
this.$route.query.xx

元信息

可以在meta中携带一些参数,例如title

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true , title:'这是一个页面' }
        }
      ]
    }
  ]
})

// 组件内
$route.meta.title // 这是一个页面

路由守卫

前置守卫

// permission.js
import router from './router'
import NProgress from 'nprogress' 
// 验证用户的token是否过期 
// NProgress 进度条
router.beforeEach((to, from, next) => {
  const hasToken = localStorage.getItem("token");
  NProgress.start();
  if (hasToken) {
    next();
  } else {
    if (to.path !== "/login") {
      next("/login");
    } else {
      next();
    }
  }
})

后置守卫

// 关闭进度条
router.afterEach(() => {
  NProgress.done();
});

history模式

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

http://yoursite.com/user/id
需要后端配合

hash

vue-router 默认是hash模式

http://localhost:8080/#/technology

router-view

组件是一个 functional 组件,渲染路径匹配到的视图组件

<slider></slider>
<div class="body-container">
  <router-view></router-view>
</div>

Vuex

是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用场景

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。
// 安装依赖
npm install vuex --save

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
  },
  getters
})

export default store;

//main.js
import store from './store'
new Vue({
 	store,
  render:h=>h(App)
})

State

存放着Vuex中的状态

批量获取多个状态的时候可以使用mapState辅助函数帮助我们生成计算属性

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

Getter

就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

// 访问
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Mutation

可以直接更改状态,只能是同步操作。

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

使用
store.commit('increment')

Action

通过提交mutation来更改状态,可以包含异步操作。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

使用
store.dispatch('increment')

Modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的tate、mutations、actions、getters.

请求

封装axios

// request.js
import axios from 'axios'
// 创建axios实例
const service = axios.create({
  baseURL: xxx, 
  timeout: 20000 
})

service.interceptors.request.use(
    config=>{
       // 统一设置请求头
       config.headers['Authorization'] = getToken()
       return config
    },error => {
        return Promise.reject(error)
    }
)

service.interceptors.response.use(response => {
    return response.data
}, error => {
    const { status } = error.response
    if (status === 401) { // token失效
        // 清除token
        removeToken()
        // 跳转到登录界面
        router.push('/login')
    }
    return Promise.reject(error)
})

export default service

// api.js
import request from  './request'

export function getUser(params,data){
    return request({
        url:'xxx',
        method:'xxx',
        params,
        data
    })
}

参数序列化

qs.stringify将参数格式化字符串

qs.stringify({id:data},{indices:false}) //实现id=1&id=2

鉴于水准有限,总结的可能不太到位,还望各位掘友指出不足之处。