最开始学Vue,感觉容易,后来实际应用中,很容易忽略一些问题,犯些低级错误,或者,以前看文档根本没注意的地方。那么,就总结一下吧。
总结:
只有当实例被创建时就已经存在于 data
中的 property 才是响应式的。
如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么需要设置一些初始值。
对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。
还可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名。
数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你可以使用 splice
:
vm.items.splice(newLength)
应用:
要用的值都在data中初始化一下,不然后面用到再赋值不会同步更新视图。
如果是对象和数组,可以使用$set方法。
比如:更新表格中某条数据。
this.$set(this.tableData,index,row);
生命周期钩子的 this
上下文指向调用它的 Vue 实例。
不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a)
或 vm.$watch('a', newValue => this.myMethod())
。
应用:箭头函数中的this指向父级,不指代Vue实例。
ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this
的值将保持为闭合词法上下文的值)。
created 在实例创建完成后被立即调用。
在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el
property 目前尚不可用。
应用:页面数据初始化方法写在create和mounted中都可以,区别在于是否需要获取dom节点。
比如:动态路由参数的获取(也可以写在mounted),设置搜索默认值(如日期区间默认当前一个月)可以写在create中。
挂载阶段中所做的主要工作是创建Vue
实例并用其替换el
选项对应的DOM
元素,同时还要开启对模板中数据(状态)的监控,当数据(状态)发生变化时通知其依赖进行视图更新。
mounted
不会保证所有的子组件也都一起被挂载。
如果你希望等到整个视图都渲染完毕,可以在 mounted
内部使用 vm.$nextTick。
mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }
在组件内使用 vm.$nextTick()
实例方法特别方便,因为它不需要全局 Vue
,并且回调函数中的 this
将自动绑定到当前的 Vue 实例上。
因为 $nextTick()
返回一个 Promise
对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}
async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
async函数可能包含0个或者多个await表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该await表达式的返回值。使用async / await关键字就可以在异步代码中使用普通的try / catch代码块。
await关键字只在async函数内有效。如果你在async函数体之外使用它,就会抛出语法错误 SyntaxError 。
async/await的目的为了简化使用基于promise的API时所需的语法。async/await的行为就好像搭配使用了生成器和promise。
async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。
例如:
async function foo() { await 1 }
等价于
function foo() { return Promise.resolve(1).then(() => undefined) }
在await表达式之后的代码可以被认为是存在在链式调用的then回调中。
应用:使用promise方便控制函数的调用顺序。比如新增页面的基本信息保存和附件保存是两个方法。需要先保存基本信息,再保存附件,附件保存成功或失败后,都需要跳转编辑页面。
activated
被 keep-alive 缓存的组件激活时调用。
该钩子在服务器端渲染期间不被调用。
应用:页面初始化方法写在activated中一般不会执行,除非页面有缓存更新。
比如一个编辑页面,有缓存,表现为用户随便修改点啥,然后又去浏览其他页面,等再回头看这个编辑页的时候,上次修改的地方还保留着,感觉挺好。如果没缓存,不好意思,修改没点击保存就恢复原样。
页面加了缓存,就需要配合使用activated刷新页面,不然页面初始化方法写在mounted中只会执行一次。这样做有个弊端,用户切换页面,留不住修改但没保存的数据。
终极解决方案就是使用动态路由(不用session)传参,同一个编辑页支持重复打开多个标签页,不用activated,页面初始化方法写在mounted中。
mixins
-
类型:
Array<Object>
-
详细:
mixins
选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和Vue.extend()
一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
-
示例:
var mixin = { created: function () { console.log(1) } } var vm = new Vue({ created: function () { console.log(2) }, mixins: [mixin] }) // => 1 // => 2
extends
-
类型:
Object | Function
-
详细:
允许声明扩展另一个组件 (可以是一个简单的选项对象或构造函数),而无需使用
Vue.extend
。这主要是为了便于扩展单文件组件。这和
mixins
类似。 -
示例:
var CompA = { ... } // 在没有调用 `Vue.extend` 时候继承 CompA var CompB = { extends: CompA, ... }
$listeners 包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。
子组件
<template>
......
<el-collapse-transition name="el-zoom-in-top">
<div v-show="!collapse || visible || reviewed" class="handle-body" :style="{'padding-top':hasHeader?'0':'20px'}">
<slot name="body"></slot>
<el-row v-if="$listeners.reset || $listeners.search" class="field-form-btns" type="flex" justify="center">
<el-button v-if="$listeners.search" type="primary" size="medium" @click="$emit('search',$event)">查询</el-button>
<el-button v-if="$listeners.reset" size="medium" @click="$emit('reset',$event)">重置</el-button>
</el-row>
</div>
</el-collapse-transition>
</template>
父组件
<screen-card title="查询信息" :open="true" :collapse="true" @reset="resetForm('searchForm')" @search="getTableData">
<template v-slot:body>
......
</template>
</screen-card>
main.js
import App from './App.vue'
//render 类型:(createElement: () => VNode) => VNode
//render 字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。
//该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。
//Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。
//如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。
//可以使用 vm.$mount() 手动地挂载一个未挂载的实例。
//vm.$mount( [elementOrSelector] ) 返回值:vm - 实例自身
new Vue({
router,
store,
render:h=>h(App)
}).$mount(elementOrSelector:'#app')
其他写法如下,不同的写法,取决于使用vue-cli初始化项目时的一些配置。
new Vue({
el:"#app",
router,
store,
template:'<layout>',
components:{layout}
})
export和export default的区别 或 import XX 和 import {XX} 的区别
//src\base\security-password.vue
export default{
name:"SecurityPassword",
data(){
return{
}
}
}
//src\views\login\index.vue
import SecurityPassword from '@base/security-password'
//src\layout\components\AppMain.vue
export default{
name:"AppMain",
data(){
return{
}
}
}
//src\layout\components\index.js
export {default AppMain} from './AppMain';//src\layout\components\AppMain.vue
export {default Navbar} from './Navbar';
export {default Sidebar} from './Sidebar/index.vue';
//src\layout\index.vue
import {AppMain,Navbar,Sidebar} from './components'
Vue中使用base64编码和解码
npm install --save js-base64
引入:import { Base64 } from 'js-base64'
使用:
async getNewsDetail() {
const res = await getNewsDetail(this.query)
this.article = res
this.article.content = Base64.decode(res.content)
}
Cannot read property 'protocol' of undefined
错误原因:VueResource 需要在 axios 之前引用(use)。
import VueResource from 'vue-resource'
import axios from 'axios'
import Vuex from 'vuex'
import store from '@/store'
// Element 引入。需要注意的是,样式文件需要单独引入。
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(VueResource)
Vue.use(axios)
Vue.use(Vuex)
Vue.use(store)
// 项目中所有拥有 size 属性的组件的默认尺寸均为 'small',弹框的初始 z-index 为 3000。
Vue.use(ElementUI, { size: 'small', zIndex: 3000 })
改变v-html渲染出来的样式
应用:资讯详情,渲染经过base64解压出来的html标签格式的文本。用到Vue的 v-html 属性,css ( >>>,作用相当于 /deep/ )设置文本中图片宽度100%。
结构
<div class="article">
<div class="title">{{ article.title }}</div>
<div class="author">
<span>{{ article.title }}</span>
<span>{{ article.createTime }}</span>
</div>
<div class="content" v-html="article.content"/>
<div class="pv"><span>{{ article.pv }}</span>浏览</div>
</div>
方法
methods: {
async getNewsDetail() {
const res = await getNewsDetail(this.query)
this.article = res
this.article.content = Base64.decode(res.content)
}
}
样式
div.article {
margin-top: 4px;
margin-bottom: 24px;
background: #fff;
padding: 10px;
div.title {
line-height: 24px;
font-size: 16px;
color: #000;
}
div.author{
margin:16px 0;
font-size:12px;
span:first-child{
font-weight: 500;
color:#333;
padding-right:12px;
}
span:last-child{
font-size:10px;
color:#808080;
font-weight: 300;
}
}
div.content{
color:#333;
font-weight: 500;
line-height: 18px;
font-size:12px;
>>>p{
width:100%;
img{
width:100%;
height:auto;
}
}
}
触发子组件事件
父组件引用子组件(弹框)
<share-dialog ref="ShareDialog"/>
父组件方法(打开弹框)
openDialog() {
this.$refs['ShareDialog'].show()
}
子组件
<template>
<div>
<el-dialog
:visible.sync="dialogVisible"
width="100%">
<img src="../../../static/img/share@2x.png" alt="img">
</el-dialog>
</div>
</template>
<script>
export default {
name: 'ShareDialog',
data() {
return {
dialogVisible: false
}
},
methods: {
show() {
this.dialogVisible = true
}
}
}
</script>
<style lang="scss" scoped>
// 修改弹框样式
/deep/ .el-dialog{
background: transparent;
box-shadow: 0 0 0 transparent;
margin-top:0 !important;
img{
width:40vw;
height:auto;
float:right;
}
}
</style>
获取视频宽高
// 判断视频横竖
// 直接取videoWidth videoHeight 是 0
// canplay 事件,视频达到可以播放时触发;
// videoWidth 和 videoHeight 属性为视频真实宽高,这两个属性为只读属性,赋值不会生效;
// width 和 height 属性为视频在页面上显示的尺寸,可以在元素设置或JS赋值;
// width 和 height 属性优先级低于样式,同时使用样式和属性设置宽高,最后生效的是样式;//待测试
var video = document.querySelector('video')
const that = this// vue
video.addEventListener('canplay', function() {
var w = video.videoWidth
var h = video.videoHeight
if (h - w > 0) {
// 竖版
that.isHeightVideo = true
} else {
// 横版
that.isHeightVideo = false
}
})
// 判断视频横竖
// undo 视频宽高为啥是固定的 150 300
var h = window.getComputedStyle(this.$refs.video).height
var w = window.getComputedStyle(this.$refs.video).width
console.log(h.replace(/\px/g, ''))// 150px
console.log(w.replace(/\px/g, ''))// 300px
if (h.replace(/\px/g, '') - w.replace(/\px/g, '') > 0) {
// 竖版
this.isHeightVideo = true
} else {
// 横版
this.isHeightVideo = false
}
深度 watch
应用举例:子组件图表,监听父组件相关图表数据的变化,及时重绘图表。
props: {
chartData: {
type: Object,
required: true
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
}
props
props 没有定义默认值 default 返回 undefined 。
props 属性值为对象或数组时,应该使用工厂函数的形式。
const compB = {
template: `<div>
<p>age:{{age}}</p>
<p>sex:{{sex}}</p>
<p>hobby.ball:{{hobby.ball}}</p>
<p>hobby.game:{{hobby.game}}</p>
</div>`,
props: {
age: Number,
sex: {
type: String,
default: 'male',
validator(value) {
return value === 'male' || value === 'female'
}
},
hobby: {
type: Object,
default() {
return {
ball: '',
game: ''
}
}
}
}
}