Vue学习笔记

Vue的学习

1、vue基础介绍

1.1 Vue实例

1.1.1 创建Vue实例

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的。 — 官方定义

  <div id="app">
      {{message}}
    </div>
const vm = new Vue({
    el:'#app',
    //选项
})

Vue 实例和 Vue 应用是什么关系呢?
官方介绍:一个 Vue 应用由一个通过new Vue()创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所以 Vue 实例是属于 Vue 应用的一部分,与组件树组成了 Vue 应用:

// 一个Vue应用,由根实例+组件树组成
根实例
└─ 根组件 // 此行开始,为组件树
   ├─ 组件1
   │  ├─ 组件1-1
   │  └─ 组件1-2
   └─ 组件2
      ├─ 组件2-1
      └─ 组件2-2

1.1.2 Vue实例常用选项说明

选项名说明类型
el通过CSS选择器或者HTMLElement实例的方式,提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标string/Element
template字符串模板,将会替换挂载的元素string
render字符串模板的代替方案,该渲染函数接收一个createElement方法作为第一个参数来创建VNode(createElement: () => VNode) => VNode
dataVue实例的数据对象,用于数据绑定Object / Function。组件只支持Function
props用于接收来自父组件的数据Array<string>/Object
methodsVue实例的事件,可用于事件绑定{ [key: string]: Function }
computed计算属性,用于简化模板的复杂数据计算{ [key: string]: Function or { get: Function, set: Function } }
watch观察 Vue 实例变化的一个表达式或计算属性函数{ [key: string]: string or Function or Object or Array }
directives自定义指令Object
filters过滤器Object
components组件Object

1.1.3 DOM 相关选项

el属性

Vue 实例中,el选项提供一个页面中已存在的 DOM 元素作为实例的挂载目标。挂载的意思是,在选中的该元素所在的位置进行页面渲染,该元素会被替换成需要渲染的页面内容。我们可以传入一个 CSS 选择器,也可以传入一个 DOM 元素。例如,页面中有一个 id 为#app 的元素,如果我们希望以<div id="app"></div>该元素作为 Vue 实例的挂载目标,以下方式都是可以的:

new Vue({
  // 1. 传入 Element 元素方式一
  el: document.getElementById("app"),

  // 2. 传入 Element 元素方式二
  el: document.getElementsByTagName("div")[0],

  // 3. 传入 CSS 选择器方式一
  el: "#app",

  // 4. 传入 CSS 选择器方式二
  // 最好选择唯一的元素,不推荐该方式
  el: "div"
});

所谓挂载元素,在实例挂载之后,元素可以用vm.$el访问。需要在mounted之后才能获取到:

new Vue({
  el: "#app",
  template: "<div>{{ message }}</div>",
  data() {
    return {
      message: "欢迎来到Vue的世界"
    };
  },
  mounted() {
    console.log(this.$el);
  }
});

如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用vm.$mount()手动开启编译,也就是这样:

const vm = new Vue({
  template: "<div>{{ message }}</div>",
  data() {
    return {
      message: "欢迎来到Vue的世界"
    };
  },
  mounted() {
    console.log(this.$el);
  }
});

// 需要的时候使用
vm.$mount("#app");
template

给 Vue 实例提供字符串模板,该模板将会替换挂载的元素。

<body>
  <div id="app"></div>
  <script>
    new Vue({
      el: "#app",
      template: "<p>{{ message }}</p>",
      data() {
        return {
          message: "欢迎来到Vue的世界"
        };
      },
      beforeMount() {
        console.log("beforeMount", this.$el);
      },
      mounted: function() {
        console.log("mounted", this.$el);
      }
    });
  </script>
</body>

这里挂载的元素指的是<div id="app"></div>,当我们使用了 template 选项之后,我们在页面中可以看到最终页面中的内容是 template 中的内容,此时<div id="app"></div>已经被替换成 template 中的<p></p>,并将 message 中的内容替换成绑定的数据了:
在这里插入图片描述
我们也能看到,在beforeMount生命周期中,vm.$el获取的是挂载的元素模板,而在mounted生命周期后则变成了 template 中的真实 DOM 元素:
在这里插入图片描述
如果 Vue 选项中包含 render 渲染函数,则 template 将被忽略,我们来看看渲染函数。

render

字符串模板 template 的代替方案,该渲染函数接收一个createElement方法作为第一个参数用来创建 VNode。
Vue 里使用了虚拟 DOM,而createElement创建的便是虚拟 DOM,在 Vue 里称为 VNode。

new Vue({
  // 该段实现:
  // <p v-if="condition">condition work!</p>
  // <p v-else>condition not work!</p>
  render: function (createElement) {
    if (this.condition) {
      return createElement('p', "condition work!")
    } else {
      return createElement('p', 'condition not work!')
    }
  }
  // 该段实现:
  // <ul><li v-for="item in items">{{item}}</li></ul>
  render: function (createElement) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item)
    }))
  }
});

1.2 生命周期

Vue生命周期大体上可以分为三个阶段:实例化阶段、运行阶段和销魂阶段。
实例化阶段包括beforeCreatecreatedbeforeMountmounted;
运行阶段包括beforeUpdateupdated
销毁阶段包括beforeDestroydestroyed

 // 各个生命周期函数通过调用下面这个函数了解其所处的生命阶段
       function show(inf,obj){
          console.log(inf);
          console.log("------------------------------------------");
          console.log('获取vue实例data里的数据:');
          console.log(obj.information);
          console.log("------------------------------------------");
          console.log('挂载的对象,就是DOM:');
          console.log(obj.$el);
          console.log("------------------------------------------");
          console.log('页面上已经挂载的DOM:');
          console.log(document.getElementById('app').innerHTML);
       }

1.2.1 实例化阶段

beforeCreate

beforeCreate是实例化阶段的第一个生命周期函数,当执行到这个函数的时候,当前的实例上,只是初始化了几个主要的生命周期函数而已,但是此时data和method还没有初始化,此时还没有数据和真实DOM。

代码

  var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        beforeCreate(){
            show('在实例初始化之后、进行数据侦听和事件/侦听器的配置之前同步调用。',this)
        },
   }

显示结果
在这里插入图片描述

created

当执行到created生命周期函数,就表示当前实例的data和methods已经初始化好了;如果页面想要获取自己的首屏数据,最好在这个方法中进行。

代码:

var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        created(){
            show("在实例创建完成后被立即同步调用。",this)
        },
}

显示结果
在这里插入图片描述

beforeMount

当执行到这个beforeMount,就进入到了创建阶段的第三个生命周期函数;此时,将要把内存中的HTML结构渲染到页面上,但是并没有真正开始渲染,此时页面中的{{}}里面的变量还没有被替换。此时this.$el已经有值。

代码:

    var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        beforeMount(){
            show('在挂载开始之前被调用:相关的 render 函数首次被调用。',this)
        },
     }

显示结果:
在这里插入图片描述

mounted

当执行到mounted这个阶段,渲染操作已经完成。
注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等待整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick:

mounted() {
  this.$nextTick(function () {
    // 仅在整个视图都被渲染之后才会运行的代码
  })
}

代码:

  var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        mounted(){
            show('在实例挂载完成后被调用',this)
        },
  }

显示结果:
在这里插入图片描述

1.2.2 运行阶段

beforeUpdate

当执行到beforeUpdate这个生命周期函数,就表示在data里面的数据已经发生变化,但是DOM元素还没有更新。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
请不要在此函数中更改状态,否则会触发死循环。

代码

    var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        mounted(){
            this.message = 'HelloWorld'
        },
        beforeUpdate(){
            show('在数据发生改变后,DOM 被更新之前被调用',this)
        },
    }

显示结果
在这里插入图片描述

updated

在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或侦听器取而代之。

注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等待整个视图都渲染完毕,可以在 updated 内部使用 vm.$nextTick

updated() {
  this.$nextTick(function () {
    // 仅在整个视图都被重新渲染完毕之后才会运行的代码
  })
}

代码:

 var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        mounted(){
            this.message = 'HelloWorld'
        },
        updated(){
            show('在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。',this)
        },
   }

显示结果:
在这里插入图片描述

1.2.3 销毁阶段

beforeDestroy

实例销毁之前调用。在这一步,实例仍然完全可用。

代码:

 var vm = new Vue({
        el:'#app',
        data:{
            message:'Vue的生命周期顺序',
        },
        mounted(){
            this.$destroy()
        },
        beforeDestroy(){
            show('组件被销毁之前',this)
        },
 }

显示结果:
在这里插入图片描述

destroyed

实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。

1.2.4 其他不常用的生命周期

activated

被 keep-alive 缓存的组件激活时调用。

deactivated

被 keep-alive 缓存的组件失活时调用。

errorCaptured

在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

1.2.5 生命周期图示

在这里插入图片描述

1.3 常用指令

1.3.1 数据绑定

插值语法{{}}

文本插值,可配合过 Javascript 表达式和过滤器使用。
快速刷新页面会有闪动的问题。

 <div id="app">
        <div>{{message}}</div>
        <div >{{flag === 1 ? message : '我是一个空的'}}</div>
    </div>
  var vm = new Vue({
        el:'#app',
        data:{
            flag:0,
            message:'我是一个属性的值'
        },
    })
v-text

v-text是元素的innerText属性,它的作用和前面的{{}}是一样的,用于数据绑定。但是v-text没有闪动的问题。

  <div id="app">
        <div v-text='message'></div>
        <div v-text='flag === 1 ? message : ""'></div>
        <div v-text='"我是一个字串"'></div>
    </div>
   var vm = new Vue({
        el:'#app',
        data:{
        	flag:1,
            message:'我是data属性里面的定义的值',
        },
    })

我们使用了 v-text 指令,会将传入的值动态插入 <div> 标签内。

v-html

v-html是元素的 innerHTML,它用于绑定一段 html 标签:

在单文件组件里,scoped 的样式不会应用在 v-html 内部因为那部分 HTML 没有被 Vue 的模板编译器处理。 如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局 <style> 元素手动设置类似 BEM 的作用域策略。

 <div id="app">
        <div v-html='html'></div>
        <div v-html='flag === 1 ? html : ""'></div>
        <div v-html='"<h1>我是一个字串</h1>"'></div>
    </div>
  var vm = new Vue({
        el:'#app',
        data:{
        	flag:1,
            html:'<h1>我是data属性里面的定义的值<h1>',
        },
    })

我们使用了 v-html 指令,会将 html 元素插入 <div> 标签内。

v-pre

该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如:

<div id="app">
      <div v-pre>{{message}}</div>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        message:'HelloWorld!'
    },
})

显示结果:{{message}}

v-once

一次性插值,数据改变时插值处的内容不会更新。

<div id="app">
        <div v-once>{{message}}</div>
    </div>
  var vm = new Vue({
        el:'#app',
        data:{
            flag:1,
            message:'我是一个属性的值'
        },
        mounted(){
            this.message = "HelloWorld"
        }
    })

1.3.2 属性绑定 v-bind

v-bind用于给元素的属性赋值。v-bind后面是 :属性名=[变量名]。例如:v-bind:title="message"

 <div id="app">
      <input type="text" v-bind:value="message">
    </div>
const vm = new Vue({
    el:'#app',
    data: {message:'HelloWorld!'},
})

v-bind指令给input标签的value属性赋值。
vue 还提供了指令 v-bind 的简写方式,可以直接通过:属性名的方式比如:<input type="text" v-:value="message">

1.3.3 事件绑定 v-on

vue 中提供了指令 v-on 来进行事件的绑定。用法:v-on:事件名="方法",例如:v-on:click=“alert”

<div id="app">
    <button v-on:click="hello">hello</button>
  </div>
const vm = new Vue({
  el: '#app',
  data: {},
  methods: {
    hello() {
    	alert('hello')
    }
  }
})

vue同样给v-on提供了简写方式,只需要通过@事件类型的方式就可以了。例如:@click="hello"

v-on事件函数中传入参数

  • 如果事件直接绑定函数名称,那么默认会传递事件对象event作为事件函数的第一个参数。
 <button v-on:click='handle'>改变内容</button>

如果想要使用传递的事件对象event就需要用参数接收一下。

const vm = new Vue({
    el:'#app',
    data: {},
    methods:{
        handle:function(event){
            console.log(event.target.innerHTML)
        }
    },
})
  • 如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,并且事件对象的名称必须是$event
<div id="app">
      <button v-on:click='handle1("lichangan",18,$event)'>改变内容1</button>
      <button v-on:click='handle2("lichangan",18)'>改变内容2</button>
    </div>
const vm = new Vue({
    el:'#app',
    data: {},
    methods:{
        handle1:function(name,age,event){
            console.log(event.target.innerHTML)
        },
        handle2:function(name,age){
            console.log(event.target.innerHTML)
        }
    },
})
input相关事件
  • @input或者v-on:input:实时监测用户的输入,每次输入都会调用.
  • @keyup.enter: 在pc上需要点击回车键触发,而在手机上则是需要点击输入键盘上的确定键才可触发。
  • @change:在手机上都是要经过触发虚拟键盘的搜索键才会触发事件。
    注:在ios手机上会出现问题:
    如果要的效果是输入值不用虚拟键盘触发方法就调查询接口进行查询,这时在安卓手机上没有问题,但是在ios手机上会出现多次触发的情况。
    简单的解决办法:
    对input的值进行监听(watch),把原本需要绑在input框的事件在监听变化时调用。
  • @blur(失焦):要满足输入框在输入完成、移到其他地方时进行验证时,需要用到该事件,用此事件进行绑定验证方法即可。
事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。实现方法就是在事件名称后面以后缀的形式添加指定的修饰符。

知识扩展:

  • event.preventDefault() 用来取消事件的默认动作。
  • event.stopPropagation() 用来阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。

Vue 提供了以下事件修饰符:

1、.stop: 阻止单击事件继续传播;

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

2、.prevent: 提交事件不再重载页面;

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></for

3、.capture: 添加事件监听器时使用事件捕获模式,即元素自身触发的事件先在自身处理,然后交由内部元素进行处理;

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

4、.self: 只有在event.target是当前元素自身时触发处理函数,即事件不是从内部元素触发的;

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

5、.once: 点击事件将只触发一次;
不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

6、.passive: 滚动事件会立即触发,不会等待其他串联事件。即prevent会失效。

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能。

不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key` 是 `Enter` 时调用 `login()` -->
<input v-on:keyup.enter="login">

<!-- 也可以使用 keyCode -->
<input v-on:keyup.13="login">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • 1、.enter: 回车键;
  • 2、.tab: TAB键;
  • 3、.delete: 删除和退格键;
  • 4、.esc: 只有在event.终止键;
  • 5、.space: 删除键;
  • 6、.up: 上方向键:
  • 7、.down: 下方向键:
  • 8、.left: 左方向键:
  • 9、.right: 右方向键:
系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • 1、.ctrl:
  • 2、.alt:
  • 3、.shift:
  • 4、.meta:

注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。

<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

Vue提供了.exact修饰符,实现单独的系统按键的事件。

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
自定义按键修饰符别名

在Vue中可以通过config.keyCodes自定义按键修饰符别名

<div id="app">
    预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法
    <input type="text" v-on:keydown.f5="prompt()">
</div>

<script>
    
    Vue.config.keyCodes.f5 = 116;

    let app = new Vue({
        el: '#app',
        methods: {
            prompt: function() {
                alert('我是 F5!');
            }
        }
    });
</script>

1.3.4 双向数据绑定v-model

在原生 Javascript 的项目中,要获取用户输入框输入的内容,需要通过DOM对象的方式。在Vue项目中则不用这么繁琐,因为vue通过了指令v-model来实现数据的双向绑定。

用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model 本质上负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。— 官方定义

单行文本input
 <div id="app">
      <div v-text='message'></div>
      <input type="text" v-model='message'>
      <button v-on:click='change'>改变内容</button>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        message:'HelloWorld!'
    },
    methods:{
        change:function(){
            this.message = "你好,中国"
        }
    },
})

我们通过给input元素绑定指令v-model, 使得输入框中的数据和message实现双向绑定。当用户输入内容时message会跟着一起改变,当通过函数修改message的值时,input输入框中的值也跟着一起变化。

多行文本textarea
 <div id="app">
    <textarea v-model="desc" placeholder="请输入商品描述"></textarea>
    <p>描述是: {{ desc }}</p>
  </div>
 var vm = new Vue({
	el: '#app',
  data: {
  	desc: ''
  },
})

我们通过 v-model 给输入框 textarea 和 desc 形成双向绑定,当 textarea 中数据发生改变时 desc 也会发生改变。同理,我们在控制台通过 vm.desc = "" 给 desc 赋值时输入框的内容也会发生改变。

单个复选框
<div>
       <input type="radio" id="isLike" v-model='flag' value="1" ><label for="isLike">是否喜欢</label>
   </div>
var vm = new Vue({
	el: '#app',
  data(){
        return {
            flag : 0
        }
    }
})

value的值为1,所以当flag为1是会选中,当flag为0时为不选中。

多个复选框
      <input type="checkbox" id="food" value="食品" v-model="types">
        <label for="food">食品</label>
        <input type="checkbox" id="book" value="图书" v-model="types">
        <label for="book">图书</label>
        <input type="checkbox" id="clothes" value="衣服" v-model="types">
        <label for="clothes">衣服</label>
        <br>
        <span>类型: {{ types }}</span>
const vm = new Vue({
	el: '#app',
  data: {
  	types: ["食品"]
  },
})

我们通过 v-model 给多个选择框 checkbox 和 types 形成双向绑定,当任意 checkbox 改变选中状态时 types 也会发生改变。同理,我们在控制台通过 vm.types = [] 给 types 赋值时对应 checkbox 的选中状态也会发生改变。

单选按钮
 <input type="radio" id="yes" value="1" v-model="isFree">
      <label for="one"></label>
      <input type="radio" id="no" value="0" v-model="isFree">
      <label for="no"></label>
      <br>
      <span>选择: {{ isFree }}</span>
var vm = new Vue({
	el: '#app',
  data: {
  	isFree: 1
  },
})

当isFree为1的时候,value值是1的被选中。当isFree为0的时候,value值是0的被选中。

下拉选择框
<div>
      <select v-model="company">
        <option value="">请选择</option>
        <option>顺丰</option>
        <option>中通</option>
        <option>圆通</option>
      </select>
      <span>选项: {{ company }}</span>
    </div>
var vm = new Vue({
	el: '#app',
  data: {
  	company: '顺丰'
  },
})

我们通过 v-model 给选择框 select 和 company 形成双向绑定,当 select 改变选项时 company 也会发生改变。同理,我们在控制台通过 vm.company = 0 给 company 赋值时 select 的选中项也会发生改变。

修饰符- .lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
修饰符 - .number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">
修饰符- .trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

1.3.5 v-for

v-for 用于列表的循环渲染。

数组的循环渲染

当我们对数组进行循环的时候,item 表示数组中具体的某一项:

<div id="app">
      <ul>
          <li :key='item.id'v-for="(item,index) in items">{{index+1}}号选手:{{item.name}}</li>
      </ul>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        items:[
            {name:'lichangan',id:1},
            {name:'huge',id:2}
        ]
    },
    methods:{
    },
    components:{}
})

我们使用 v-for 对数组进行遍历,循环输出<li></li>,并在每次循环的时候将 name 插入到 <li> 标签内。
在这里插入图片描述

对象的循环渲染

根据属性的个数来渲染,把属性渲染到对一个的标签内部。

<  <div id="app">
      <ul>
          <li :key='index' v-for="(value,key,index) in item">{{value,key,index}}</li>
      </ul>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        item:{name:'lichangan','age':18,'sex':"male"}
    },
    methods:{
    },
    components:{}
})

我们使用 v-for 对数组进行遍历,循环输出<li></li>,并在每次循环的时候将属性的值插入到 <li> 标签内。
效果:
在这里插入图片描述

  • 不推荐同时使用 v-if 和 v-for
  • 当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级
key的作用

帮助Vue区分不同的元素,从而提高性能

<li :key='item.id' v-for='(item,index) in list'>{{item}} + '---' {{index}}</li>
  • key 的作用
    (1)key来给每个节点做一个唯一标识
    (2)key的作用主要是为了高效的更新虚拟DOM

1.3.6 v-if和v-show

vue提供了v-ifv-show两个指令来控制页面元素的显示和隐藏。

 <div id="app">
      <div v-if='vfflag'>v-if</div>
      <div v-show='vfshow'>v-show</div>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        vfflag:false,
        vfshow:false
    },
    methods:{
    },
    components:{}
})

v-show实现隐藏的原理是通过display:none;来控制的,v-if是动态的向DOM树内添加或者删除DOM元素来实现显示和隐藏的

  • v-showv-if的区别

(1)v-show本质就是标签display设置为none,控制隐藏
(2)v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
(3)v-if是动态的向DOM树内添加或者删除DOM元素
(4)v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

如果频繁的让一个元素显示和隐藏,推荐用v-show。如果元素渲染出来之后基本上变化比较少,推荐用v-if

1.3.7 v-if、v-else-if、v-else

事实上,v-if的条件渲染和JavaScript条件判断语句中的if、else、else if非常类似。

  <div id="app">
      <div v-if='type==="A"'>优秀</div>
      <div v-else-if='type==="B"'>良好</div>
      <div v-else-if='type==="C"'>一般</div>
      <div v-else></div>
    </div>
const vm = new Vue({
    el:'#app',
    data: {
        type:'C'
    },
    methods:{
    },
    components:{}
})

1.3.8 v-cloak

插值表达式存在“闪动的问题”,需要使用v-cloak指令来解决。
解决该问题的原理:先隐藏,替换好值之后在显示最终的值。

  • 用法
    这个指令保持在元素上直到关联实例结束编译。和CSS规则如[v-cloak]{display: none}一起使用,这个指令可以隐藏未编译的Mustache标签,直到实例准备完毕。
/*CSS*/
[v-cloak] {
	display:none;
}
<div v-cloak>
{{message}}
</div>

不会显示直到编译结束。

2、动态样式绑定

动态绑定样式包括class的绑定和内联样式style的绑定。

2.1 绑定class属性

  • 传统的html的css类引用语法:
<h2 class="css类名1 css类名2">html传统写法</h2>

2.1.1 对象语法

  • v-bind对象语法,需要给css-class类名赋值一个boolean值,来决定css类是否生效。我们可以动态的改变布尔值,来切换样式。
<h2 v-bind:class="{css类名1: true|false, css类名2: true|false}">{{message}}</h2>
  • 我们使用了v-bind:class,class属性依然可以使用。
<h2 v-bind:class="{css类名1: true|false, css类名2: true|false}" class="css类名3">{{message}}</h2>

比如实现一个点击按钮修改按钮的样式。

   <button
      v-bind:class="{ 'button-selected': isSelected }"
      class="button-common button-normal"
      @click="change"
    >
      收藏
    </button>
data() {
    return {
      isSelected: false,
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
    },
  },

2.1.2 数组语法

  • 数组语法绑定class类
<div :class="['css类名1','css类名2']">数组语法</div>
  • 数组中嵌套对象
<div :class="['css类名1', 'css类名2', {css类名3: true|false}]"> 数组中嵌套对象</div>

利用数组实现:实现一个点击按钮修改按钮的样式

 <div>
    <button :class="classArr" @click="change">收藏</button>
  </div>
  data() {
    return {
      isSelected: false,
      classArr: ["button-common", "button-normal"],
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
      if (this.isSelected) {
        this.classArr = ["button-common", "button-selected"];
      } else {
        this.classArr = ["button-common", "button-normal"];
      }
    },
  },

2.1.3 class可以与:class一起使用

  • 我们使用了v-bind:class,class属性依然可以使用。
<h2 v-bind:class="{css类名1: true|false, css类名2: true|false}" class="css类名3">{{message}}</h2>

2.2 绑定style属性

  • 传统的html的style引用语法:
<h1 style="color: red; font-size: 18px;">内联样式</h1>

2.2.1 对象语法

 <h1 :style="{ color: 'red', 'font-size': '18px' }">内联样式</h1>

2.2.2 数组语法

 <h1 :style="[style1, style2]">内联样式</h1>
 data() {
    return {
      style1: { color: "red" },
      style2: { fontSize: "18px" },
    };
  },

2.2.3 style可以:style一起使用

 <h1 :style="[style1, style2]" style="text-decoration: underline">
      内联样式
    </h1>

3、计算属性

计算属性,就是当其依赖属性的值发生变化时,这个属性的值会自动更新,与之相关的 DOM 部分也会同步自动更新。— 官方定义

计算属性实际上是一个方法,它可以完成各种复杂的逻辑,包括运算、函数调用等,并最终返回一个值。

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。所以,对于任何复杂逻辑,你都应当使用计算属性。

3.1 使用计算属性

<button v-bind:class="buttonStyle" class="button-common" @click="change">
      收藏
    </button>
 data() {
    return {
      isSelected: false,
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
    },
  },
  computed: {
    buttonStyle() {
      if (this.isSelected === true) {
        return { "button-selected": true };
      } else {
        return { "button-normal": true };
      }
    },
  },

3.2 Getter和Setter

每一个计算属性都包含一个 getter 和一个 setter,我们上面的两个示例都是计算属性的默认用法,只是利用了 getter 来读取。在你需要时,也可以提供一个 setter函数 , 当手动修改计算属性的值就像修改一个普通数据那样时,就会触发 setter函数,执行一些自定义的操作。

<button v-bind:class="buttonStyle" class="button-common" @click="change">
      收藏
    </button>
 data() {
    return {
      isSelected: false,
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
    },
  },
  computed: {
    buttonStyle: {
      get() {
        if (this.isSelected === true) {
          return { "button-selected": true };
        } else {
          return { "button-normal": true };
        }
      },
      set(newValue) {},
    },
  },

3.3 计算属性缓存VS方法

 <button v-bind:class="buttonStyle()" class="button-common" @click="change">
      收藏
    </button>
  data() {
    return {
      isSelected: false,
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
      this.buttonStyle();
    },
    buttonStyle() {
      if (this.isSelected === true) {
        return { "button-selected": true };
      } else {
        return { "button-normal": true };
      }
    },
  },

通过上面的我们可以看出来,我们将buttonStyle定义为一个方法而不是计算属性,最终的结果是完全一样的。
然而不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时才会重新求值。也就意味着isSelected没有发生改变时,多次访问buttonStyle计算属性会立即返回之前的计算结果,而不必再次执行函数。

也就是说下面的计算属性将不会更新,因为Date.now()不是响应式依赖的。
当点击反复点击收藏,会发现now的值没有发生任何变化。
但是如果now是一个方法的话,反复点击总是获取到最新的时间。

<button v-bind:class="buttonStyle()" class="button-common" @click="change">
      收藏{{ now }}
      /*收藏{{now()}} now()是一个方法*/
    </button>
  data() {
    return {
      isSelected: false,
    };
  },
  methods: {
    change() {
      this.isSelected = !this.isSelected;
      this.buttonStyle();
    },
    buttonStyle() {
      if (this.isSelected === true) {
        return { "button-selected": true };
      } else {
        return { "button-normal": true };
      }
    },
  },
  computed: {
    now() {
      return Date.now();
    },
  },

所以在使用的过程中,如果你需要缓存,请使用方法。如果需要缓存请使用计算属性。

4 侦听器

侦听器 watchVue 提供的一种用来观察和响应 Vue 实例上的数据变化的属性。当被侦听的数据发生变化时,会触发对应的侦听函数。

当你有一些数据需要随着其它数据变动而变动时,通常更好的做法是使用计算属性而不是命令式的 watch 回调。如果当需要在数据变化时执行异步或者开销较大的操作时,用watch比较合适。

侦听器 watch 实际是 vue 实例上的一个对象属性。当我们需要对 vue 实例上某个属性进行侦听时,我们以需要被侦听的属性名作为 watch 对象的键,以一个函数 function 作为该键的值。函数 function 接收两个参数:侦听数据变化之后的值newValue;侦听数据变化之前的值oldValue。

4.1 对字符串、布尔值、数字、数组类型的监听

var vm = new Vue({
  el: '#app',
  data() {
    return {
      count: 0
    }
  },
  watch: {
    count: function(newVal, oldVal) {
      // 具体处理逻辑
    },
  }
})

4.2 侦听对象某个属性的变化

<div id="app">
      <div>商品名称:<input v-model="product.name"/></div>
    </div>
 var vm = new Vue({
      el: '#app',
      data: {
        product: {
          name: ''
        }
      },
      watch: {
        'product.name': function(newValue){
          console.log(newValue)
        }
      }
    })

4.4 监听对象

当watch去监听对象的时候,默认是监听的对象的地址,如果对象的地址发生变化,也就是对象被重新赋值才会触发监听函数。

 data() {
    return {
      product: {
        name: "111",
      },
    };
  },
  watch: {
    product(newValue) {
      console.log("触发监听方法");
      console.log(newValue);
    },
  },
  mounted() {
    this.product = { name: "hhhh" };
    // this.product.name = "liii";
  },

如果希望对象的某个属性变化时也能触发监听方法,可以使用deep属性。

4.5 deep属性

deep 属性代表是否深度监听,默认值是 false。当设置为 true 时,会对对象里面的每个属性进行侦听。

  data() {
    return {
      product: {
        name: "111",
      },
    };
  },
  watch: {
    product: {
      handler(newValue) {
        console.log("触发监听方法");
        console.log(newValue);
      },
      deep: true,
    },
  },
  mounted() {
    // this.product = { name: "hhhh" };
    this.product.name = "liii";
  }

4.6 immediate属性

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器.若想让 watch 侦听器立即被调用,则需要用immediate选项.

 watch: {
    product: {
      handler(newValue) {
        console.log("触发监听方法");
        console.log(newValue);
      },
      deep: true,
      //表示页面初次渲染好之后,就立即触发当前的 watch 侦听器 
      //immediate 的作用是: 控制侦听器是否自动触发一次!
      immediate:true
    },
  },

5 过滤器(Vue3已废弃)

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。 — 官方定义
过滤器是对即将显示的数据做进一步的筛选处理,然后进行显示,值得注意的是过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据。过滤器在日常开发中也会大量使用,例如:枚举值的转换、首字母大写等等。

过滤器有时候同methods、computed、watch一样可以达到处理数据的目的,但又不能替代它们,因为它不能改变原始值。如果一个过滤器的内部特别复杂,可以考虑把它写成一个计算属性,因为计算属性本身带有缓存,可复用性强,而过滤器一般用来做一些简单的操作。

5.1 定义过滤器

过滤器可以用在两个地方:双花括号插值和 v-bind 表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由 “管道” 符号表示:

<!-- 在双花括号中 -->
{{ message | filterName }}

<!--`v-bind`-->
<div v-bind:id="message | filterName"></div>

5.1.1 定义全局过滤器

通过 Vue.filter (name, function (){} ) 方式注册全局过滤器,第一个参数 name 为自定义过滤器名称;第二个参数是过滤函数,返回处理后的值。

 <div id="app">
    <div>
      <label>英文名:</label>
      <input type="text" v-model="name"/>
    </div>
    <div>{{ name | capitalize}}</div>
  </div>
 Vue.filter('capitalize', function(value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        name: ''
      }
    }
  })

5.1.2 定义局部过滤器

在 Vue 实例 options 中使用 filters 选项来注册局部过滤器,这样过滤器只能在这个实例中使用:

filters: {
  '过滤器名字': function(value) {
    // 具体过滤逻辑
  }
}
<div id="app">
    <div>
      <label>英文名:</label>
      <input type="text" v-model="name"/>
    </div>
    <div>{{ name | capitalize}}</div>
  </div>
var vm = new Vue({
    el: '#app',
    data() {
    	return {
        name: ''
      }
    },
    filters: {
      capitalize: function(value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
  })

注意:

  • 全局注册时是 filter 没有 s , 而组件过滤器是 filters,是有 s 的,虽然写的时候没有 s 也不报错,但是过滤器是没有效果的。
  • 当全局过滤器和局部过滤器名字重复的时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用
  • 一个表达式可以使用多个过滤器,其执行顺序从左往右,前一个过滤器的结果作为后一个过滤器的被处理数据,所以要注意使用顺序

在实际开发中,全局的过滤器要比局部过滤器使用的更广泛一些,说白了我们为什么要使用过滤器,其实就跟使用函数是一样,把一些方法封装,供其它组件使用,这样调用起来更方便也更快捷。

大家知道全局过滤器是在 main.js 中定义的,但万一项目过大,有多个过滤器,那 main.js 就一堆代码,为了项目模块化,最好是有专门的目录来统一存放这些过滤器,然后把处理函数给抽离出去,放在一个.js文件中,下边通过实例代码展示。

5.1.3 示例一(局部过滤器)

格式化时间或日期,补全指定位数,不足个位数补0

// filter/index.js文件
export default {
    dateFormat: value => {
        const dt = new Date(value * 1000)
    
        const y = dt.getFullYear()
        const m = (dt.getMonth() + 1 + '').padStart(2, '0') // .padStart(指定位数,"要补全的符号或值")
        const d = (dt.getDay() + '').padStart(2, '0')
        
        const hh = (dt.getHours() + '').padStart(2, '0')
        const mm = (dt.getMinutes() + '').padStart(2, '0')
        const ss = (dt.getSeconds() + '').padStart(2, '0')
        
        return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
    }
}// 在 .vue 文件中使用局部过滤器
<script>
    import filters from '../filter'export default {
        ... ... 
        filters: { ...filters },
        data() {
            return {}
        }
    }
</script><div> 日期:{{ date | dateFormat }} </div>

5.1.4 示例二(全局过滤器)

通用字典项的回显:比如性别男女或通用选择是否,后端传给我们的数据是0、1,我们需要在页面上显示男女或是否

// constants/dictionary.js 文件export const GENDER_MENU = [
    { code: 0, label: '男'},
    { code: 1, label: '女'}
];export const COMMON_MENU = [
    { code: 0, label: '否'},
    { code: 1, label: '是'}
];export default {
    GENDER_MENU, COMMON_MENU
}

filter/dict.js 文件

// filter/dict.js 文件import Dict from '../constants/dictionary'export const genderMenu = {
    func: value => {
        const target = Dict.GENDER_MENU.filter(item => {
            return item.code === value;
        })
        return target.length ? target[0].label : value;
    }
}export const commonMenu = {
    func: value => {
        const target = Dict.COMMON_MENU.filter(item => {
            return item.code === value;
        })
        return target.length ? target[0].label : value;
    }
}

filter/index.js 文件

// filter/index.js 文件import * as filters from './dict' // 导入过滤函数const Install = Vue => {
    // 导入的 filters 是一个对象,使用Object.keys()方法,得到一个由key组成的数组,遍历数据,让key作为全局过滤器的名字,后边的是key对应的处理函数,这样在任何一个组件中都可以使用全局过滤器了
    Object.keys(filters).forEach(key => {
        Vue.filter(key, filters[key].func)
    })
    /*
    for(const _filter in filters) {
        Vue.filter(`${_filter}`, filters[_filter].func)
    } */
}export default Install

main.js 文件

// main.js 文件... ...
import filters from  './../filter/index'
Vue.use(filters)
... ...

在.vue 文件中使用全局过滤器

// .vue 文件中使用全局过滤器<p>性别:{{ gender | genderMenu }}</p>  

5.2 过滤器串联

{{ message | filterA | filterB }}

filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

<div id="app">
    <div>
      <label>英文名:</label>
      <input type="text" v-model="name"/>
    </div>
    <div>{{ name | trim | capitalize}}</div>
  </div>
Vue.filter('capitalize', function(value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  })
  Vue.filter('trim', function(value) {
    return value.replace(/^\s+|\s+$/g,"")
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        name: ''
      }
    }
  })

5.3 接收多个参数的过滤器

过滤器是 JavaScript 函数,因此可以接收参数:

<!-- 在双花括号中 -->
{{ message | filteName(params1,params2) }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="message | filteName(params1,params2)"></div>
  <div id="app">
    <div>
      <label>个数:</label>
      <input type="number" v-model="count"/>
      <label>单位: 1000 </label>
    </div>
    <div>单价: {{price}} / 个</div>
    <div>总价: {{ count | sum(price, unit)}}</div>
  </div>
  Vue.filter('sum', function(value, price, unit) {
    return value * price * unit
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        count: 0,
        unit: 1000,
        price: 20
      }
    }
  })

6、组件

组件是可复用的 Vue 实例,所以它们与new Vue()接收相同的选项,例如data、computed、watch、methods以及生命周期钩子等。除了el这种是根实例特有的选项。

组件是可以扩展HTML元素,封装可重用的代码。

几乎任意类型的应用界面都可以抽象为一个组件树。

在这里插入图片描述
几乎所有的页面都可以抽象成组件树,但为什么是树状呢?因为我们的页面组成、DOM 节点、HTML 元素也都是树状结构的,组件说白了也就是将一部分的 HTML+CSS+Javascript 代码组合成一个可复用、更简洁表达的代码片段,因此最终呈现也是树状结构的。
树状结构是组织和管理中十分快捷、方便又清晰的结构,我们的文件管理、思维导图等也都是基于树状结构来进行的。

6.1 组件的注册

HTML 中的标签名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。在 Vue 中,组件命名有两种方式:
(1) 使用短横线分隔(kebab-case)命名。当使用 kebab-case 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,如<my-component></my-component>
(2) 使用大驼峰(PascalCase)。当使用 PascalCase 定义一个组件时,既可以使用 PascalCase 也可以使用 kebab-case 来引用,像<my-component></my-component><MyComponent></MyComponent>

6.1.1 全局组件注册

我们可以通过调用 Vue.component 的方式来定义全局组件,它接收两个参数:1. 组件名,2. 组件属性对象。

全局组件可以在任何其他组件内使用,所以当我们设计的组件,需要在不同地方使用的时候,我们应当注册全局组件。

Vue.component("my-button", {
  // 选项
  // 除了 el 以外,组件的选项与 Vue 实例相同
});
// 全局注册一个名为 button-counter 的新组件
 Vue.component('button-counter', {
        data: function() {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="count++;">点击了几次{{count}}</button>'
    })

6.1.2 局部组件注册

局部注册可通过在实例中的components选项进行配置,这样组件只能在这个实例中使用。

// 获取组件
import MyButton from "../components/my-button";

new Vue({
  components: { MyButton }
});

而以这种方式使用组件的时候,则需要在组件里通过name选项进行命名:

// my-button.vue
new Vue({
  name: "my-button"
});

这种方式定义的组件,如果也进行了全局注册,其命名会以全局注册的名字为准,也就是全局注册的命名优先级更高。

  // 定义组件
    var buttonCounter = {
        data: function() {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="handle">点击了几次{{count}}</button>',
        methods: {
            handle: function() {
                this.count += 2;
            }
        }
    }
    var componentTest = {
        template: ''
    }
    var vue = new Vue({
        el: '#app',
        // 注册局部组件
        components: {
            'buttonCounter': buttonCounter,
            'componentTest': componentTest
        }
    })

局部组件只能在它注册的父组件中使用,比如上面的例子中只能在#app里面使用。

6.2 单个文件组件

一个组件是一些逻辑和功能完整的代码片段组成的,同时也包括了 HTML、CSS 和 Javascript 的代码。在 Vue 里,我们常常使用单文件组件,使用.vue 后缀命名的文件,一般也包括这三部分:

<template>
  <!-- 组件模板 -->
</template>

<script>
  // 组件逻辑
  // 在.vue文件中,需要默认export一个Vue实例
  export default {
    name: "MyComponent"
  };
</script>

<style>
  /* 组件样式 */
</style>

通过这种方式,我们可以更方便地在项目中管理组件和文件。这里的样式,我们还可以通过添加scoped属性的方式<style scoped>,来增加组件的样式作用域。具体的实现是,通过使用 PostCSS 来实现以下转换:

<template>
  <div class="example">hi</div>
</template>

<style scoped>
  .example {
    color: red;
  }
</style>

<!-- 会转换成以下 -->
<!-- 通过增加局部随机属性,来匹配到具体的组件 -->
<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

<style>
  .example[data-v-f3f3eg9] {
    color: red;
  }
</style>

除了scoped之外,前面我们也讲到官方的脚手架 Vue CLI 中内置了 CSS Loader 一大堆,包括 extract-css-loader、vue-style-loader、css-loader、cssnano、postcss-loader、sass-loader、less-loader 等。所以我们在使用官方脚手架的时候,也可以尽情地使用这些 CSS 预处理器,使用 lang 属性就可以:<style lang="scss"></style>

单文件组件是 Vue 里推荐的使用方式,如果开发者不习惯在一个页面中同时维护着 HTML、CSS 和 Javascript 的话,也可以通过 src 属性等方式来引入:

<!-- my-component.vue -->
<template>
  <div>This will be pre-compiled</div>
</template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>

6.3 组件间通信

6.3.1 父组件向子组件传值

父组件通过属性的方式向子组件传值,子组件通过props方式来接收。

在父组件的子组件标签中绑定自定义属性。

// 父组件,子组件的自定义属性myName
<user-detail :myName="name" />
    
export default {
    components: {
        UserDetail
    }
    ......
}

在子组件中使用props(可以是数组,也可以是对象)接收。

// 子组件
export default {
    props: ['myName'],
    mounted(){
    	//直接通过this调用。
		console.log(this.myName);
	}
}

基于 vue 的单向数据流,即组件之间的数据是单向流通的,子组件是不允许直接对父组件传来的值进行修改的,所以应该避免这种直接修改父组件传过来的值的操作,否则控制台会报错。

  • 如果传过来的值是简单数据类型,是可以在子组件中修改,也不会影响其他兄弟组件内同样调用了来自该父组件的值。

具体操作是可以先把传过来的值重新赋值给data中的一个变量,然后再更改那个变量。

// 子组件
export default {
    props: ['myName'],
    data() {
        return {
            name : this.myName    // 把传过来的值赋值给新的变量
        }
    },
    watch: {
        myName(newVal) {
            this.name = newVal //对父组件传过来的值进行监听,如果改变也对子组件内部的值进行改变
        }
    },
    methods: {
        changeName() {  
            this.name = 'Lily'  // 这里修改的只是自己内部的值,就不会报错了
        },
    }
}
  • 注:如果不使用 watch 来监听父组件传递的 myName 值,子组件中的 name 值是不会随着父组件的 myName 值进行改变,因为 data 中 name: this.myName 仅仅只是定义了一个初始值。
  • 如果引用类型的值,当在子组件中修改后,父组件的也会修改,因其数据是公用的,其他同样引用了该值的子组件也会跟着被修改。可以理解成父组件传递给子组件的值,就相当于复制了一个副本,这个副本的指针还是指向父组件中的那个,即共享同一个引用。所以除非有特殊需要,否则不要轻易修改。
Prop 类型

props可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值等等:

props高级选项作用说明
type会检查该prop是否是给定的类型,否则抛出警告可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组
default为该 prop 指定一个默认值如果该 prop 没有被传入,则换做用这个值,对象或数组的默认值必须从一个工厂函数返回
required定义该 prop 是否是必填项,默认为false在非生产环境中,如果这个值为true且该prop没有被传入的,则一个控制台警告将会被抛出
validator自定义验证函数会将该 prop 的值作为唯一的参数代入在非生产环境下,如果该函数返回一个false的值 (也就是验证失败),一个控制台警告将会被抛出
Vue.component("my-component", {
  // 简单的数组
  props: ["propA", "propB", "propC", "propD", "propE", "propF"],
  // 高级配置
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function() {
        return { message: "hello" };
      }
    },
    // 自定义验证函数
    propF: {
      validator: function(value) {
        // 这个值必须匹配下列字符串中的一个
        // 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
        return ["success", "warning", "danger"].indexOf(value) !== -1;
      }
    }
  }
});

注意,给数组和对象类型的 props 设置默认值的时候,需要按照以下的写法:

props: {
  detail: {
    type: Object,
    default: () => {
      return {
        name: '句号'
      }
    }
  },
  loves: {
    type: Array,
    default: () => {
      return []
    }
  }
}

6.3.2 子组件向父组件传值

子组件绑定一个事件,通过this.$emit()来触发

在子组件中绑定一个事件,并给这个事件定义一个函数。

// 子组件
<button @click="changeParentName">改变父组件的name</button>export default {
    methods: {
        //子组件的事件
        changeParentName: function() {
            this.$emit('handleChange', 'Jack') // 触发父组件中handleChange事件并传参Jack
            // 注:此处事件名称与父组件中绑定的事件名称要一致
        }
    }
}

在父组件中定义并绑定handleChange事件。

// 父组件
<child @handleChange="changeName"></child>
​
methods: {
    changeName(name) {  // name形参是子组件中传入的值Jack
        this.name = name
    }
}
通过 callback 函数

先在父组件中定义一个callback函数,并把 callback 函数传过去

// 父组件
<child :callback="callback"></child>
​
methods: {
    callback: function(name) {
        this.name = name
    }
}

在子组件中接收,并执行 callback 函数

// 子组件
<button @click="callback('Jack')">改变父组件的name</button>
​
props: {
    callback: Function,
}
通过 $parent / $children 或 $refs 访问组件实例

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。

// 子组件  
export default {  
    data () {  
        return {  
            title: '子组件'  
        }  
    },  
    methods: {  
        sayHello () {  
            console.log('Hello');  
        }  
    }  
}
// 父组件
<template>
  <child ref="childRef" />
</template><script>
  export default {
    created () {
      // 通过 $ref 来访问子组件
      console.log(this.$refs.childRef.title);  // 子组件
      this.$refs.childRef.sayHello(); // Hello
      
      // 通过 $children 来调用子组件的方法
      this.$children.sayHello(); // Hello 
    }
  }
</script>

注:这种方式的组件通信不能跨级。

$attrs / $listeners

https://segmentfault.com/a/1190000022708579

6.3.3 兄弟组件间数据传递

$emit和props结合的方式

对于兄弟组件的数据通信:它们有共同的父组件,我们可以通过父组件传递的方式实现数据通信。

当子组件B 通过 $emit() 触发了父组件的事件函数 editName,改变了父组件的变量name 值,父组件又可以把改变了的值通过 props 传递给子组件A,从而实现兄弟组件间数据传递。

<child-a :myName="name" />
<child-b :myName="name" @changeName="editName" />  
    
export default {
    data() {
        return {
            name: 'John'
        }
    },
    components: {
        'child-a': ChildA,
        'child-b': ChildB,
    },
    methods: {
        editName(name) {
            this.name = name
        },
    }
}

在子组件B中接收变量和绑定触发事件

// child-b 组件
<p>姓名:{{ myName }}</p>
<button @click="changeName">修改姓名</button>
    
<script>
export default {
    props: ["myName"],
    methods: {
        changeName() {
            this.$emit('changeName', 'Lily')   // 触发事件并传值
        }
    }
}
</script>
// child-a 组件
<p>姓名:{{ newName }}</p>
    
<script>
export default {
    props: ["myName"],
    computed: {
        newName() {
            if(this.myName) { // 判断是否有值传过来
                return this.myName
            }
            return 'John' //没有传值的默认值
        }
    }
}
</script>

对于子孙组件的数据通信:可以通过 props 的方式向下逐层传递下去,也可以通过 $emit 将事件向上逐层传递。

.sync修饰符

在 vue 2.3 中,为了提供更高效的开发方式,对子组件向父组件通信进行了封装。使用 .sync 语法糖,代替了原本需要些一大堆的事件监听模式。

  • 在子组件中,使用this.$emit("update:属性名",新值);来对props 里面被.sync 修饰的值进行修改。
  • 在父组件中,<child v-bind:属性名.sync="属性值"></child>,使用 b-bind 配合 sync 修饰符。
//子组件
<template>
    <div>
        子组件展示父组件传入的值:{{timer}}
    </div>
</template>

<script>
    export default {
        name: "floatMenu",
        props:['timer'],
        mounted() {
            setInterval(()=>{
                this.$emit("update:timer",new Date().getTime());
            },2000)
        },
    }
</script>
//父组件
<template>
    <div>
        <h4>父组件中的值:{{fTime}}</h4>
        <child v-bind:timer.sync="fTime"></child>
    </div>
</template>

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

    export default {
        name: "father",
        components: {
            child,
        },
        data() {
            return {
                active: "index",
                fTime: new Date().getTime(),
            }
        },
    }
</script>
通过一个 空 vue 实例

对于非关系组件的数据通信:通过使用一个空的Vue实例作为中央事件总线。

创建一个 EventBus.js 文件,暴露一个 vue 实例。

import Vue from 'Vue'  
export default new Vue()

在要传值的文件里导入这个空 vue 实例,绑定事件并通过 $emit 触发事件函数
(也可以在 main.js 中全局引入该 js 文件,我一般在需要使用到的组件中引入)

<template>
    <div>
        <p>姓名: {{ name }}</p>
        <button @click="changeName">修改姓名</button>
    </div>
</template><script>
import { EventBus } from "../EventBus.js"export default {
 data() {
     return {
         name: 'John',
     }
  },
  methods: {
      changeName() {
          this.name = 'Lily'
          EventBus.$emit("editName", this.name) // 触发全局事件,并且把改变后的值传入事件函数
      }
    }
}
</script>

在接收传值的组件中也导入 vue 实例,通过 $on 监听回调,回调函数接收所有触发事件时传入的参数

import { EventBus } from "../EventBus.js"export default {
    data() {
        return {
            name: ''
        }
    },
    created() {
         EventBus.$on('editName', (name) => {
             this.name = name
         })
    }
}

这种通过创建一个空的 vue 实例的方式,相当于创建了一个事件中心或者说是中转站,用来传递和接收事件。这种方式同样适用于任何组件间的通信,包括父子、兄弟、跨级,对于通信需求简单的项目比较方便,但对于更复杂的情况,或者项目比较大时,可以使用 vue 提供的更复杂的状态管理模式 Vuex 来进行处理。

vuex

6.3.4 多层父子组件通信

provide/inject

有时需要实现通信的两个组件不是直接的父子组件,而是祖父和孙子,或者是跨越了更多层级的父子组件,这种时候就不可能由子组件一级一级的向上传递参数,特别是在组件层级比较深,嵌套比较多的情况下,需要传递的事件和属性较多,会导致代码很混乱。

这时就需要用到 vue 提供的更高阶的方法:provide/inject。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

provide/inject:简单来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量,不管组件层级有多深,在父组件生效的生命周期内,这个变量就一直有效。

父组件:

export default {
  provide: { // 它的作用就是将 **name** 这个变量提供给它的所有子组件。
    name: 'Jack'
  }
}

子组件:

export default {
  inject: ['name'], // 注入了从父组件中提供的name变量
  mounted () {
    console.log(this.name);  // Jack
  }
}

注:**provide 和 inject 绑定并不是可响应的。**即父组件的name变化后,子组件不会跟着变。

如果想要实现 provide 和 inject 数据响应,有两种方法:
1.provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在后代组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props,methods

// 父组件 
<div>
      <button @click="changeName">修改姓名</button>
      <child-b />
</div>
<script>
    ......
    data() {
        return {
            name: "Jack"
        };
    },
    provide() {
        return {
            parentObj: this //提供祖先组件的实例
        };
    },
    methods: {
        changeName() {
            this.name = 'Lily'
        }
    }
</script>

后代组件中取值:

<template>
  <div class="border2">
    <P>姓名:{{parentObj.name}}</P>
  </div>
</template>
<script>
  export default {
    inject: {
      parentObj: {
        default: () => ({})
      }
    } // 或者inject: ['parentObj']
  };
</script>

注:这种方式在函数式组件中用的比较多。函数式组件,即无状态(没有响应式数据),无实例化(没有 this 上下文),内部也没有任何生命周期处理方法,所以渲染性能高,比较适合依赖外部数据传递而变化的组件。

2.使用 Vue.observable 优化响应式 provide。

Bus
Vuex
a t t r s / attrs/ attrs/listeners

6.3.5 插槽的使用

<slot> 元素可以理解为一个占位符,或者说是子组件暴露的一个让父组件传入自定义内容的接口。插槽内可以包含任何模板代码,包括HTML,甚至是其他的组件。在工作中如果你的组件内容是可变的,这个时候我们可以考虑使用插槽。

一个组件的展示层你需要做到大体结构固定,但其内的部分结构可变,样式表现不固定。例如 Button 中是否显示 icon,或者 Modal 框的中间内容展示区域的变化等,要通过子组件自己实现是不可能的。组件并不直接支持 HTML DOM 结构的传递,此时就可以通过使用 slot 作为 HTML 结构的传递入口来解决问题。

插槽的类型
  • 匿名插槽 default
  • 具名插槽 name
  • 作用域插槽 v-slot
默认插槽的使用

匿名插槽也叫做默认插槽。和具名插槽相对比,它是不需要设置name属性的,它隐藏的name属性默认为default

在自定义组件中使用<slot />标签进行占位

{
  components: {
    'MyConponent': {
      template: '<div><slot /></div>'
    }
  }
}

当我们使用该组件时,在组件标签内写入需要展示的具体内容:

<my-conponent>这里是要显示的插槽内容!</my-conponent>

在使用的时候还有一个问题要注意的 如果是有2个以上的匿名插槽是会child标签里面的内容全部都替换到每个slot;

具名插槽的使用

具名插槽就是给<slot />标签加上 name 属性。语法:<my-component><slot name="插槽名字"/></my-component>

定义具名插槽:

 Vue.component('myComponent', {
    template: '<div>Hello !<slot name="xingming"></slot> <slot name="age"></slot></div>'
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
 })

使用具名插槽:

  <div id="app">
    <my-component>
      <span slot="xingming">姓名</span>
      <span slot="age">18</span>
    </my-component>
  </div>
作用域插槽的使用

子组件内数据可以被父页面拿到(解决了数据只能从父页面传递给子组件)
用法:将 data 变量名 作为 <slot> 元素的一个属性绑定上去。

默认插槽中使用作用域插槽

定义作用域插槽:<slot :变量名=需要传入的数据></slot>

 Vue.component('myComponent', {
    // 在默认插槽中传入数据 detail
    template: '<div>Hello !<slot :detail="detail"></slot></div>',
    data() {
      return {
        detail: {
          name: '句号',
          love: 'coding'
        }
      }
    }
  })

在使用组件时,通过v-slot:插槽名字='数据别名'的方式使用。

 <div id="app">
        <my-component>
          <!-- 使用 v-slot 接收默认插槽的数据,并把数据命名为  slotProps-->
          <template v-slot:default="slotProps">
            <br />
            <!-- 使用 插槽数据-->
            我叫:{{slotProps.detail.name}},我的爱好是:{{slotProps.detail.love}}
          </template>
        </my-component>
      </div>
具名插槽中使用作用域插槽

具名插槽必须在v-slot后面加上插槽名,否则数据无法在具名插槽中使用。
定义作用域插槽:<slot :变量名=需要传入的数据></slot>

  Vue.component('myComponent', {
    // 定义具名插槽 detail 并传入数据 detail
    template: '<div>Hello !<slot :detail="detail" name="detail"></slot></div>',
    data() {
      return {
        detail: {
          name: '句号',
          love: 'coding'
        }
      }
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
  })

在使用组件时,通过v-slot:插槽名字='数据别名'的方式使用。

<div id="app">
        <my-component>
          <!-- 使用 v-slot 接收具名插槽 detail 的数据,并把它命名为  slotProps-->
          <template v-slot:detail="slotProps">
            <br />
            <!-- 使用 插槽 detail 的数据-->
            我叫:{{slotProps.detail.name}},我的爱好是:{{slotProps.detail.love}}
          </template>
        </my-component>
    </div>

6.3.5 组件的封装

怎样才算一个称职的组件

能力描述Vue中对应的属性
组件内维护自身的数据和状态data
组件内维护自身的事件methods
通过提供配置的方式,来控制展示,或者控制执行逻辑props
通过一定的方式(事件触发/监听、API提供),提供与外界(如父组件)$emit、$on、ref

6.4 动态组件

动态组件是让多个组件使用同一个挂载点,并动态切换。
keep-alive 是 vue 的内置组件,能在组件切换过程中将状态保存在内存中,防止重复渲染 DOM。

6.4.1 使用动态组件

通过使用保留的 <component> 元素,动态地把组件名称绑定到它的 is 特性,可以实现动态组件:

<div id="app">
    <component :is="currentView"></component>
    <button @click="changeView('A')">切换到A</button>
    <button @click="changeView('B')">切换到B</button>
    <button @click="changeView('C')">切换到C</button>
  </div>
  Vue.component('ComponentA', {
    template: '<div> 组件 A </div>',
  })
  Vue.component('ComponentB', {
    template: '<div> 组件 B </div>',
  })
  Vue.component('ComponentC', {
    template: '<div> 组件 C </div>',
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        currentView: 'ComponentB'
      }
    },
    methods: {
      changeView(name) {
        this.currentView = `Component${name}`
      }
    }
  })

6.4.2 keep-alive

keep-alive 是 Vue 提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个 DOM 元素。被 keep-alive 缓存的组件只有在初次渲染时才会被创建,并且当组件切换时不会被销毁。

基础用法

直接使用 keep-alive 包裹需要缓存的组件即可:

 <div id="app">
    <keep-alive>
      <component :is="currentView"></component>
    </keep-alive>
    <button @click="changeView('A')">切换到A</button>
    <button @click="changeView('B')">切换到B</button>
    <button @click="changeView('C')">切换到C</button>
  </div>
  Vue.component('ComponentA', {
    template: '<div> 组件 A </div>',
    created() {
      console.log('组件A created')
    }
  })
  Vue.component('ComponentB', {
    template: '<div> 组件 B </div>',
    created() {
      console.log('组件B created')
    }
  })
  Vue.component('ComponentC', {
    template: '<div> 组件 C </div>',
    created() {
      console.log('组件C created')
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        currentView: 'ComponentB'
      }
    },
    methods: {
      changeView(name) {
        this.currentView = `Component${name}`
      }
    }
  })

keep-alive 缓存的组件只有在初次渲染时才会被创建。所以,我们通过修改 currentView 切换组件时,组件的 beforeDestroy 事件不会触发。若该组件是第一次渲染,会触发 created 事件,当再次切换显示该组件时,created 事件不会再次触发。

activated 和 deactivated 生命周期

activateddeactivated 和我们之前学习的生命周期函数一样,也是组件的生命周期函数。不过, activateddeactivated 只在 <keep-alive> 内的所有嵌套组件中触发。activated:进入组件时触发。deactivated:退出组件时触发。

include 和 exclude

include 和 exclude 是 keep-alive 的两个属性,允许组件有条件地缓存。
include: 可以是字符串或正则表达式,用来表示只有名称匹配的组件会被缓存。
exclude: 可以是字符串或正则表达式,用来表示名称匹配的组件不会被缓存。

6.5 异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,并且把结果缓存起来供未来重渲染。

6.5.1 基本用法

    // 全局注册同步组件
    Vue.component('sync-component',{
        template:'<div>同步组件</div>'
    })
    // 全局注册异步组件
    Vue.component('async-component',function(resolve,reject){
    	//向`resolve`回调传递组件定义
        resolve({template:'<div>异步组件</div>'})
    })

这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。

webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

// 这个 `import` 函数会返回一个 `Promise` 对象。

Vue.component('async-webpack2-component',()=>import('./async-component'))

当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:

   export default {
        data () {
            return {}
        },
        components: {
            'async-component': () => import('./async-component')
        }
    }

6.5.2 异步组件配合webpack进行代码分割

为什么有时候要使用异步导入组件配合webpack进行代码分割呢?因为如果我们有一个组件非常庞大,把所有得组件都打包到app.vue入口文件中,当页面加载时将会非常慢,其中有一些组件在当前路由中并没有使用到,但也会被加载,这时导致页面加载非常慢的重要原因。如果这些组件只有在使用时才被加载,这样肯定会加快整个页面的加载速度,而webpack正好能够帮我们将异步组件单独打包成文件。

7 Vue混入Mixins

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

我们在日常开发中经常遇到多个页面或者功能模块有相同代码逻辑的情况,Mixin 帮助我们抽离公共代码逻辑。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被 “混合” 进入该组件本身的选项。

7.1 使用mixin

7.1.1 定义一个mixin

mixin本质上就是一个Object对象,它和vue实例上的属性一致,包含data、methods、computed、watch、生命周期函数等等。

var myMixin = {
	data() {
		return {
			//...
		}
	},
	created() {
		//...
	},
	methods:{
		//...
	},
	computed() {
		//...
	}
}

7.1.2 混入mixin

想要混入定义好的mixin,只需要通过组件的mixins属性传入想要混入的mixin数组即可:

var vm = new Vue({
	el:'#app',
	mixins:[myMixin]
})

我们定义了一个 Vue 实例,并在实例上混入 myMixin。

实例:

 <div id="app">
    我是:{{name}}, 年龄:{{year}}
  </div>
 // 定义 mixin
  var myMixin = {
    data(){
      return {
        name: '句号'
      }
    },
    created: function () {
      this.mixinFun()
    },
    methods: {
      mixinFun: function () {
        console.log('mixin function')
      }
    }
  };
  var vm = new Vue({
    el: '#app',
    // 使用mixin
    mixins:[myMixin],
    data() {
    	return {
        year: '18'
      }
    }
  })

运行程序可以看到,在 myMixin 中定义的数据 name 渲染到页面上。同时打开控制台可以看到 ‘mixin function’ 被打印出来,说明 created 钩子函数被执行。

7.2 选项合并

我们在定义 mixin 时会出现属性名重复的情况,例如:

var myMixin = {
  data() {
    return {
      name: 'Imooc'
    }
  },
  create() {
    console.log('Imooc')
  }
}

var vm = new Vue({
  data() {
    return {
      name: '句号'
    }
  },
  create() {
    console.log('句号')
  }
})

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行 “合并”。这些重复项的合并有固定的规则,接下来我们从三个方面来详细讲解选项合并的规则。

7.2.1 data的合并

数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。详细的合并规则如下:
1、首先判断mixin中的数据类型和组件实例对象上的数据类型是否相同;
2、如果不同,组件实例上的数据将覆盖mixin上的数据;
3、如果相同,判断是否为Object的数据格式
4、如果不是Object的数据格式,组件实例上的数据将覆盖mixin的数据;
5、如果是Object的数据格式,从第一步开始循环判断Object的每一个属性。

 <div id="app"></div>
  var myMixin = {
    data(){
      return {
        isOpen: false,
        date: '2020-02-02',
        desc: {
          title: 'Mixins 基础教程',
          desc: '本教程将讲解如何使用 mixins',
          author: {
            name: '慕课网',
            location: '北京'
          }
        }
      }
    }
  };
  var vm = new Vue({
    el: '#app',
    mixins:[myMixin],
    data() {
    	return {
        isOpen: true,
        date: new Date().toLocaleString(),
        desc: {
          author: {
            name: 'Imooc',
            age: '20'
          }
        }
      }
    },
    created() {
      console.log(this.data)
    }
  })

7.2.2 钩子的合并

同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
也就是说,如果我们在 mixin 和组件中都定义了钩子函数 created,那么 mixin 和 组件中的函数都会被执行。需要注意的是:mixin 中的钩子函数将在组件的钩子函数之前执行。

 <div id="app">
    mixin 示例
  </div>
 var myMixin = {
    created() {
      alert('mixin created 先执行')
    }
  };
  var vm = new Vue({
    el: '#app',
    mixins:[myMixin],
    created() {
      alert('组件 created 后执行')
    }
  })

7.2.3 值为对象的选项合并

值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

 <div id="app">
    mixin 示例
  </div>
 var myMixin = {
    methods: {
      sayName() {
        console.log('我是慕课网')
      },
      sayHello() {
        console.log('hello 大家好')
      }
    }
  };
  var vm = new Vue({
    el: '#app',
    mixins:[myMixin],
    methods: {
      sayName() {
        console.log('我是句号')
      },
      sayYear() {
        console.log('我的年龄是:18')
      }
    }
  })
  vm.sayName() // ---> 我是句号
  vm.sayHello() // ---> hello 大家好
  vm.sayYear() // ---> 我的年龄是:18

7.3 全局混入

混入也可以进行全局注册。使用时需要格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。通过 Vue.mixin ({…}) 可以注册全局混入:

Vue.mixin({
  data: {
    name: "Imooc"
  }
})
 <div id="app">
    mixin 全局混入 示例
  </div>
  Vue.mixin({
    created() {
      console.log('全局mixin created')
    },
    methods: {
      sayHello() {
        console.log('hello 大家好')
      }
    }
  })

  var vm = new Vue({
    el: '#app'
  })
  vm.sayHello()

8、自定义指令

Vue 除了提供了默认内置的指令外,还允许开发人员根据实际情况自定义指令,它的作用价值在于当开发人员在某些场景下需要对普通 DOM 元素进行底层操作的时候。 – 官方定义

8.1 注册自定义指令

Vue 自定义指令和组件一样存在着全局注册和局部注册两种方式。全局注册的自定义指令可以在项目中的所有组件中使用,局部注册的指令只能在当前组件内部使用。

8.1.1 全局注册

我们可以通过调用 Vue.directive 的方式来定义全局指令, 它接收两个参数:1. 指令名,2. 指令的钩子函数对象。
命名:

  • 短横线:<my-directive>
  • 驼峰式: <MyDirective>使用驼峰命名指令时,首字母最好以大写字母开头。

注意:注册指令时,指令名称不需要家-v前缀,默认是自动加上前缀的,使用指令的时候一定要加上v-前缀。

// 注册
// 驼峰命名
Vue.directive('MyDirective', {/* */})
// 短横线命名
Vue.directive('my-directive', {/* */})

// 使用
<div v-my-directive></div>

下面我们注册一个全局指令 v-focus,该指令的功能是在页面加载时,使得元素获得焦点。

<div id="app">
    <div>
      <label>姓名:</label>
      <input id="name" v-focus type="text"/>
    </div>
    <div>
      <label>年龄:</label>
      <input id="age" type="text"/>
    </div>
  </div>
  Vue.directive('focus', {
    inserted(el) {
      el.focus()
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
  })

我们定义了指令 v-focus,定义 inserted 钩子函数,在节点被插入时获得焦点。
HTML 代码第 4 行,我们在 input 元素上使用指令,当页面打开时 id 为 name 的输入框会自动获取焦点。

8.1.2 局部注册

指令的局部注册和组件的局部注册类似,在实例的参数options中使用directives选项来注册局部指令,局部指令只能在当前这个实例中使用:

// 注册
// 短横线命名
{
  directives: {
      'my-directive': {
        inserted: function (el) {
          el.focus()
        }
      }
    }
}
// 驼峰命名
{
  directives: {
      'MyDirective': {
        inserted: function (el) {
          el.focus()
        }
      }
    }
}

// 使用
<div v-my-directive></div>
  <div id="app">
    <div>
      <label>姓名:</label>
      <input v-focus type="text"/>
    </div>
    <div>
      <label>年龄:</label>
      <input type="text"/>
    </div>
  </div>
var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    },
    directives: {
      focus: {
        inserted: function (el) {
          el.focus()
        }
      }
    }
  })

8.2 钩子函数

Vue.directive 第二个参数接收的是钩子函数对象,这些钩子函数都是可选的。

  • bind:只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始化设置;
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中);
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下);
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用;
  • unbind:只调用一次,指令与元素解绑时调用。

8.2.1 钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM ;
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀;
    • value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2;
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用;
    • expression:字符串形式的指令表达式,例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”;
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情;
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

8.2.2 动态指令参数

指令的参数可以是动态的。例如,在 v-mydirective:[argument]=“value” 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
例如你想要创建一个自定义指令,用来改变页面元素的字体颜色。我们可以像这样创建一个通过指令值来更新字体颜色的自定义指令:

  <div id="app">
    <div v-color="color">Hello !</div>
    <button @click="changeColor">切换颜色</button>
  </div>
Vue.directive('color', {
    bind: function (el, binding, vnode) {
      el.style.color = binding.value
    },
    update(el, binding) {
      el.style.color = binding.value
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        color: 'red'
      }
    },
    methods: {
      changeColor() {
        this.color = '#' + Math.floor( Math.random() * 0xffffff ).toString(16);
      }
    }
  })

上面的例子中我们通过指令动态设置了元素的字体颜色。但如果场景是我们需要修改元素的边框颜色又该怎么办呢?有些同学可能说我们可以再写一个 v-border-color 不就行了。那如果又有修改背景色的需求呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新:

<div id="app">
    <div v-color:[colorstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
    <div v-color:[borderstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
    <div v-color:[backgroundstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
    <button @click="changeColor">切换颜色</button>
  </div>
 Vue.directive('color', {
    bind: function (el, binding, vnode) {
      var s = binding.arg
      el.style[s] = binding.value
    },
    update(el, binding) {
      var s = binding.arg
      el.style[s] = binding.value
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {
        color: 'red',
        colorstyle: 'color',
        borderstyle: 'border-color',
        backgroundstyle: 'background-color',
      }
    },
    methods: {
      changeColor() {
        this.color = '#' + Math.floor( Math.random() * 0xffffff ).toString(16);
      }
    }
  })

8.4 渲染函数

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。

假设我们要生成一些带锚点的标题,标题的尺寸和锚点的地址需要通过属性传递,最终生成如下格式的 DOM 结构:

<h1>
  <a href="#hello-world">
    Hello world!
  </a>
</h1>
 <div id="app">
    <anchored-heading :level="1" href="#hello-wold">Hello world!</anchored-heading>
    <anchored-heading :level="2" href="#hello-wold">Hello world!</anchored-heading>
    <anchored-heading :level="3" href="#hello-wold">Hello world!</anchored-heading>
  </div>
<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <a :href="href">
      <slot></slot>
    </a>
  </h1>
  <h2 v-else-if="level === 2">
    <a :href="href">
      <slot></slot>
    </a>
  </h2>
  <h3 v-else-if="level === 3">
    <a :href="href">
      <slot></slot>
    </a>
  </h3>
  <h4 v-else-if="level === 4">
   <a :href="href">
      <slot></slot>
    </a>
  </h4>
  <h5 v-else-if="level === 5">
    <a :href="href">
      <slot></slot>
    </a>
  </h5>
  <h6 v-else-if="level === 6">
    <a :href="href">
      <slot></slot>
    </a>
  </h6>
</script>
Vue.component('anchored-heading', {
    template: '#anchored-heading-template',
    props: {
      level: {
        type: Number,
        required: true
      },
      href: {
        type: String,
        default: ''
      }
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
  })

上述代码虽然实现了功能,但是代码冗长,而且在每一个级别的标题中重复书写了 <a :href="href"><slot></slot></a>

 <div id="app">
    <anchored-heading :level="1" href="#hello-wold">Hello world!</anchored-heading>
    <anchored-heading :level="2" href="#hello-wold">Hello world!</anchored-heading>
    <anchored-heading :level="3" href="#hello-wold">Hello world!</anchored-heading>
  </div>
  Vue.component('anchored-heading', {
    render: function (createElement) {
      return createElement(
        'h' + this.level,   // 标签名称
        
        [createElement('a', {
          attrs: {
            href: this.href
          }
        },this.$slots.default)]
        
      )
    },
    props: {
      level: {
        type: Number,
        required: true
      },
      href: {
        type: String,
        default: ''
      }
    }
  })
  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
  })

8.4.1 虚拟DOM

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。

return createElement('h1', this.blogTitle)

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为 “虚拟节点 (virtual node)”,也常简写它为 “VNode”。“虚拟 DOM” 是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

8.4.2 createElement 参数

createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中属性对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

8.4.3 深入数据对象

有一点要注意:正如 v-bind:class 和 v-bind:style 在模板语法中会被特别对待一样,它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML attribute,也允许绑定如 innerHTML 这样的 DOM 属性 (这会覆盖 v-html 指令)。

{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML attribute
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 属性内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层属性
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}
  • 首先,我们通过 createElement 函数创建 h 标签;
  • 它的子集是 createElement 函数创建的 a 标签;
  • a 标签的子集是通过 this.$slots.default 获取的默认插槽。

9 插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制,一般有下面几种:
(1)添加全局方法或者属性。如: vue-custom-element
(2)添加全局资源:指令 / 过滤器 / 过渡等。如 vue-touch
(3)通过全局混入来添加一些组件选项。如 vue-router
(4)添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
(5)一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

Vue 插件是对 Vue 全局功能的扩展,他可以给 Vue 添加全局方法、属性、组件、过滤器、指令等等。

9.1 使用插件

通过全局方法 Vue.use () 使用插件。它需要在你调用 new Vue () 启动应用之前完成:

Vue.use(MyPlugin)

new Vue({
  // ...组件选项
})

也可以传入一个可选的选项对象:

Vue.use(MyPlugin, { someOption: true })

Vue.use 会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。
Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use ()。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use ():

// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时
var Vue = require('vue')
var VueRouter = require('vue-router')

// 不要忘了调用此方法
Vue.use(VueRouter)

9.2 开发插件

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

const MyPlugin = {}

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

接下来,我们写一个具体的插件示例:

<div id="app">
    <my-button>default</my-button>
    <div class="marigin"></div>
    <my-button type="default">default</my-button>
    <div class="marigin"></div>
    <my-button type="primary">primary</my-button>
    <div class="marigin"></div>
    <my-button type="warning">warning</my-button>
    <div class="marigin"></div>
    <my-button type="success">success</my-button>
  </div>
<style>
  .marigin{
    margin-top: 10px;
  }
  .button{
    width: 100%;
    height: 34px;
    line-height: 32px;
    outline: none;
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    cursor: pointer;
    
  }
  .button-default {
    background-color: #ffffff;
    color: #333333;
    border: 1px solid #ccc;
  }
  .button-primary {
    background-color: #39f;
    color: #ffffff;
    border: 1px solid #39f;
  }
  .button-warning {
    background-color: #f90;
    color: #ffffff;
    border: 1px solid #f90;
  }
  .button-success {
    background-color: #0c6;
    color: #ffffff;
    border: 1px solid #0c6;
  }
</style>
  // 定义 MyPlugin
  const MyPlugin = {}
  MyPlugin.install = function(Vue, options) {
    Vue.component('MyButton',{
      template: '<button :class="btnClass"><slot/></button>',
      props: {
        type: {
          type: String,
          default: 'default'
        }
      },
      computed: {
        btnClass() {
          return ["button",`button-${this.type}`]
        }
      }
    })
  }
  // 使用插件 MyPlugin
  Vue.use(MyPlugin)

  var vm = new Vue({
    el: '#app',
    data() {
    	return {}
    }
  })

10、Vue.nextTick 的原理和用途

10.1 原理

异步说明: Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
事件循环说明:简单来说,Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

//改变数据
vm.message = 'changed'
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新。
console.log(vm.$el.textContent)//并不会得到changed

//这样可以,nextTick里面的代码会在DOM更新后执行。
Vue.nextTick(()=>{
	console.log(vm.$el.textContent) //可以得到changed
})

在这里插入图片描述

10.2 Vue.nextTick的机制

10.2.1 为什么要用Vue.nextTick()

JS的运行机制:JS是单线程的,它是基于事件循环的。
(1)所有的同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。
(3)当执行栈中的所有同步任务执行完后,就会读取任务队列。那些对应的异步任务,会结束等待状态,进入执行栈。
(4)主线程不断重复第三步。

这里主线程执行的过程就是一个tick,而所有的异步结果都是通过任务队列来调度的。Event Loop分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入下一个tick,并在两个tick之间进行UI渲染。

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据的变化,并缓存在同一个事件循环中,等待同一个数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保可以得到更新后的DOM,所以设置了Vue.nextTick()方法。

10.2.2 什么是Vue.nextTick()

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM—官方文档。

10.3 应用场景

需要在视图更新之后,基于新的视图进行操作。

10.3.1 created、mounted

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

10.3.2 v-show获取焦点

点击按钮显示原本以 v-show = false 隐藏起来的输入框,并获取焦点。

showsou(){
  this.showit = true //修改 v-show
  document.getElementById("keywords").focus()  //在第一个 tick 里,获取不到输入框,自然也获取不到焦点
}

修改为:

showsou(){
  this.showit = true
  this.$nextTick(function () {
    // DOM 更新了
    document.getElementById("keywords").focus()
  })
}

10.3.3 点击获取元素宽度

<div id="app">
    <p ref="myWidth" v-if="showMe">{{ message }}</p>
    <button @click="getMyWidth">获取p元素宽度</button>
</div>
getMyWidth() {
    this.showMe = true;
    //this.message = this.$refs.myWidth.offsetWidth;
    //报错 TypeError: this.$refs.myWidth is undefined
    this.$nextTick(()=>{
        //dom元素更新后执行,此时能拿到p元素的属性
        this.message = this.$refs.myWidth.offsetWidth;
  })
}

11、ref的用法

11.1 ref的三种用法

  • 如果在普通的DOM元素上使用,引用指向的就是DOM元素。
  • 如果用在子组件上,引用就是指向组件实例,可以使用组件所有方法。
  • 利用 v-for 和 ref, 获取的是一组数据或dom节点
<!--vm.$ref.p将会是DOM节点-->
<p ref='p'>hello</p>

<!--vm.$ref.child将会是子组件实例-->
<child-component ref='child'></child-component>
    <ul>
      <li :key="index" v-for="(item, index) in people" ref="refContent">{{ item }}</li>
    </ul>
export default {
  data: function () {
    return {
      people: ['三姑', '四婶', '五叔', '六姨', '七舅老爷']
    }
  },
  created() {
    this.$nextTick(() => {
      console.log(this.$refs.refContent)
    })
  }
}

在这里插入图片描述

11.2 注意事项

  • ref需要在dom渲染完成后应用,在使用时确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
  • 如果ref是循环出来的,有多个重名,那么ref值会是一个数组,此时要拿到单个ref只需要循环就可以。

12、基于vue 动态加载图片src的解决方法

12.1 vue-cli中的assets和static的区别。

使用vue-cli构建的项目有两个存放静态文件的目录,分别是 src/assetsstatic/。那么二者有什么区别呢?

首先,我们需要理解Webpack是如何处理静态资源的。在*.vue组件中,所有的templatecss 都会被vue-html-loadercss-loader所解析以寻找资源路径。比如<img src="./logo.png">background: url(./logo.png)./logo.png是一个相对资源路径,其被Webpack解析成模块依赖。

由于logo.png不是JavaScript,当被视为模块依赖时,需要使用 url-loaderfile-loader对其进行处理。

static/ 下的文件都不会被Webpack处理,使用相同的文件名,直接拷贝到最终路径。我们必须使用绝对路径来引用这些文件,在 config/index.js进行配置,假设进行如下配置:

// Webpack配置文件
// config/index.js
module.exports = {
  // ...
  build: {
    assetsRoot: path.resolve(__dirname, '../dist'), //  构建输出目录
    assetsSubDirectory: 'static', // 资源子目录 除了index.html,其余的js img css都分在这里
    assetsPublicPath: '/' // 项目目录
  }
  // ...
}

如上配置,我们需要使用绝对路径/static/[filename] 来引用 static/目录下的文件。

简单总结就是:

  • src/assets里面的文件在构建的时候会被Webpack打包到最终输出文件,所以src/assets适合放本项目的资源文件。
  • static/里面的文件在构建的时候不会被Webpack处理,直接拷贝到最终路径并引用,所以static/适合放一些第三方类库文件。

12.2 用js动态加载assets或者本文件的图片出现404的状态码

<li v-for="(item,index) in images" :key="index">
<img :src="item.src"></li>
//js部分
data(){
 return {
 	images:[{src:'./1.png'},{./2.png}]
 }
}

跑起来发现图片不显示,错误码为404。

原因:在webpack中会将src/assets目录下面的图片来当做模块来用,因为是动态加载的,所以url-loader将无法解析图片地址,然后npm run dev 或者npm run build之后导致路径没有被加工【被webpack解析到的路径都会被解析为/static/img/[filename].png或者/public/img/[filename].png,完整地址为localhost:8080/static/img/[filename].png或者localhost:8080/public/img/[filename].png

解决办法:

  • (1)将图片作为模块加载进去,比如images:[{src:require(‘./1.png')},{src:require(‘./2.png')}]这样webpack就能将其解析。
  • (2)将图片放到static目录下,但必须写成绝对路径如images:[{src:”/static/1.png”},{src:”/static/2.png”}]这样图片也会显示出来,当然你也可以通过在webpack.base.config.js定义来缩短路径的书写长度。

如果图片太多可以简化操作:
第一步:在static里面新建一个json文件
在这里插入图片描述
第二步:填写json文件,如图
在这里插入图片描述
第三步:将json引入相应的vue文件中,解析引用就行了。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值