# Vue高级特性

# 一、scoped实现原理

在vue文件中的style标签上,有一个特殊的属性:scoped。当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化。

vue中的scoped属性的效果主要通过PostCSS转译实现,如下是转译前的vue代码:

<template>
 <div class="example">hi</div>
</template>
<style scoped>
.example {
 color: red;
}
</style>
1
2
3
4
5
6
7
8

转义后:

<template>
 <div class="example" data-v-5558831a>hi</div>
</template>
<style>
.example[data-v-5558831a] {
 color: red;
}
</style>
1
2
3
4
5
6
7
8

即:PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom。

# 二、自定义v-model

在input元素中使用v-model实现双向数据绑定,其实就是在输入的时候触发元素的input事件,通过这个语法糖,也能够实现父子组件数据的双向绑定。

组件上的 v-model 默认使用名为 value 的 prop 和名为 input 的事件。

组件的input事件通过组件内部输入框的input事件提交$emit,手动触发。

所以为了保持组件内input的value和input跟组件的value和input一致,组件内的<input>必须:

  • value 使用作为prop的value
  • input 绑定到组件的input事件

父组件

<template>
  <div id="app">
    <Child v-model="number"></Child>
    <p>{{number}}</p>
  </div>
</template>

<script>
import Child from "./components/Child.vue";

export default {
  name: "App",
  data() {
    return {
      number: 0
    };
  },
  components: {
    Child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

子组件,Child.vue

<template>
  <div>
    <button @click="$emit('input',value+1)">点击</button>
  </div>
</template>

<script>
export default {
  name: "Child",
  props: ["value"]
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12

通过v-bind把父组件的数据绑定到了子组件的props属性中,而在props上默认用value取值,然后通过$emit触发事件input,因为v-model绑定的事件是input,故在子组件上触发了父组件的input事件,通过触发事件来进行传值,实现了父子组件数据的双向绑定。

# 使用model属性

在子组件里定义model属性,设置prop和event。

父组件

<template>
  <div id="app">
    <Child v-model="number"></Child>
    <p>{{number}}</p>
  </div>
</template>

<script>
import Child from "./components/Child.vue";

export default {
  name: "App",
  data() {
    return {
      number: 1
    };
  },
  components: {
    Child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

子组件

<template>
  <input type="text" :value="value" @input="$emit('change',$event.target.value)" />
</template>

<script>
export default {
  name: "Child",
  model: {
    prop: "value", // 定义父组件传过来的prop名称
    event: "change" // 定义子组件向父组件派发的事件名称
  },
  props: {
    value: String // 和上面model里的prop名称一致
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 三、Vue自定义过滤器、自定义指令

# 自定义过滤器

在插值表达式中,如果需要对变量的格式进行处理,可以使用过滤器,过滤器的使用方法如下:

{ { ctime|dateFormat } },dateFormat即为过滤器函数,它的第一个参数为ctime的值,传入的是ctime的值,返回一个新的值。过滤器可定义为全局过滤器和局部过滤器,全局过滤器为所有vue实例所共享,局部过滤器为当前实例所私用。

Vue.filter('dataFormat',function(dateStr,pattern=""){
	var dt=new Date(dateStr);
	var y=dt.getFullYear();
	var m=dt.getMonth()+1;
	var d=dt.getDate();
	// return y+'-'+m+'-'+d;
	if (pattern.toLowerCase() === 'yyyy-mm-dd'){
		return `${y}-${m}-${d}`;
	}else{
		var hh=dt.getHours();
		var mm=dt.getMinutes();
		var ss=dt.getSeconds();
		return `${y}-${m}-${d}-${hh}:${mm}:${ss}`;
	}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 自定义指令

使用Vue.directive()定义全局指令。

参数1:指令的名称,注意,在定义的时候,指令的名称前面不需要加 v- 前缀,但是调用的时候必须加v-前缀。

参数2:是一个对象,这个对象身上有一些指令相关的函数,这些函数可以在特定的阶段执行相关的函数操作。

Vue.directive('focus', {
    bind: function (el) { 
    // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
    // 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
    // 和样式相关的操作,一般都放在bind函数        
    },
    inserted: function (el) {  
    // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
       el.focus()
    // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
    },
    updated: function (el) {  
    // 当VNode更新的时候,会执行 updated, 可能会触发多次
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 四、$nextTick

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

# 五、slot插槽

slot(插槽),简言之就是在组件添加一个占位的空间,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动替换组件模板中。

# 默认插槽

父组件

<template>
  <div class="first-page">
    <div class="first-content">
      我来自父组件
      <child>
        <span style="color: red;">我将替换子组件插槽</span>
        <!-- 单个插槽,替换子组件匿名插槽 -->
      </child>
    </div>
  </div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";

export default {
  name: "parent",
  components: {
    // 用来声明页面组件
    child
  }
};
</script>
<style media="screen">
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

子组件

<template>
  <div class="sidebar">
    <h1>默认插槽</h1>
    <div class="slot">
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default { };
</script>
1
2
3
4
5
6
7
8
9
10
11
12

# 具名插槽

父组件,通过设置slot属性,即slot="name_slot"的方式指定要替换哪个插槽。在2.6版本后,使用v-slot:name_slot指令的方式指定要替换哪个插槽。

# 2.6版本以下

父组件

<template>
  <div class="first-page">
    <div class="first-content">
      我来自父组件
      <child>
        <span slot="name_slot">我将替换子组件slot name="name_slot"的插槽</span>
      </child>
    </div>
  </div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";

export default {
  name: "parent",
  components: {
    // 用来声明页面组件
    child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2.6版本

父组件,具名插槽可以缩写,例如 v-slot:name_slot 可以被重写为 #name_slot

<template>
  <div class="first-page">
    <div class="first-content">
      我来自父组件
      <child>
        <template v-slot:name_slot>
          <span>我将替换子组件slot name="name_slot"的插槽</span>
        </template>
      </child>
    </div>
  </div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";

export default {
  name: "parent",
  components: {
    // 用来声明页面组件
    child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

子组件,通过slot标签设置name属性来给自己取名字<slot name="name_slot"></slot>

<template>
  <div class="sidebar">
    <h2>具名插槽</h2>
    <div class="nameSlot">
      <slot name="name_slot"></slot>
    </div>
  </div>
</template>

<script>
export default { };
</script>
1
2
3
4
5
6
7
8
9
10
11
12

# 作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。

子组件

<template>
  <span>
    <!-- 为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素
    的一个 attribute 绑定上去,绑定在 <slot> 元素上的 attribute 被称为插槽 prop -->
    <slot :user="user">{{ user.lastName }}</slot>
  </span>
</template>

<script>
export default {
  data() {
    return {
      user: {
        lastName: "zls",
        firstName: "baa"
      }
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

父组件

<template>
  <div class="first-page">
     <!-- 使用 v-slot 来定义包含所有插槽 prop 的对象命名为 slotProps,
    但你也可以使用任意你喜欢的名字。default表示默认插槽-->
    <child>
      <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template>
      <!-- 解构插槽的写法 -->
      <template v-slot:default="{user}">{{ user.firstName }}</template>
    </child>
  </div>
</template>
<script type="text/javascript">
import child from "./components/Child.vue";

export default {
  name: "parent",
  components: {
    // 用来声明页面组件
    child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<child v-slot="{user}">
  {{ user.firstName }}
</child>
1
2
3

# 六、动态组件、异步组件

# 动态组件

使用is来切换不同的组件,适合需要根据数据动态渲染的场景。 <component v-bind:is="currentTabComponent"></component>。 currentTabComponent是data里的数据可以动态改变从而切换不同的组件,显示不同的组价内容。

<template>
  <div class="first-page">
    <component :is="comName"></component>
  </div>
</template>
<script>
import Child from "./components/Child";
export default {
  data() {
    return {
      comName: "Child"
    };
  },
  components: {
    Child
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。

  • import 函数
  • 按需加载,异步加载大组件
<template>
  <div class="first-page">
    <Child v-if="isShow"></Child>
    <button @click="isShow=true">点击加载子组件</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isShow: false
    };
  },
  components: {
    Child: () => import("./components/Child")
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 七、keep-alive缓存组件

使用keep-alive缓存组件的时候不会被销毁。

  • 缓存组件
  • 频繁切换,不需要重复渲染
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
1
2
3

# 八、mixin使用

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

export default {
  mixins:[myMixin],
  data() {
    return {
      isShow: false
    };
  },
  components: {
    Child: () => import("./components/Child")
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

mixin的问题:

  • 变量来源不明确,不利于阅读
  • 多mixin可能会造成命名冲突
  • mixin和组件可能出现多对多的关系,复杂度较高
Last Updated: 7/20/2020, 3:51:57 PM