vue2深度学习
一、 标签属性
-
v-if
和v-show
的区别v-if
每次切换都会重新删除或创建元素,切换性能消耗较高,频繁切换不建议使用v-if
,推荐使用v-show
- 使用
v-if
值为false
的组件,在进入时,不被渲染创建,而使用v-show
的组件,会进入时就将所有组件创建了 - 切换时,不会触发组件生命周期(进入时已经创建了)
v-show
不会进行dom节点的删除和增添。切换时,只是进行元素的样式显隐修改:display的样式值,初始化渲染消耗较高- 使用场景
- v-if多用于显示隐藏有数据刷新情况 - 初次加载较快
- v-show多用于固定显隐,如某些按钮的显示隐藏
-
v-bind
和v-model
的区别v-bind
- 用来绑定数据和属性以及表达式
- 只能实现数据的单向绑定,从M(data)自动绑定到V(页面),无法实现数据的双向绑定。
- 形式:
v-bind:某属性="数据"
,v-bind:
可以简写为“:”
v-model
仅适用于输入框,实现数据双向绑定,- 形式:
v-model="数据"
注:v-model在表单元素外使用不起作用
-
v-on
-
事件的表达可简写为
@
后跟事件名 -
可通过
@事件.属性
可以对事件进行一定控制.stop
:阻止事件冒泡.prevent
:阻止默认行为;例如a的跳转.capture
:添加事件的监听捕获.self
:只有事件发生在自己身上时才触发.once
:只触发一次事件,串联情况下,前边的控件效果也只有一次效果
-
.stop
和.self
的区别:.stop
写在被阻止冒泡的事件身上,阻止该事件引发的所有冒泡.self
写在冒泡途中的某事件身上,阻止冒泡行为在自己身上的触发,仅当事件发生在自己身上时才被触发,冒泡在他身上无效,但是当触发它时,也会产生冒泡
-
一、 组件内属性
-
watch的使用
-
watch内监听的数据有两种书写格式
-
函数式:
watch: { aaa(newName, oldName){ // 第一个参数 : 新值 // 第二个参数 : 旧值,之前的值 } // 当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行 // 当监听一个对象时,不能监听其属性变化 }
-
对象式: aaa: {}
watch: { aaa: { handler(newName, oldName) {}, // 发生变化便执行的函数 // 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的 immediate: true, // immediate值为true时,会监听第一次绑定 deep: true // 是否开启深度监听 - 优点:监听对象任意值发生变化都会触发执行 -- 缺点:所监听的对象有任意值变化,都会遍历监听所有属性,所有相对十分消耗性能 } // 定向深度监听 - watch不能监听 aaa.b这样的值,但是可以将前面的那种写法改成字符串形式,即'aaa.b',如此便可以定向监听了 // 示例 'aaa.b': {} }
-
-
路由监听
data:{} watch:{ '$route' (to, from){ console.log(to, from) console.log('当监听到路由地址改变时,会触发,常用来监听复用同一个组件被多个不同路由复用的情况') } }
-
-
计算属性和data的数据区别
-
data里边的数据只会在页面构建时进行获取,并将其变成静态数据,构建后边不会在改变了
-
computed计算属性里边的值,会时刻监督影响输出数值所涉及变量的变化,从而进行数据重渲染
-
计算属性里边的值(不管是不是与输出值相关)发生变化,都会触发计算属性执行
计算属性里边要获取路由信息,需要写成函数的return形式
-
vue的计算属性中仅包含对返回值的计算,不能用于修改data里边的数据(此问题发生在对象和数组中,引用类型,引者改权)
-
计算属性小案例
在echarts图表中,通过生命周期
mounted
来调用实现图表构建函数,而图表调用值仅发生在生命周期调用时的一次,即对计算属性里边的值仅调用一次,计算属性在没有被调用,便将数据缓存里,后边改变计算属性里边的相关数据,由于计算属性值没有被调用,所以数据不会刷新
-
-
数据优先级
props > methods > data > computed > watch
二、 路由
-
路由模式:
hash
模式: 带#
的- 路由跳转时,若找不到对应路由,不会发送请求
- 打包自测时,使用hash,如果使用
histor
模式,会出现空白页问题
history
模式: 不带#
的 – 需要后端重定向- 路由跳转时,找不到对应路由,会将对应路由带在路径上,发送一次请求
-
三种路由传参
-
字符串形式
this.$router.push('/abc/' + this.aaa + '?k=' + this.bbb)
-
模板字符串
this.$router.push(`/abc/${this.aaa}?k=${this.bbb}`)
-
对象传参
this.$router.push({ name: 'abc', params: { keyword: this.a }, query: { k: '你好' } })
-
-
路由传参误区
-
路由跳转传参时,对象的写法可以是name、path形式。但是,path不能与params传参一起使用(若一起使用,会报错)
-
配置路由时,使用params参数占位了但是跳转时却不传参数 - 此时路由路径会出现问题(路径参数消失 - 若有问号,则问好前的路由路径丢失), 若想解决,可以在params参数后面加一个问号
{ path: '/abb/:aaa?', name: 'abb', ... }
-
配置了params参数,传的是空值(空的字符串) - 路径会出现问题(同不传一样,路径消失),可以通过或运算,取
undefined
this.$router.push({ name: 'aab', params: { keyword: '' || undefined, abc: '你好' } })
-
路由组件可以通过配置路由params参数及props来传props参数(只能传params参数),也可以直接配置路由的props项,将其配置为对象(传死值)来传递对应参数(额外传值),以及将props项写成函数的形式来传递参数(常用)
// 路由文件 // 形式一 { path: '/aab/:abc', name: 'aab', props: true } // 使用 this.$router.push({ name: 'aab', params: '22' }) // 形式二 { path: '/aab', name: 'aab', props: { aa: '李大爷', bb: '王二狗' } } // 形式三 { path: '/aab', name: 'aab', props: ($route) => { return { keywor: $route.params, ka: $route.query } } }
-
-
路由编程式导航重复点击问题
原因:编程式导航引入了promise,而promise需要传递一个成功或者失败的回调
解决:// 路由文件 - 对push进行二次封装 let originPush = VueRouter.prototype.push // 将vueRouter原型对象的push存一份 VueRouter.prototype.push = function(loction, resolve, reject) { if(resolve && reject) { originPush.call(this, loction, resolve, reject) } else { originPush.call(this, loction, ()=>{}, ()=>{}) } }
导航守卫
-
全局守卫
-
全局前置守卫
beforeEach
router.beforeEach((to, from, next) => { // to: 代表去的路由 // form: 代表来着哪个路由 // next: 控制路由的跳转与取消 -- next()跳转 --- next(false)禁止跳转 ---next('要跳转的路径') 跳转到指定路径 ---若没有此选项,则不能进行路由页面跳转 })
-
全局后置守卫
afterEach
-
-
路由独享守卫
- beforeEnter(to,form,next){}
- 只有进入当前页面时才执行
- 写于当前路由配置项中
-
组件内守卫
写于对应组件内,与生命周期同级
组件守卫里面不能访问当前组件实例的属性和方法- beforeRouteEnter(to, from, next){} 进入组件前执行的函数
- beforeRouteUpdate(to, from, next){} 组件复用时执行的守卫函数 – 有条件的,不好用
- beforeRouteLeave(to, from, next){} 离开组件前执行的守卫函数 – 不使用next()的话,不能离开当前页面
-
复用组件数据刷新
// 原理 - 复用同一个组件,不会触发生命周期,但是依然会离开当前组件,然后再进入当前组件,这时,就可以再守卫那里做事情了 beforeRouteEnter (to, from, next) { console.log('进入') next((vm) => { // 所有数据都在vm上 vm.aaa = true }) }, beforeRouteLeave (to, from, next) { console.log('离开') this.aaa = false next() } 思路:离开时使用 v-if = false 将组件关掉,进入时等于true使组件重新渲染
-
页面路由跳转,滚动清零
在路由配置文件里边加上scrollBehavior(to, from, saved){return {x:0,y:0}}
-
路由拆分
// 路由源文件 router > index.jsx import Vue from 'vue' import Router from 'vue-router' // 拆分路由引入 import VOTE from './modules/vote' Vue.use(Router) // 定义路由 const routes = [ ] const router = new Router({ mode: 'history', routes, VOTE }) export default router // 拆分的文件 export default [ { path: '/vote/index', name: 'VoteIndex', component: resolve => require(['@/view/vote/index'], resolve) }, 。。。 ]
三、 组件关系
-
组件复用
// 当复用组件是,可以通过watch来监控路由 watch: { $route(to, from){ } }
-
组件传值
-
父传子
-
子组件通过
props
接收父组件传来的值 -
type 也可以是一个自定义构造器函数,使用 instanceof 检测。
-
在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还无法使用
-
单向数据流,子组件可以通过计算属性或者watcha来监听修改
// 传值校验 poprs: { a: Number, // 基础类型校验 b: [String, Number], // 多种类型校验 c: { type: String, required: true, // 必传 default: 100 // 有默认值 - 一般与必传不一起使用 }, d: { // 对象或数组的使用 type: [Object, Array], validator: (val) => { // 自定义校验 return val > 10 } default: () => { return {} // 或 return [] } } }
-
-
子传父
子组件通过
this.$emit("方法名", 数据)
向父组件传值 – 本质是触发父组件的@方法名
对 应的函数 -
公共bus
// gloublBus.js // 名字自定义 import Vue from 'vue' export default new Vue()
// 发送方 import gloubs from '公共bus地址' // 某函数下 gloubs.$emit('注册key名--注册车辆', data) // data为要穿的数据,可以是任何值吧
// 使用方 import gloubs from '公共bus地址' // 生命周期函数created()或 mounted()下 gloubs.$on('使用对应的注册的车名', res => {})
也可以
// main.js中 Vue.prototype.$bus = new Vue()
// 发送 this.$bus.$emit('xxx', data) // 接收 this.$bus.$on('xxx', ()=>{})
-
四、 插槽
一共有三种插槽:匿名、具名、作用域
语法:必须写在引用组件内,子组件必须有<slot></slot>
-
匿名插槽
<!-- 父组件 --> <demon> 写于父组件 </demon> <!-- 子组件 --> <template> <slot></slot> </template>
特点:可以直接接收引用处写在组件内的内容,并用其替换子组件的
<slot></slot>
,无需特殊操作
缺点:单向广播,无差别替换 - 下边些几个<slot></slot>
,将引用处内容重复几次 -
具名插槽
<!-- 父组件 --> <demon> <div slot="zhangsan"></div> <div slot="lisi"></div> <!-- 或者 --> <template slot="zhangsan"></template> <!-- 或者 - 注意: v-slot 只能添加在 <template> 上,除非子组件是匿名插槽,此时可以直接写于组件上--> <template v-slot:zhangsan></template> </demon> <!-- 子组件 --> <template> <slot name="zhangsan"></slot> <slot name="lisi"></slot> </template>
特点: 针对性放矢,对号入座式替换,可以根据需要对不同部分内容进行对应替换
缺点: 也是单向 -
作用域插槽
<!-- 父组件 --> <demon> <!-- 第一种写法 --> <!-- 匿名写法 --> <div slot-scope="nihao"> {{nihao}} </div> <!-- 或者 --> <!-- 具名写法 --> <div slot="zhangsan" slot-scope="nihao"> {{nihao}} </div> <!-- 或者 --> <!-- 插槽写法 -- 一般都这样写 --> <template slot="zhangsan" slot-scope="nihao"> {{nihao}} </template> <!-- 第二种写法 --> <template v-slot:zhangsan="nihao"> {{nihao}} </template> </demon> <!-- 子组件 --> <template> <slot name="zhangsan" :nida="msg"></slot> </template> <script> export default { data () { return { msg: '我是你大爷' } } } </script>
- slot,slot-scope可以写在普通元素上,也可以写在template上,但是两个属性必须写在同一个元素标签上,不然无效 – 测试过,未写于同一标签,不显示内容
- 特例: 在使用表格组件时,自封装表格,在引用组件上使用
<el-table-column slot="setting"><template slot-scope="scope"></el-table-column>
时,可以分开写 – 原因: 未知 - v-slot替换了slot,slot-scope的分开写法,但是v-slot只能写于template标签上,不然报错
- v-slot可以简写成’#',只能写于template标签中
五、 部分API学习
- 全局 - API
-
vue.directive(key, definition)
- key {String}: 注册指令的名字 - 就一个字符串
- definition {function | object}: 对注册指令的的定义 - 调用该指令对应执行的事件,可以是一个函数或是一个对象
解释: 自定义指令
范例 - 注册一个自定义指令v-focus// 全局注册自定义指令 // 对象形式注册 Vue.directive('focus', { // 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 bind: function () {}, // 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中) inserted: function () {}, // 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。 update: function () {}, // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。 componentUpdated: function () {}, // 只调用一次,指令与元素解绑时调用 unbind: function () {} }) // 函数形式注册 - 只会调用bind和update - 触发相同行为 Vue.directive('my-directive', function (el) {}) // 局部注册自定义指令 directives: { focus: { // 指令的定义 inserted: function (el) {} } } // 使用示例 - <input v-focus:foo.a.b="message"> // 参数: /** * 1、 el : 指令所绑定的元素,可以用来直接操作 DOM。 * 2、 binding: 一个对象 * { * name:指令名,不包括 v- 前缀。 * value:指令的绑定值 - 解析执行后的具体值 * oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用 - 无论值是否改变都可用 * expression:字符串形式的指令表达式 - 绑定值解析执行前的字符串 * arg:传给指令的参数 - 例如: v-focus:foo --> v-focus:[argument]="value" * modifiers:一个包含修饰符的对象 * vnode:Vue 编译生成的虚拟节点 * } */
-
vue.filter(key, definition)
- key {String}: 过滤器的名字 - 就一个字符串
- definition {function}: 过滤的函数
解释: 过滤器
范例
注: 可串联多个 - 筛选后,再筛选<!-- 使用一 - 双花括号中 --> <div>{{ message | capitalize }}{{ message | capitalize }}</div> <!-- 使用二 - v-bind 中 --> <div v-bind:id="message | capitalize"></div>
// 全局注册过滤器 Vue.filter('capitalize', function (value) {}) // 返回处理后的值 // 局部注册过滤器 filters: { capitalize: function (value) {} }
-
vue.mixin({}) – 混入方法
- 作用:给vue添加一些公共的方法和数据
- 混入的生命周期高于原生的生命周期
-
五、 项目配置
-
环境变量配置
-
开发环境: 根目录下创建
.env.development
VUE_APP_ENV = 'dev' VUE_APP_BASH_API = '基础路径' # 必须以VUE_APP开头
-
生产环境: 根目录下创建
.env.production
VUE_APP_ENV = 'pro' VUE_APP_BASH_API = '基础路径' # 必须以VUE_APP开头
-
-
环境变量使用
// 使用判断 process.env.VUE_APP_ENV // 判断这个的值,取对应地址
六、 特殊语法
-
本质:是一个语法糖,简化了父子传值(具体来讲,是简化父组件传值写法)。
效果:不用父组件写自定义监听事件,便可以使子组件修改父组件的值- 格式:子组件传递给父组件值的格式:this.$emit(‘update:aaa’,值)
- update+冒号是必须写的,后面跟的 aaa 是父组件传来的值的名称
- 第二个值便是传递给父组件的值
-
.sync使用示例
<!-- 父组件传值变动写法 --> <组件名 :aaa.sync="bcc"/> <!-- 子组件返回值变动写法 --> <script> 方法名() { this.$emit('update:.sync前边的值,其实也就是对应的方法', 值) } </script>
-
原型:
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
-
简化后:
<text-document :title.sync="doc"></text-document>
总体简化了一个
v-on
及对应的监听事件 -
子组件里边要做改变
// 原来的写法 this.$emit('事件名',值) // 现在的写法 this.$emit('update:.sync前边的值,其实也就是对应的方法', 值) 示例: { this.$emit('update:title', 123) }
-
- 格式:子组件传递给父组件值的格式:this.$emit(‘update:aaa’,值)
-
this.$nextTick()
作用及用法- 作用:用于获取vue更新之后的dom
- 确保数据更新视图之后才执行(数据数据变了,DOM数据也变化后)
- 属异步,可以理解为一个定时器
- vue中使用的都是虚拟DOM,当对页面进行数据修改时,同步操作是获取不到DOM元素的内容的。因为虚拟DOM到真实的DOM是需要时间的,大型项目约在17~20ms之间。
- 使用场景:当你想要在修改修改数据之后,马上操作数据影响的DOM
-
ref
- vue中获取dom的方法
- 可以通过ref来获取子组件内的方法或数据
七、 vuex
- 使用vuex的好处
- 能够在vuex中集中管理共享数据,易于开发和后期维护
- 能够高效的实现组件之间的数据共享,提高开发效率
- 存储在vuex中的数据都是响应式,能够实时与各个页面保持同步
vuex对外只认store – 在全局中。
即: 在main.js中导出时,必须是store
此时可以在this里面找到$store
属性,若是其他的,则在全局中找不到你所公布的那个vuex属性,且也没有$store
-
在main.js中错误的vuex导出
// 引入 import Vue from 'vue' import abcc from './自定义路径/vuex' // vuex那个文件名可以随意取名,无所谓 // 导出 new Vue({ abcc, render: h => h(App) }).$mount('#app')
这里边的abcc是随便取的vuex导入名,可以认为他可代替任何不是store的名字
这样写的结果是在全局中找不到vuex属性,即你即找不到*$store*也找不到aabc这个属性
伴随错误:
Error in render: “TypeError: Cannot read property ‘state’ of undefined” found in 文件地址
-
正确引入
// 引入 import Vue from 'vue' import store from './自定义路径/vuex' // vuex那个文件名可以随意取名,无所谓 // 引入的名称发生了变化 // 导出 new Vue({ store, render: h => h(App) }).$mount('#app')
此时vuex使用便正常了
-
vuex 传参
-
vuex只能传递一个参数,若要传递多个参数,则可以以对象或者组织的形式传入
-
vuex之grtters
grtters中的数据,在没有使用的时候,是不会请求的
-
使用
-
state的使用
-
在state中,写入共享属性及值
-
在需要的地方通过
import { mapState } from 'vuex'
的方式引入 -
引入后,在计算属性computed中对数据进行导入使用:
computed: { ...mapState(['对应state中的数据']) }
-
在其他组件中,也可以通过
this.$store.state.要访问的数据名字
来访问相应数据
-
-
数据操作 - 数据修改–方法引用:
错误示例:可以通过对"this. s t o r e . s t a t e . 要访问的数据名字 " 数据进行改变达到对同步数据进行改变,当项目大了,不利于维护正确示例:通过对 m u t a t i s 中定义函数,用以数据改变,在通过 t h i s . store.state.要访问的数据名字"数据进行改变达到对同步数据进行改变,当项目大了,不利于维护 正确示例:通过对mutatis中定义函数,用以数据改变,在通过this. store.state.要访问的数据名字"数据进行改变达到对同步数据进行改变,当项目大了,不利于维护正确示例:通过对mutatis中定义函数,用以数据改变,在通过this.store.commit(‘函数名’, ‘参数一’)来调用对于函数
-
也可以使用导入式引入
-
在需要使用方法的地方通过
import { mapState, mapMtations } from 'vuex'
的方式引入 -
在组件方法里边通过使用
...mapMutations(['方法名'])
methods:{ ...mapMutations(['方法名']), // 建议写在第一的位置,便于查找其他方法 }
-
后边调用时,可以通过
this.方法名(参数)
调用对应方法 -
想要调用actions中的函数,需要通过this.$store.dispatch(‘函数名’)
-
-
同步异步:
-
在mutations中写异步代码,效果可以在页面显示,但是违反规则,故不要再mutations函数中共湖南执行异步操作
-
异步操作中,action无法直接操作state中的数据,需要调用间接调用mutations中的函数来执行修改
abc(context){ context.commit('mutations中的某个函数') }
- gitter可以理解为对state中的数据进行加工形成新的数据
- getter使用:可以通过"this.$sotore.getters.名称"来调用getter中的函数
也可以通过导入的方式将getter中的数据加载到相应组件
impor { mapGetters } from ‘vuex’// 数据引入
再计算属性里边导入:computed: {
…mapGetters([‘对应getter中的数据’])
}
getter中的数据需要return出来 --即有返回值
-
-
扩展
-
在vue中使用
require()
来获取"本地"静态图片 – require() - 文件加载原因:vue在DOM中直接引入的图片会被转为 base64 格式,使用变量引入的话,图片不会转为 base64 格式,因此引入的图片不会正常显示,使用require(‘./aaa.png’) 可以将引入的图片转为base64
附:在标签中直接写上图片路径,路径会转为 base64,但是如果通过变量设置静态路径,则不会转为 base64,仍然显示变量值对应的路径,故图片无法正常显示 -
读取文件夹下的文件名
require.context('../abc',false,/.\.js$/)
参数一:文件夹的路径
参数二:是否查询子文件夹
参数三: 要匹配的文件后缀 – 是个正则表达式const file = require.context('../abc',false,/.\.js$/) console.log(file.keys()) // 通过keys()可以打印文件下的所有文件名 - 构成一个文件名数组
-
组件的切换过渡
-
将过渡事件写在vue定义的一个"域"内:
<transition></transition>
-
通过给
<transition>
里边加name="my"
属性可以将相应的阶段属性的v-
改成my-enter
的样式 -
在样式标签里边进行定义样式:
v-enter
:时间点-进入之前/元素初始状态/还没进入页面v-leave-to
:时间点-动画离开后/离开的终止状态/动画已经结束v-enter-active
:入场时间段v-leave-active
:离场时间段
-
在离开或进入的时间段里边写动画属性便可以产生动画效果
// 例: .v-leave-active{ transition: all 0.8s ease; }
// 在给组件设置动画时,对它的外部(外置部位)加一个过渡组件包裹 <transition mode="过渡效果"> <component :is="key"/> </transition>
-
-
定义指令
-
定义全局指令:
Vue.directive( "指令名称",{含有相关函数的对象})
- 指令名称定义时前边不加
v-
前缀,但是,调用/使用时名称前边必须加v-
前缀,以此进行调用。 - 每个函数的第一个参数都是el,表示被绑定的那个元素,是一个原生的js对象
- 第二个参数是一个对象,包含指令名(name)绑定值(value/expression)
Vue.directive( "指令名称", { bind:function (el) {} // 每当指令绑定到元素上时,会立即执行bind函数,只执行一次。 inserted:function (el) {} // 元素插入到dom中时会执行inserted函数,仅执行一次 updated:function (el) {}// 当VNode更新时执行updated,可能会触发多次 })
- 指令名称定义时前边不加
-
定义私有指令:
// 与data同级 directives:{ "指令名称": { // 对象加相应函数 } // 简写: '指令名':function (el,binding) { // 执行的函数 // 等同于把事件绑定在bind和update上 } // 例如: "color":function (el, binding) { el.style.color = parseInt(binding.value)+"px" } }
-
-
组件创建
-
全局创建(注册)
Vue.component('组件名',{ template:'<h3>我是你的组件</h3>' }) // 简写时,将Vue.extend()省略,直接换成对象,包含一个template属性名。
-
外联创建
<template id="对应的id名"> <div> 里边写正常的HTML的内容,但要被一个根元素包裹 </div> </template> Vue.component('组件名', { template:'#id名' })
-
私有组件创建
components:{ "组件名":{ template:"<div>组件内容</div>" } sss:{ template:"id名" } }
-
组件的data
- 必须是一个方法,且必须是返回一个对象
- 使用方法和全局data一样,但只能在自己的组件中使用。
Vue.component('组件名',{ template:'<h3>我是你大爷{{msg}}</h3>', data: function () { return{ msg : 'nia' } } })
-
data方法的return{}和外联对象
- 使用外联的对象,在同一个组件多次调用时,会发生所有组件互相影响的效果(有相应变化)
- 原因:对象地址相同
- 使用{}的好处:每次创建组件时,各自的对象分开,互不影响
-
对template属性值的介绍:
不论哪种创建方式创建的组件,组件的template属性指向的内容模板(值)
有且仅有唯一一个根元素 — 即一个根div或什么的元素,无并接属性。
- SPA(单页面应用)缺点
- SEO优化不好(SEO是基于多页面应用的)