Vue高级部分详解

325 阅读5分钟

把vue里面的一些不是太为人众知的知识点罗列出来并进行讲解提过了解决问题的多种思路

自定义v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件

上面说 v-model 只能接受 value 属性和响应 input 事件,其实这种说法并不是绝对的,我们可以通过 model 选项来改变这种情况。

model 接受有两个属性:

  • props 代替原来 value 的值。
  • event 代替原本 input 出发的事件。
Vue.component('base-checkbox', {
  model: {
	// 要与props对应
    prop: 'checked',
	// 要与$emit触发的事件对应
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 ``触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。

**注意:**你仍然需要在组件的 props 选项里声明 checked 这个 prop。

$nextTick

前置知识

  • Vue是异步渲染
  • data改变之后,DOM不会立即渲染
  • $nextTick会在DOM渲染之后被触发,以获取最新DOM节点

举例

<template>
  <div id="app">
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {{item}}
        </li>
    </ul>
    <button @click="addItem">添加一项</button>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
      return {
        list: ['a', 'b', 'c']
      }
  },
  methods: {
    addItem() {
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)
        this.list.push(`${Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
        // 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
        this.$nextTick(() => {
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          // eslint-disable-next-line
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
</script>


slot

插槽基本用法

不传递组件或内容时显示默认内容

// 插槽组件SlotDemo
<template>
    <a :href="url">
        <slot>
            默认内容,即父组件没设置内容时,这里显示
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {}
    }
}
</script>


// 父组件使用插槽
<SlotDemo :url="website.url">
    {{website.title}}
</SlotDemo>

作用域插槽

想使用插槽组件自己内部的data时,需要在父组件内部使用v-slot指令给插槽取名,然后通过**{{slotProps.slotData.title}}**获取

注意:需要先向传递数据 :slotData="website"

// 插槽组件ScopedSlotDemo
<template>
    <a :href="url">
        <slot :slotData="website">
            {{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
        </slot>
    </a>
</template>

<script>
export default {
    props: ['url'],
    data() {
        return {
            website: {
                url: 'http://wangEditor.com/',
                title: 'wangEditor',
                subTitle: '轻量级富文本编辑器'
            }
        }
    }
}
</script>


// 父组件使用插槽
<ScopedSlotDemo :url="website.url">
    <template v-slot="slotProps">
        {{slotProps.slotData.title}}
    </template>
</ScopedSlotDemo>

具名插槽

slot上定义name值, 父组件使用插槽时在template v-slot:name对应插槽的名字

// 插槽组件deom
<template>
	<header>
        <slot name="header">命名插槽</slot>
    </header>
	<slot>匿名插槽</slot>
</template>

// 父组件使用插槽
<deom>
    <template v-slot:header>
        <h1>
            命中header的slot中
        </h1>
    </template>
    <p>
        命中匿名插槽
    </p>
</deom>   

动态组件

  • 动态组件就是几个组件放在一个挂载点下,然后根据父组件的某个变量来决定显示哪个,或者都不显示。
  • 在挂载点使用 component 标签,然后使用 :is =“组件名”,它会自动去找匹配的组件名,如果有,则显示;
  • is的属性值应该为在父组件中注册过的组件的名称

用法如下:

<!-- 动态组件 -->
<!-- is的属性值应该为在父组件中注册过的组件的名称 -->
<component :is="NextTickName"/>

<script>
import NextTick from './NextTick'

export default {
    components: {
        NextTick
    },
    data() {
        return {
            NextTickName: "NextTick",
        }
    }
}

如果给 is 属性绑定动态值,那么就可以实现组件的动态切换,例子如下:

<!-- html 部分-->
<div id="app">
    <button v-for="item in tabs" @click="change = item.id">
    	{{ item.text }}
    </button>
    <component :is="change"></component>
</div>
// js 部分
<script>
    new Vue({
      el: '#app',
      data: {
        change: 'one' // 默认显示组件 one
        tabs: [
            {id: 'one', text: '线路一'},
            {id: 'two', text: '线路二'},
            {id: 'thr', text: '线路三'}
        ]
      },
      components: {
        one: {template: '<div>我是线路一</div>'},
        two: {template: '<div>我是线路二</div>'},
        thr: {template: '<div>我是线路三</div>'}
      }
    })
</script>

异步组件

异步组件就是定义的时候什么都不做,只在组件需要渲染(组件第一次显示)的时候进行加载渲染并缓存,缓存是以备下次访问。

Vue实现按需加载,官方推荐使用结合webpack的代码分割功能进行。定义为异步加载的组件,在打包的时候,会打包成单独的js文件存储在static/js文件夹里面,在调用时使用ajax请求回来插入到html中。

使用方法如下(推荐使用,但是webpack > 2.4)

const FormDemo = () => import('../BaseUse/FormDemo')

此时就会在第一次需要渲染FormDemo组件时才会加载组件

缓存组件

使用场景

  • 频繁切换,不需要重新渲染
  • 常做vue性能优化keep-alive

有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就可以用到keep-alive组件。

keep-alive的生命周期

  • 初次进入时:created > mounted > activated;退出后触发 deactivated
  • 再次进入:会触发 activated;事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中

代码演示

<template>
    <div>
        <button @click="changeState('A')">A</button>
        <button @click="changeState('B')">B</button>
        <button @click="changeState('C')">C</button>

        <keep-alive> <!-- tab 切换 -->
            <KeepAliveStageA v-if="state === 'A'"/> <!-- v-show -->
            <KeepAliveStageB v-if="state === 'B'"/>
            <KeepAliveStageC v-if="state === 'C'"/>
        </keep-alive>
    </div>
</template>

<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'

export default {
    components: {
        KeepAliveStageA,
        KeepAliveStageB,
        KeepAliveStageC
    },
    data() {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state) {
            this.state = state
        }
    }
}
</script>

mixin抽离公共逻辑

简单介绍

Mixin允许你封装一块在应用的其他组件中都可以使用的函数

作用:多个组件可以共享数据和方法,在使用mixin的组件中引入后,mixin中的方法和属性也就并入到该组件中,可以直接使用。钩子函数会两个都被调用,mixin中的钩子首先执行。

可能存在的缺点:

  1. 变量来源不明确
  2. 多mixin可能会造成命名冲突
  3. mixin可能会出现多对多的关系, 复杂度高

代码示例

MixinDemo.vue

<template>
    <div>
        <p>{{name}} {{major}} {{city}}</p>
        <button @click="showName">显示姓名</button>
    </div>
</template>

<script>
import myMixin from './mixin'

export default {
    mixins: [myMixin], // 可以添加多个,会自动合并起来
    data() {
        return {
            name: '双越',
            major: 'web 前端'
        }
    },
    methods: {
    },
    mounted() {
        // eslint-disable-next-line
        console.log('component mounted', this.name)
    }
}
</script>

mixin.js

export default {
    data() {
        return {
            city: '北京'
        }
    },
    methods: {
        showName() {
            // eslint-disable-next-line
            console.log(this.name)
        }
    },
    mounted() {
        // eslint-disable-next-line
        console.log('mixin mounted', this.name)
    }
}