Vue实战进阶

最开始学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 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如: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')

总结:正常写不香吗

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/[email protected]" 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

}

猜你喜欢

转载自blog.csdn.net/Irene1991/article/details/110189209