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
使用方法
- npm install --save-dev @babel/plugin-proposal-optional-chaining
- 在babel.config.js中添加plugins: ["@babel/plugin-proposal-optional-chaining"]
- 重启项目
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])
})
}
}
父子组件传值
- 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()
跨级组件传值
$attrs,$listenersVuexProvide,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))
缺点:
- 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
- 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
- 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
- 如果对象中存在循环引用的情况也无法正确实现深拷贝;
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
鉴于水准有限,总结的可能不太到位,还望各位掘友指出不足之处。