Vue知识框架

92 阅读11分钟

思维导图

前言

此知识框架为本人独立完成,难免有疏漏之处,请多多包涵。若对你有帮助,不妨给我点个免费的赞。若文章无法正确显示,备用文章链接Mick的自学笔记,我会不断更新文章内容,学无止境。

妹子图

总览

Vue

Vue是什么

Vue是什么

Vue的引用

Vue的引入

MVVM

MVVM

模板语法

模板语法

指令

指令

Vue实例

Vue实例

条件渲染

条件渲染

class与style的绑定

class与style的绑定

事件

事件

链表渲染

列表渲染

生命周期

生命周期

表单

表单

组件

组件

Vue-router

Vue-router

vue-cli

vue-cli

Vuex

Vuex

差不多就这些,还有一些没展示出来的,点击总览里的脚注进行跳转

总览

思维导图全部1

点击脚注链接跳转

Vue基础语法

1.前言

概述:Vue是一款前端渐进式框架,可以提高前端开发效率,模块化开发。

特点

Vue通过MVVM模式,能够实现视图与模型的双向绑定。

数据驱动:简单来说,就是数据变化的时候, 页面会自动刷新, 页面变化的时候,数据也会自动变化.

Snipaste_2022-07-27_15-13-52

Vue.js的三种安装方式

Vue.js三种安装方式

Vue的导入

概述:Vue是一个类似于Jquery的一个JS框架,所以,如果想使用Vue,则在当前页面导入Vue.js文件即可。 语法

<!-- 在线导入 -->
<!-- 开发环境版本,包含了用帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

<!-- 本地导入 -->
<script src="node_modules/vue/dist/vue.js"></script>

案例:

<div id="app">
    <h1>用户名:<input type="text" v-model="name"/></h1> <br/>
    <h1>您输入的用户名是: {{name}}</h1>
</div>

<script type="text/javascript">
    //创建一个Vue对象
    var app = new Vue({
        //指定,该对象代表<div id="app">,也就是说,这个div中的所有内容,都被当前的app对象管理
        el: "#app",
        //定义vue中的数据
        data: {
            name: ""
        }
    });
</script>

2.模板语法

文本

使用Vue自带的插值语法进行书写

{{}}

注意:

我们可以在双括号里填写状态(vue实例里data里的值),比如在Vue实例中,有个状态叫做msg,我们可以这样用它:{{msg}}

识别html标签

使用v-html指令,可以非常容易让Vue知道你在这个地方是要渲染一个DOM节点的。

<div v-html="html"></div>

案例

<body>
  <div class="box">
    {{username}}
    <div v-html="html"></div>
  </div>
</body>

</html>
<script src="../js/vue.js"></script>
<script>
  var app = new Vue({
    el: ".box",
    data() {
      return {
        username: "xm",
        html: '<h1>我是h1标签</h1>'
      }
    }
  })
</script>

Snipaste_2022-07-28_08-51-33

使用JavaScript表达式

表达式:加减乘除,三元运算符,与或非

vue提供了完全的javaScript的数据支持

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{age+1}}:{{age>5?"成年":"未成年"}}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式

注意:

在插值表达式中,建议不要放太复杂的表达式,因为插值语法本来就是渲染数据的。数据处理可以放到计算属性里去做。

3.内置指令

1.v-model表单绑定

实现双向绑定的本质:

  • 使用v-model的作用就是实现双向数据绑定2

  • v-model可以使用input时间和:value来进行替代。

  <body>
    <div id="app">
  
      <input type="text" v-model="message">
      <h2>{{message}}</h2>
  
      <input type="text" :value="message" @input="valueChange">
    </div>
    <script>
      let app = new Vue({
        el: '#app',
        data: {
          message: '你好呀!'
        },
        methods: {
          valueChange(event) {
            this.message = event.target.value
          }
        }
      })
  
    </script>
  </body>
  

上面的代码里,event事件用来获取输入框输入的值。

监听input方法,使得一旦有输入数据,便会调用valueChange方法,在valueChange方法中通过event事件得到值,完成对vue实例中值的修改。

Snipaste_2022-05-22_19-21-48

2.v-on事件绑定

使用v-on实现事件绑定,简写v-on:=@

+<button @click="handleChange()">change</button>


<script>
  var app = new Vue({
    el: ".box",
    data() {
      return {
        username: "xm",
        age: 19,
        html: '<h1>我是h1标签</h1>'
      }
    },
+    methods: {
+      handleChange() {
+        console.log(this);
+        console.log(app._data.username);
+        this._data.age = 18
+      }
+    }
+  })
</script>

3.v-bind

单向数据绑定

  • 缩写:

  • 预期any (with argument) | Object (without argument)

  • 参数attrOrProp (optional)

  • 修饰符

    • .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。(差别在哪里?)
    • .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。(从 2.1.0 开始支持)
    • .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。
  • 用法

    动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

    在绑定 classstyle attribute 时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。

    在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型。

    没有参数时,可以绑定到一个包含键值对的对象。注意此时 classstyle 绑定不支持数组和对象。

  • 示例

    <!-- 绑定一个 attribute -->
    <img v-bind:src="imageSrc">
    
    <!-- 动态 attribute 名 (2.6.0+) -->
    <button v-bind:[key]="value"></button>
    
    <!-- 缩写 -->
    <img :src="imageSrc">
    
    <!-- 动态 attribute 名缩写 (2.6.0+) -->
    <button :[key]="value"></button>
    
    <!-- 内联字符串拼接 -->
    <img :src="'/path/to/images/' + fileName">
    
    <!-- class 绑定 -->
    <div :class="{ red: isRed }"></div>
    <div :class="[classA, classB]"></div>
    <div :class="[classA, { classB: isB, classC: isC }]"></div>
    
    <!-- style 绑定 -->
    <div :style="{ fontSize: size + 'px' }"></div>
    <div :style="[styleObjectA, styleObjectB]"></div>
    
    <!-- 绑定一个全是 attribute 的对象 -->
    <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
    
    <!-- 通过 prop 修饰符绑定 DOM attribute -->
    <div v-bind:text-content.prop="text"></div>
    
    <!-- prop 绑定。“prop”必须在 my-component 中声明。-->
    <my-component :prop="someThing"></my-component>
    
    <!-- 通过 $props 将父组件的 props 一起传给子组件 -->
    <child-component v-bind="$props"></child-component>
    
    <!-- XLink -->
    <svg><a :xlink:special="foo"></a></svg>
    

    .camel 修饰符允许在使用 DOM 模板时将 v-bind property 名称驼峰化,例如 SVG 的 viewBox property:

    <svg :view-box.camel="viewBox"></svg>
    

    在使用字符串模板或通过 vue-loader/vueify 编译时,无需使用 .camel

4.v-for列表渲染

  • 预期Array | Object | number | string | Iterable (2.6 新增)

  • 用法

    基于源数据多次渲染元素或模板块。此指令之值,必须使用特定语法 alias in expression,为当前遍历的元素提供别名:

    <div v-for="item in items">
      {{ item.text }}
    </div>
    

    另外也可以为数组索引指定别名 (或者用于对象的键):

    <div v-for="(item, index) in items"></div>
    <div v-for="(val, key) in object"></div>
    <div v-for="(val, name, index) in object"></div>
    

    v-for 的默认行为会尝试原地修改元素而不是移动它们。要强制其重新排序元素,你需要用特殊 attribute key 来提供一个排序提示:

    <div v-for="item in items" :key="item.id">
      {{ item.text }}
    </div>
    

    从 2.6 起,v-for 也可以在实现了可迭代协议的值上使用,包括原生的 MapSet。不过应该注意的是 Vue 2.x 目前并不支持可响应的 MapSet 值,所以无法自动探测变更。

    当和 v-if 一起使用时,v-for 的优先级比 v-if 更高。详见列表渲染教程

in,of

数组:item(元素),index(下标)

对象:item(属性值),name(属性名),index(下标)

  <div class="app">
    <ul>
      <li v-for="(item,index) of list">__name:{{item.name}}__age:{{item.age}}</li>
      <li v-for="(item,username,index) of admin">{{item}}{{username}}{{index}}</li>
    </ul>
  </div>
  
  var app = new Vue({
    el: ".app",
    data: {
      list: [
        { name: "xm", age: 17 },
        { name: "wm", age: 17 },
        { name: "vm", age: 17 }
      ],
      admin: {
        username: 'bab',
        age: 19,
        thumb: '图片',
        office: '市长'
      }
    },
    methods: {

    }
  }) 

Snipaste_2022-07-29_11-44-40

维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute

:question: 为啥设置key值?

:key:给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排列现有元素.

:question:设置什么值为key值?

:key:理想型item.id; item

:question:虚拟DOM

:key:提升性能,在数据更新前,生成新的虚拟DOM,新旧DOM进行对比,形成最新补丁,以最小的代价,对DOM进行修改.

Snipaste_2022-07-29_14-49-34

5.v-else-if

  • 类型any

  • 限制:前一兄弟元素必须有 v-ifv-else-if

  • 用法

    表示 v-if 的“else if 块”。可以链式调用。

    <div v-if="type === 'A'">
      A
    </div>
    <div v-else-if="type === 'B'">
      B
    </div>
    <div v-else-if="type === 'C'">
      C
    </div>
    <div v-else>
      Not A/B/C
    </div>
    
  • 参考条件渲染 - v-else-if

6.v-if

  • 预期boolean

  • 用法

    根据表达式的值的 truthiness 来有条件地渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。如果元素是 <template>,将提出它的内容作为条件块。

    当条件变化时该指令触发过渡效果。

    当和 v-if 一起使用时,v-for 的优先级比 v-if 更高。详见列表渲染教程

  • 参考条件渲染 - v-if

7.v-show

  • 预期boolean

  • 用法

    根据表达式之真假值,切换元素的 display CSS property。

    当条件变化时该指令触发过渡效果。

v-if vs v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

8.v-text

  • 预期string

  • 详细

    更新元素的 textContent。如果要更新部分的 textContent,需要使用 {{ Mustache }} 插值。

  • 示例

    <span v-text="msg"></span>
    <!-- 和下面的一样 -->
    <span>{{msg}}</span>
    
  • 参考数据绑定语法 - 插值

9.v-html

  • 预期string

  • 详细

    更新元素的 innerHTML注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译。如果试图使用 v-html 组合模板,可以重新考虑是否通过使用组件来替代。

    在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用 v-html永不用在用户提交的内容上。

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

  • 示例

    <div v-html="html"></div>
    
  • 参考数据绑定语法 - 插值

10.v-slot

  • 缩写#

  • 预期:可放置在函数参数位置的 JavaScript 表达式 (在支持的环境下可使用解构)。可选,即只需要在为插槽传入 prop 的时候使用。

  • 参数:插槽名 (可选,默认值是 default)

  • 限用于

    • <template>
    • 组件 (对于一个单独的带 prop 的默认插槽)
  • 用法

    提供具名插槽或需要接收 prop 的插槽。

  • 示例

    <!-- 具名插槽 -->
    <base-layout>
      <template v-slot:header>
        Header content
      </template>
    
      Default slot content
    
      <template v-slot:footer>
        Footer content
      </template>
    </base-layout>
    
    <!-- 接收 prop 的具名插槽 -->
    <infinite-scroll>
      <template v-slot:item="slotProps">
        <div class="item">
          {{ slotProps.item.text }}
        </div>
      </template>
    </infinite-scroll>
    
    <!-- 接收 prop 的默认插槽,使用了解构 -->
    <mouse-position v-slot="{ x, y }">
      Mouse position: {{ x }}, {{ y }}
    </mouse-position>
    

    更多细节请查阅以下链接。

  • 参考

总结:

  • 作用:提升组件的复用性,扩展组件的能力。

  • 单个插槽:

  • 具名插槽:

  1. 案例
<body>
  <div class="app">
    <navbar>
      <div>我是从插槽插进来的元素一</div>
      <div>我是从插槽插进来的元素二</div>
    </navbar>

    <navbar>
      <li>我是再次调用时插进来的元素一</li>
    </navbar>
  </div>
</body>


</html>

<template id="children">
  <div>
    <div>{{this.name}}</div>
    <slot></slot>
    <slot></slot>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  Vue.component("navbar", {
    template: '#children',
    data() {
      return {
        name: "------"
      }
    }

  })
  new Vue({
    el: '.app',
    data: {
    },
    methods: {
    }
  })
</script>

Snipaste_2022-08-02_11-20-51

我们来看看输出结果是什么样的吧!

Snipaste_2022-08-02_11-22-27

这时,就感觉不太容易控制输出了,所以就需要具名插槽了。

我们可以在标签内部添加name属性,给插槽起个名字。当显示元素想要显示时,就需要在标签里添加属性slot='[具名插槽的name]',这样就可以在规定位置进行展示了。

  <div class="app">
    <navbar>
      <div slot="a">我是从插槽插进来的元素一</div>
      <div>我是从插槽插进来的元素二</div>
    </navbar>

    <navbar>
      <li>我是再次调用时插进来的元素一</li>
    </navbar>
  </div>

<template id="children">
  <div>
    <div>{{this.name}}</div>
    <slot name="a"></slot>
    <slot></slot>
  </div>
</template>

Snipaste_2022-08-02_11-29-30

4.Vue实例

data

data中变量之间不能相互访问,this指向window

两种写法

根组件
var vm = new Vue({
  el: "#app",
  data: {
    msg: "hello",
    username: "非常好"
  }
})
子组件
data() {
  return {
    username: "xm",
    age: 19,
    html: '<h1>我是h1标签</h1>',
    bgclor: 'red',
    url: '',
    list: ['aaa', 'bbb', 'ccc'],
    isCreate: true,
    isShow: false
  }
},

注意:要区分根组件和子组件data的区别!

根组件是使用对象的写法 子组件是使用函数的写法

为啥子组件和根组件写法不同呢?

函数在调用时,都会在自己的栈空间新建新的变量,这样各个组件之间不会相互影响。父子组件之间的数据会相互影响。

props

主要是用来保存父组件给子组件传的值。

可以是数组或对象类型,用于接受来自父组件的数据。

两种写法

数组
props: [
  "mynav",
  "mybtn"
],
对象
props: {
  mynav: {
    type: String,
    required: true
  },
  mybtn: {
    type: Boolean,
    default: false
  }
},

总结:

两种写法,看情况选择。使用对象写法可以设置默认值,规定类型,设置是否必须等。要比使用数组更灵活。

methods

主要负责处理事件

  • 触发事件的时候去执行
  • this.变量名 访问data中的数据
  • this -> Vue实例
methods: {
  handleAdd() {
    this.list.push(this.mytext);
    this.mytext = '';
  },
  handleDel(index) {
    this.list.splice(index, 1)
  }
}

watch

  • 侦听器
  • 监听data中数据的变化,监听路由的变化
  • 一个数据影响到多个数据的变化
  • 没有缓存,支持异步操作

普通监听

data() {
  return {
    age: 20,
  };
},
   
watch: {
  age(newval, oldval) {
    console.log(newval);
  },
},

这是监听age变量的例子,控制台输出新值

深度监听

使用于监听对象里面的值。

data() {
  return {
    obj: {
      username: "admin",
    },
  };
},

watch:{
   obj: {
   handler(newval, oldval) {
     console.log(newval);
   },
   deep: true,
   //深度监听
  }, 
}

这里使用deep:true实现了深度监听

另外一种书写方式:

data() {
  return {
    obj: {
      username: "admin",
    },
  };
},

watch: {
  "obj.username"(newval) {
    console.log(newval);
  },
},

computed

  • 有缓存的,不支持异步操作
  • 计算多个属性得到的结果会缓存,方便下次使用
  • 多个数据影响到一个数据变化,必须有一个返回值
<div class="app">
  {{reversedMessage }}
</div>   

data: {
    message: "message"
  },
 computed: {
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
}

这是官方文档给的反转字符串的例子

methods与computed的区别

目的执行次数return是否必须是否依赖缓存
计算属性计算结果依赖缓存,计算过的属性不再计算,执行一次必须依赖
函数事件处理调用几次,执行几次,没有缓存不是必须不依赖

5.事件处理

vue中使用v-on绑定事件,事件在vue实例中的methods属性中定义。同时可以有一个参数,和js类似也叫事件对象。

1.事件的基本使用

  1. 使用v-on: xxx或者 @xxx绑定事件,其中xxx是时间名。
  2. 当事件的回调需要配置在methods对象中,最终会在Vue实例上。
  3. methods中配置的函数,不要用箭头函数,否则this就不是Vue实例了。
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是Vue实例或组件实例对象。
  5. @click="demo" 和 @click="demo($event)"效果一致,但后者可以传参。

 <button @click="showInfo1">点我提示信息1</button>
 <button @click="showInfo2($event,66)">点我提示信息2</button>

new Vue({
    el: "#app",
    data: {
      name: 'xx'
    },
    methods: {
      showInfo1(event) {
        alert('showInfo1')
        console.log(event);
      },
      showInfo2(event, number) {
        console.log(event);
        console.log(number);
      }
    }
  })

showInfo2就可以把事件对象和参数进行输出。

内联处理器方法

加上括号()是为了传参

<button @click="handleClick1('aaa','bbb')">内联处理器方法,加上括号()是为了传参</button>

--vue--
      handleClick1(a, b) {
        this.count++;
        console.log(a, b);
      },

即传参又事件对象

<button @click="handleClick2($event,'aaa','bbb')">即传参又事件对象</button>

--vue--
      handleClick2(evt, a, b) {
        console.log(evt, a, b);
      }

即想要传参,又想要事件对象,形参$event是必须的,而且是不可改动的

2.事件修饰符

事件流

事件发生时,事件在DOM元素之间有一种流向

事件流的分类

  1. 冒泡型事件流:从最明确到最不明确的元素 on+type;元素.addEventlistener(type,fn,false)

  2. 捕获型事件流:从最不明确到最明确的元素 元素.addEventlistener(type,fn,true)

事件执行的三个阶段

  1. 事件捕获阶段

  2. 目标阶段

  3. 事件冒泡阶段


:question: :在Vue中如何阻止默认行为呢?

:key: :如果你记得js,里面应该有个事件对象,给事件对象挂载preventDefault()就像这样。

      showInfo1(e) {
        e.preventDefault()
        alert('同学你好')
      },

:key::其实还有另外一种方法,就像这样。

<a href="www.liyublogs.top" @click.prevent="showInfo1">跳转链接</a>

:question: :在Vue中如何阻止冒泡呢?

:key::如果你记得js,里面应该有个事件对象,给事件对象挂载stopPropagation()就像这样。

      
<div class="demo1" @click="showInfo1">
      <button @click="showInfo1">阻止事件冒泡</button>
</div>

showInfo1(e) {
  e.stopPropagation()
  alert('同学你好')
}

在这种情况下,若不加e.stopPropagation(),点击button后事件showInfo1会从button执行后冒泡到div再执行一次。

:key:其实还有另外一种方法,就像这样。

<div class="demo1" @click="showInfo1">
      <button @click.stop="showInfo1">阻止事件冒泡</button>
</div>

showInfo1(e) {
  alert('同学你好')
}

:key:如何切换成捕获型事件流

像这样

<div class="box1" @click="showmsg(1)">
    box1
    <div class="box2" @click="showmsg(2)">
      box2
    </div>
</div>

showmsg(msg) {
    console.log(msg);
}

这种就是冒泡,点击box2,执行输出:2,1

:key: 同样,使用.capture,就可以使用事件的捕获模式,就像这样。

<div class="box1" @click.capture="showmsg(1)">
    box1
    <div class="box2" @click="showmsg(2)">
      box2
    </div>
</div>

showmsg(msg) {
    console.log(msg);
}

这种就是捕获,点击box2,执行输出:1,2

@wheel是鼠标滚轮事件,当在页面种使用鼠标滚轮时才会触发,执行顺序是先执行函数,再执行默认事件(页面下滚)

@scroll是下拉条事件,当使用下拉条时,就会触发

vue中的事件修饰符

  1. prevent:阻止默认事件
  2. stop:阻止事件冒泡
  3. once:事件只能触发一次
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作的元素时才触发事件。
  6. passive:事件的默认行为立即执行,无需等待事件的回调执行完毕。

当然,这些修饰符也是可以连着些的,当遇到特殊场合比如既要阻止默认事件,又要阻止冒泡,我们可以给时间添加.stop.prevent.有先后之分!

.self

如果想要点击自身触发,可以给ul加上.self,这样ul里的li只点击自己才会触发,就像这样。

  <div class="app">
    <ul @click.self="handleUl">  这里用了.self
      <li @click="handleLl">点击1</li>
      <li @click.stop="handleLl1">点击stop-阻止冒泡型事件流</li>
      <li @click.self="handleLl2">点击self-只有点击自身才触发</li>
      <li @click.once="handleLl3">点击once-只执行一次</li>
    </ul>
    <a href="http://www.baidu.com" @click.prevent>百度一下</a>

    <input type="text" @keyup.enter="handle">
  </div>




    methods: {
      handleUl(event) {
        console.log("ul");
      },
      handleLl(event) {
        event.stopPropagation()
        console.log("li");
      },
      handleLl1() {
        console.log("点击stop-阻止冒泡型事件流");
      },
      handleLl2() {
        console.log("点击self-只有点击自身才触发");
      },
      handleLl3() {
        console.log("点击once-只执行一次");
      },
      handle(e) {
        console.log(e.code);
      }
    }

给ul添加.self,点击里面的li不会向上冒泡

3.键盘事件

vue给常用的按键都起了别名,比如.enter,这样当你按下对应按键的时候方法才会执行。

<input type="text" placeholder="按下回车提示输入" @keyup="showInfo">


  new Vue({
    el: "#app",
    data: {
      name: 'xxx'
    },
    methods: {
      showInfo(e) {
        if (e.keyCode != 13) return
        console.log(e.target.value);
      }
    }

  })

这里 13 是enter的keycode

当然,你也可以使用vue的别名去触发。

<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">


  new Vue({
    el: "#app",
    data: {
      name: 'xxx'
    },
    methods: {
      showInfo(e) {
        // if (e.keyCode != 13) return
        console.log(e.target.value);
      }
    }

  })

这样,只有当你按下enter键时才会触发showInfo函数

  1. Vue中,类似的按键别名还有很多。
别名对应按键
enter回车
delete删除
esc退出
space空格
tab(特殊配合keydown使用)换行
up
down
left
right
  1. vue中未提供别名的按钮,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

键盘上的每个键,都是有其自己的名字和编码的,我们使用e.keye.keycode将其输出,注意:e是事件对象

比如:Ctrl键的名字就是Control 它的keycode是 17。

同时切换大小写的按键也比较特殊,它是.caps-lock

<input type="text" placeholder="按下回车提示输入" @keyup.caps-lock="showInfo">

对于一些在浏览器中本来就起作用的按键,比如tab键,keyup就不太好用了,反而切换成keydown才比较好用.

<input type="text" placeholder="按下回车提示输入" @keydown.tab="showInfo">
  1. 系统修饰键(用法特殊):ctrl,alt,shift,meta这四个按键和tab一样配合keydown才能正常触发
  • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才触发。
  • 配合keydown使用:正常触发事件。
  1. 也可以使用keyCode去指定具体的按键(不推荐)

比如我们知道enter的键码是13,如果是用键码可以这样用.

<input type="text" placeholder="按下回车提示输入" @keydown.13="showInfo">
  1. Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<input type="text" placeholder="按下回车提示输入" @keyup.kebab-case="showInfo">

<script>
  Vue.config.productionTip = false;
    
  ----注意这里---
  Vue.config.keyCodes = {
    "kebab-case": 13,
  };

  new Vue({
    el: "#app",
    data: {
      name: 'xxx'
    },
    methods: {
      showInfo(e) {
        // if (e.keyCode != 13) return
        console.log(e.target.value);
      }
    }

  })
</script>

这样,按下回车键就会有提示信息.

按键效果也是可以叠加的,比如.ctrl.y就是ctrl和y键一起按下才会触发.

6.组件

复杂问题时,将复杂问题分解成可以处理的小问题。从而可以达到复用效果,简化开发。

  1. 它提供了一个抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  2. 任何的应用都会被抽象成一棵组件树

组件化开发

  • 如果我们将一个页面中的所有逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理和扩展。
  • 我们将一个小页面分隔成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么这个页面的维护就会变得非常容易

组件使用三步骤

  1. 调用Vue.extend()方法
  2. 调用Vue.compont()注册组件
  3. 在Vue实例的作用范围呢使用组件

Snipaste_2022-05-10_11-10-07

代码

<body>
<div id="app">
<!--    3.使用组件-->
<my-cpon></my-cpon>
</div>
<script src="../js/vue.js"></script>
<script>
    //1. 创建组件构造器
    const myComponent = Vue.extend({
        template:`
        <div>
            <h2>我是组件标题</h2>
            <p>我是组件中的一个内容</p>
        </div>
        `
    })
    //2.注册组件,并定义组件的名称
    Vue.component('my-cpon',myComponent)

    let app = new Vue({
        el:'#app',
    })
</script>
</body>

  • 运行结果

Snipaste_2022-05-10_11-34-33

这种方法写的很少了,现在已经被新的语法糖所替代

注册组件步骤解析

  1. vue.extend():
    • 调用Vue.extend()创建的是一个组件构造器
    • 通常在创建组件构造器是,传入的tampleate代表我们自己定义的组件的模板
    • 该模板就是在使用到组件的地方,要显示的HTML代码
    • 事实上,这种写法在Vue2.x文档中已经看不到了,它会直接用我们下面讲到的语法糖,这种方式是学习后面方式的基础。
  2. Vue.component():
    • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给他起一个组件的标签名称。
    • 所以需要传递两个参数:1. 注册组件的标签名。2.组件构造器
  3. 组件必须挂载到某个Vue实例下,否则他不会生效

全局组件

  • 全局组件注册后,可以在任何一个vue实例中使用,全局组件的注册参考上面的代码。

当我们通过调用Vue.component()注册组件时,组件的注册是全局的 这意味着该组件可以在任意Vue实例下使用

局部组件

如果我们注册的组件是挂载到某个实例中,那么就是一个局部组件。

只能在注册的vue实例中使用

注册方法:

<body>


  <script src="../js/vue.js"></script>

  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>


  <div id="app2">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>

  <script>
    //1. 创建组件构造器
    const conC = Vue.extend({
      template: `
        <div>
          <h2>我是标题</h2>
          <p>我是内容,哈哈哈哈</p>
        </div>
      `
    })

    //2. 注册组件(下面方法注册的是全局组件,意味着可以在多个vue实例中使用)
    //Vue.component('cpn', conC)
    
    //疑问:怎么注册才是局部组件呢?
    //在vue实例中注册
    
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      },
      components: {
        //cpn使用组件时的标签名
        cpn: conC
      }
    })
    
    const ap2 = new Vue({
      el: '#app2',
    })
  </script>
</body>

注册局部组件后,在app2实例里的组件就没有渲染出来

父组件和子组件的区分

  • 组件和组件之间存在层级关系
  • 而其中一种非常重要的关系就是父子组件的关系

<body>
  <div id="app">
    <cpn2></cpn2>
    <!-- 根组件找不到,所以不会渲染,除非再去根组件里注册 -->
    <cpn1></cpn1>
  </div>

  <script src="../js/vue.js"></script>
  <script>

    //1. 创建第一个组件
    //子组件
    const cpnC1 = Vue.extend({
      template: `
        <div>
          <h2>我是标题1</h2>
          <p>我是注册的第一个组件</p>
        </div>
      `
    })
    
    //2. 创建第二个组件构造器
    //父组件
    const cpnC2 = Vue.extend({
      template: `
        <div>
          <h2>我是标题2</h2>
          <p>我是注册的第二个组件</p>
          <cpn1></cpn1>
        </div>
      `,
      components: {
        cpn1: cpnC1
      }
    })
    
    //root组件
    const app = new Vue({
      el: '#app',
      data: {
        message: '抗争的小青年'
      },
      components: {
        cpn2: cpnC2
      }
    })

  </script>
</body>

注意: 在上面的案例中,cpnC2是父组件,cpnC1是子组件. cpnC1在cnpC2里注册,并在cpnC2的模板里使用 cpnC2在根组件里注册

Snipaste_2022-05-10_11-34-33

  1. 当程序走到cpn2这个标签时,它会去看组件构造器里的内容,并提前编译好。在cpn2模板中发现cpn1标签后,会在自己的作用域里查找是否注册过cpn1,如果找到,就用模板内容将cpn1标签覆盖。如果没有找到,就会在全局组件里面找,找到后,同样会进行替换。找到后,就整体编译好。
  2. 所以在使用cpn2标签的时候,这个标签里面的内容已经确定好了。

组件的语法糖注册方式

在上面的注册组件的方式,你可能会觉得繁琐

  • vue为了简化这个过程,提供了注册的语法糖。
  • 主要时为了省去调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
 <body>


  <script src="../js/vue.js"></script>
  <div id="app">
    <cpn1></cpn1>
    <cnp2></cnp2>
  </div>
  <script>
    //1. 注册全局组件的语法糖
    Vue.component('cpn1', {
      template: `
      <div>
          <h2>我是标题1</h2>
          <p>我是全局组件的语法糖创建出来的</p>
        </div>
      `
    })
    //2. 注册局部组件的语法糖
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀'
      },
      components: {
        cnp2: {
          template: `
          <div>
          <h2>我是标题2</h2>
          <p>我是局部组件的语法糖创建出来的</p>
        </div>
          `
        }
      }
    })
  </script>
 </body>

组件模板的抽离的写法

虽然语法糖简化了Vue组件的注册过程,但使用tempate模块写法实在时仍然不方便,

解决办法: 将其中的html分离出来写,然后挂载在对应的组件上,必然结构会变得清晰

  • Vue中提供了两种方案来定义HTML模块的内容:
  1. 使用< template >标签
 <body>
  <script src="../js/vue.js"></script>
  <div id="app">
    <cpn></cpn>
  </div>

  <!-- 模板写法,方法一 -->
  <script type="text/x-template" id="cpn">
    <div>
      <h1>我是标签</h1>
      <p>抗争的小青年</p>
    </div>
  </script>

  <script>
    //1. 注册一个全局组件
    Vue.component('cpn', {
      template: '#cpn'
    })

    const app = new Vue({
      el: '#app',
      data: {
        message: '我是抗争的小青年'
      }
    })
  </script>
 </body>

注意: 类型必须是 text/x-template

  1. 使用< template >标签
 <body>
  <script src="../js/vue.js"></script>
  <div id="app">
    <cpn></cpn>
  </div>
  <!-- 模板写法,方法二 -->
  <template id="cpn2">
    <div>
      <h1>我是标签</h1>
      <p>哎哎哎</p>
    </div>
  </template>

  <script>
    //1. 注册一个全局组件
    Vue.component('cpn', {
      template: '#cpn2'
    })

    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀'
      }
    })
  </script>
 </body>
  • < template >标签语法

图片

  • < template >标签 语法

图片

为啥组件data必须是函数

函数在调用时,都会在自己的栈空间新建新的变量,这样各个组件之间不会相互影响。父子组件之间的数据会相互影响。

7.父子组件的通信

在实际开发中,往往一些数据确实需要从上层传递到下层:

  1. 比如开发一个页面中,我们从服务器请求到了许多数据。
  2. 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
  3. 这个时候,并不会让子组件再发送一次网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。

数据传递

  • 通过props向子组件传递数据

  • 通过事件向父组件发送消息

  • 父组件通过属性的方式给子组件传值

  • 子组件使用props接收父组件传递的属性

Snipaste_2022-05-10_11-34-33

注意: Vue实例就是根组件,也叫root组件 !

父传子

父传子

props基本用法

  • 在组件中,使用选项props来声明需要从父级接受到的数据
  • props的值有两种方式:
    • 方式一:字符串数组,数组中的字符串就是传递时的名称.
    • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等.
  1. 方式一传值
 <body>
  <div id="app">
    <cpn v-bind:cmovies="movies" :cmessage="message"></cpn>
  </div>

  <template id="cpn">
    <div>
      <p v-for="item in cmovies">{{item}}</p>
      <h2>{{cmessage}}</h2>
    </div>
  </template>

  <script>
+    //父传子:props
+    const cpn = {
+      template: '#cpn',
+      props: ['cmovies', 'cmessage'],
+    }

    const app = new Vue({
      el: '#app',
      data: {
        message: '休闲玩家Mick',
        movies: ['Silence', 'Revive', 'Before']
      },
      components: {
        cpn
      }
    })
  </script>
 </body>

注意: cpn组件的注册增强写法

  1. 方式二传值
  • 在前面,我写的props选项是一个数组.除了数组之外还可以使用对象,当需要对props进行类型等验证时,就需要使用对象写法了.
  • 支持的数据类型如下,同时也支持我们自己写的类型
支持的数据类型
String
Number
Boolean
Array
Object
Date
Funcation
Symbol
 <body>
  <div id="app">
    <cpn v-bind:cmovies="movies" :cmessage="message"></cpn>  这里使用v-bind将值绑定
  </div>

  <template id="cpn">
    <div>
      <p v-for="item in cmovies">{{item}}</p>
      <h2>{{cmessage}}</h2>
    </div>
  </template>

  <script>
    //父传子:props
    const cpn = {
      template: '#cpn',
      // props: ['cmovies', 'cmessage'], 
      props: {
        //1. 类型限制
        // cmovies: Arry,
        // cmessage: String,
        // 2. 提供一些默认值,以及必传值
        cmessage: {
          type: String,
          default: '我是默认值',
          requierd: 'true'
        },
        //类型是对象或者是数组时,默认值必须是一个函数。
        cmovies: {
          type: Array,
          default() {
            return []
          }
        },
      }
    }

    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀',
        movies: ['Silence', 'Revive', 'Before']
      },
      components: {
        cpn
      }
    })
  </script>
 </body>

使用对象这种方式更好用,因为你可以给变量进行数据校验设置默认值,要求是否必须等。

Prop的大小写

  1. 如果在调用组件的时候使用了小驼峰命名的属性,那么在props中的命名需要全部小写。
  2. 如果在props中的命名采用小驼峰的方式,那么在调用组件的标签中需要使用其等价的短横线分隔的命名方式来命名属性。
 <body>
  <div id="app">
    <cpn :finfo="info" :child-meassage="message"></cpn>    重点看这里!
  </div>

  <template id="cpn">
    <div>
      <h2>{{finfo}}</h2>
      <h2>{{childMeassage}}</h2>
    </div>
  </template>

  <script>
    const cpn = {
      template: '#cpn',
      props: {
        finfo: {
          type: Object,
          default() {
            return {}
          }
        },
        childMeassage: {
          type: String,
          default() {
            return {}
          }
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        info: {
          name: 'why',
          age: 18,
          height: 1.88
        },
        message: 'aaaaa'
      },
      components: {
        cpn
      }

    })
  </script>
 </body>

总结:在组件里使用的值如果用驼峰命名,那么在绑定时,就需要在将大写字母转换成小写字母并在前面加“-”

子传父(自定义事件)

  • v-on不但可以用来监听DOM事件,也可以用来监听自定义事件。
  • 自定义事件的流程:
  1. 在子组件中,通过$emit()来触发事件
  2. 在父组件中,通过v-on来监听组件事件
<body>
  <!-- 父组件模板 -->
  <div id="app">
    <!-- 父组件监听子组件发过来的事件 -->
    <cpn @itemclick="cpnClick"></cpn>     这里使用v-on来监听自定义事件,并在vue实例中,做出响应。cpnClick是父组件的函数
  </div>

  <!-- 子组件模板 -->
  <template id="cpn">    
    <div>
      <button v-for="item in categories" @click="btnClick(item)"> {{item.name}}</button>  给子组件的按钮添加点击事件“btnClick”这是子组件的方法
    </div>
  </template>

  <script>
    //1. 子组件
    const cpn = {
      template: '#cpn',
      data() {
        return {
          categories: [
            {
              id: '1',
              name: '热门推荐'
            },
            {
              id: '2',
              name: '计生情趣'
            },
            {
              id: '3',
              name: '电脑办公'
            },
            {
              id: '4',
              name: '手机数码'
            },
          ]
        }
      },
      methods: {
        btnClick(item) {
          // 自定义事件,第一个参数是自定义事件的名字,第二个参数是要带的参数
          this.$emit('itemclick', item)
        }
      }

    }

    //2. 父组件
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀'
      },
      components: {
        cpn
      },
      methods: {
        cpnClick(item) {
          //处理事件
          console.log('cpnClick', item);
        }
      }
    })
  </script>
</body>

Snipaste_2022-05-19_21-26-47

父子通信中的子传父-使用v-model实现双向数据绑定

  • 现有需求:通过子组件中的输入框来动态绑定父组件中data中的数据。

    • 代码实现

        1. 父组件使用porps来向子组件传值
        2. 子组件通过自己定义的两个属性(number1,number2)来接受父组件的值(num1,num2)
        3. 通过v-model属性将输入框与子组件的number1和number2来进行绑定
    • 结果

      • 上面功能的实现的确没有问题,但思路有问题,而且在一般情况下,vue是不建议通过这种方式来直接修改父组件中的值的。

      • 代码如下:

      <body>
        <div id="app">
          <cpn :number1="num1" :number2="num2"></cpn>
        </div>
      
        <template id="cpm">
          <div>
            <h2>props:{{number1}}</h2>
            <h2>data:{{dnumber1}}</h2>
            <input type="text" v-model="number1">
           
            <h2>props:{{number2}}</h2>
            <h2>data:{{dnumber2}}</h2>
            <input type="text" v-model="number2">
      
          </div>
        </template>
      
        <script>
          const app = new Vue({
            el: '#app',
            data: {
              num1: 1,
              num2: 0
            },
            components: {
              cpn: {
                template: '#cpm',
                props: {
                  number1: Number,
                  number2: Number
                },
              },
            },
          })
        </script>
      </body>
  • 运行截图

Snipaste_2022-05-20_17-15-07

反思:这样虽然可以实现子组件向组件传值,但这种方法在vue看来是极为不推荐的。

  • 代码改进
    • 代码实现
      • 在上面的基础上,我在子组件中定义两个data属性(dnumber1,dnumber2),用来保存父组件传过来的值
      • 将input绑定的属性从number1改为dnumber1,number2同理
<body>
  <div id="app">
    <cpn :number1="num1" :number2="num2"></cpn>
  </div>

  <template id="cpm">
    <div>
      <!-- 用来查看父子组件中,值的变化情况 -->
      <h2>props:{{number1}}</h2>
      <h2>data:{{dnumber1}}</h2>
      <input type="text" v-model="dnumber1">
     
      <!-- 用来查看父子组件中,值的变化情况 -->
      <h2>props:{{number2}}</h2>
      <h2>data:{{dnumber2}}</h2>
      <input type="text" v-model="dnumber2">
      
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 1,
        num2: 0
      },
      components: {
        cpn: {
          template: '#cpm',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              dnumber1: this.number1,
              dnumber2: this.number2
            }

          }
        },
      },
    })
  </script>
</body>
  • 运行截图

Snipaste_2022-05-20_17-28-25

反思:这样是不报错了,但是父组件中的值没有被修改呀,看来得使用自定义事件来向父组件传值了!

  • 代码改进

    • 自组件使用$emit自定义事件创建一个自定义事件dnumber1change,dnumber2change,并将dnumber1,和dnumber2传递过去。

      • 父组件定义监听函数number1chage,number2change,在这个函数中,将取得的值value传递给父组件data中的值,从而将num1,和num2的值进行修改。
<body>
  <div id="app">
    <cpn :number1="num1" :number2="num2" @dnumber1change="number1chage" @dnumber2change="number2change"></cpn>
  </div>

  <template id="cpm">
    <div>
      <!-- 用来查看父子组件中,值的变化情况 -->
      <h2>props:{{number1}}</h2>
      <h2>data:{{dnumber1}}</h2>
      <input type="text" :value="dnumber1" @input="num1Input">
      <!-- 用来查看父子组件中,值的变化情况 -->
      <h2>props:{{number2}}</h2>
      <h2>data:{{dnumber2}}</h2>
      <input type="text" :value="dnumber2" @input="num2Input">
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 1,
        num2: 0
      },
      methods: {
        number1chage(value) {
          this.num1 = parseInt(value)
        },
        number2change(value) {
          this.num2 = parseInt(value)

        }
      },
      components: {
        cpn: {
          template: '#cpm',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              dnumber1: this.number1,
              dnumber2: this.number2
            }
          },
          methods: {
            num1Input(event) {
              this.dnumber1 = event.target.value;
              // 为了父组件可以修改值,发出一个事件
              this.$emit('dnumber1change', this.dnumber1);
            },
            num2Input(event) {
              this.dnumber2 = event.target.value;
              //  为了父组件可以修改值,发出一个事件
              this.$emit('dnumber2change', this.dnumber2);
            }
          }
        },
      }
    })
  </script>
</body>
      
  • 运行截图

    Snipaste_2022-05-20_18-02-13

总结:v-model的本质

  1. < input type="text" v-model="dnumber1" >

  2. < input type="text" v-bind:value="dnumber1" @input="dnumber1=$event.target.value" >

下面的代码等同于上面的代码, 这也就是需求实现的关键

父子组件的访问方式:$children

需求:有时候我们需要父组件访问子组件。

  • 父组件访问子组件:使用cchildrencchildren或refs3

this.$children的访问

适用情况:拿到所有的children

  • this.children是一个数组类型,它包含所有的子组件对象。

this.$refs的访问

适用情况:拿到某个children

  • this.$refs是一个对象类型,默认是空对象,必须加key(ref='aaa')
<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="aaa"></cpn>     注意看这里
    <button @click="btnClick">按钮</button>
  </div>

  <template id="cpn">
    <div>
      我是子组件
    </div>
  </template>
  <script>
    let app = new Vue({
      el: '#app',
      data: {
        message: '你好呀!'
      },
      methods: {
        btnClick() {
          //1. children
          // console.log(this.$children);
          // this.$children[0].showMessage()
          // for (let c of this.$children) {
          //   console.log(c.name);
          //   c.showMessage()
          // }
          //2. $refs
          console.log(this.$refs.aaa.name);

        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              name: '我是子组件的name'
            }
          },
          methods: {
            showMessage() {
              console.log('输出');
            }
          }
        }
      }
    })

  </script>
</body>

运行效果

  • 使用this.$children取得子组件

    Snipaste_2022-05-22_20-28-16

  • 使用this.$refs取得子组件

    Snipaste_2022-05-22_20-27-34

重点注意:

  1. 在使用this.$children时遍历子组件时用的方法。
  2. 在使用this.$refs的前提是你必须提前设置好key,也就是给目标子组件添加ref="aaa"

父子组件的访问方式:$parent

需求:有些时候需要子组件访问父组件的数据

  • 子组件访问组件用this.$parent

  • this.$parent是对象类型的

  <body>
    <div id="app">
      <h2>我是根组件</h2>
      <cpn></cpn>
    </div>
  
    <template id="cpn">
      <div>
        <h2>我是cpn组件</h2>
        <ccpn></ccpn>
      </div>
    </template>
  
    <template id="ccpn">
      <div>
        <h2>我是ccpn组件</h2>
        <button @click="btnClick">按钮</button>
      </div>
    </template>
  
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好呀!'
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                name: '我是cpn组件的name'
              }
            },
            components: {
              ccpn: {
                template: '#ccpn',
                methods: {
                  btnClick() {
                    //1. 访问父组件
                    console.log(this.$parent)
                    console.log(this.$parent.name)
                    //2. 访问根组件
                    console.log(this.$root.message)
                  }
                },
              }
            }
          }
        }
      })
  
    </script>
  </body>

运行效果

Snipaste_2022-05-22_20-51-35

Snipaste_2022-05-22_20-58-31

在上面的例子中,在根组件中注册一个cpn,再在cpn中加一个子组件ccpn,在cpn中打印父组件内容console.log(this.$parent),控制台输出vueComponent

根组件访问方式:$root

需求:子组件访问根组件。

  • 子组件访问根组件使用$root

  • this.$root是对象类型的

代码仍然是上个例子,输出console.log(this.$root)可以看到控制台将vue实例输出。

Snipaste_2022-05-22_21-17-27

8.插槽slot

为啥要使用插槽

  • 让我们封装的组件更加具有扩展性。
  • 让使用者可以决定组件内部的一些内容到底展示什么。

实际案例

  • 在移动开发中,几乎每个页面都有导航栏,导航栏必然会封装成一个插件,比如nav-bar组件。

  • 一旦有了这个组件,我们就可以在多个页面中进行复用。

    Snipaste_2022-05-23_20-50-43

如何去封装这类组件呢?

  • 他们有很多区别,但也有很多共性。
  • 我们单独去封装一个组件,显然不合适:比如每个页面都有返回,这部分内容我们就要重复去封装。
  • 但是,如果我们封装成一个,好像也不合理:有些是左侧菜单,有些是返回,有些中间是搜索,有些是文字,等等。

抽取共性,保留不同。

  • 最好的方法就是将共性抽取到组件中,将不同爆露为插槽。
  • 一旦我们预留了插槽,就可以让使用者根据自己的的需求,决定插槽中的内容。
  • 是搜索框还是文字,还是菜单,由调用者自己来决定.

实例

  • 需求:我想在第三个组件中添加一个按钮.
<body>
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>


  <template id="cpn">
    <div>
      <h2>我是组件</h2>
      <p>我是组件,哈哈哈</p>
      <button>按钮</button>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好帅'
      },
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    })
  </script>

</body>
  • 运行截图

Snipaste_2022-05-23_20-58-14

显然不符合我的预期,所以我们需要定义插槽

  • 引入插槽
<body>
  <div id="app">
    <cpn><span>我是插入的sapn标签</span></cpn>
    <cpn><i>我是插入的i标签</i></cpn>
    <cpn><button>我是插入的按钮</button></cpn>
    <cpn><button>我是插入的按钮</button></cpn>
  </div>


  <template id="cpn">
    <div>
      <h2>我是组件</h2>
      <p>我是组件,哈哈哈</p>
      <!-- 定义插槽 -->
      <slot></slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好帅'
      },
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    })
  </script>

</body>
  • 运行截图

Snipaste_2022-05-23_21-09-24

这次用了插槽,我就可以在组件中插入不同的DOM元素,实现了我的需求

插槽的默认值

  <template id="cpn">
    <div>
      <h2>我是组件</h2>
      <p>我是组件,哈哈哈</p>
      <!-- 定义插槽 -->
      <slot><button>我是默认按钮</button></slot>
    </div>
  </template>

当没有给插槽指定显示元素时,会显示默认值,也就是上面的button按钮

  • 如果要在插槽里显示多个值,同时放入组件中进行替换,一起作为替换元素.
<body>
  <div id="app">
    <cpn>
      <i>我是插入的i标签(一)</i>
      <h1>我是插入的h1标签(二)</h1>
      <i>我是插入的i标签(三)</i>
    </cpn>
  </div>

  <template id="cpn">
    <div>
      <h2>我是组件</h2>
      <p>我是组件,哈哈哈</p>
      <!-- 定义插槽 -->
      <slot><button>我是默认按钮</button></slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好帅'
      },
      components: {
        cpn: {
          template: '#cpn'
        }
      }
    })
  </script>

</body>
  • 运行截图

Snipaste_2022-05-23_21-17-39

可以看到全部替换了

具名插槽slot

使用name属性来对插槽进行区别.(和css里的类名差不多)

<div id="app">
    <cpn>
      <!-- 只将命名为center的插槽进行了替换 -->
      <span slot="center">标题</span>
    </cpn>
  </div>

  <template id="cpn">
    <div>
      <slot name="left"><span>左边</span></slot>
      <slot name="center"><span>中间</span></slot>
      <slot name="right"><span>右边</span></slot>
    </div>
  </template>
  • 运行截图

Snipaste_2022-05-23_21-33-27

只对中间插槽进行了替换

编译作用域

<body>
  <div id="app">
    <!-- 用的实例里的 -->
    <cpn v-show="isShow"></cpn>
  </div>

  <template id="cpn">
    <div>
      <slot name="left"><span>左边</span></slot>
      <slot name="center"><span>中间</span></slot>
      <slot name="right"><span>右边</span></slot>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好帅',
        isShow: 'true'
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              isShow: 'false'
            }
          }
        }
      }
    })
  </script>
</body>

Snipaste_2022-05-23_21-51-31

  • 官方给的准则:父组件模板的所有东西都会在父组件作用域内编译;子组件模板的所用东西所有的东西都会在子作用域内编译

可以看到,最后子组件里的内容是显示出来了,说使用的是vue实例里的isShow的值.这就说明每个组件都有自己的作用域,而且不会跨域.

作用域插槽slot

  • 父组件替换插槽的标签,但是内容由子组件提供。
<body>

  <div id="app">
    <cpn></cpn>
    <!-- 2.5.x一下必须使用template模板 -->
    <cpn>
      <!-- 获取子组件中的plangugaes -->
      <template slot-scope="slot">
        <!-- <span v-for="item in slot.data">{{item}}_</span> -->
        <span>{{slot.data.join(' - ')}}</span>
      </template>
    </cpn>

  </div>


  <template id="cpn">
    <div>
      <ul>
        <slot :data="plangugaes">
          <li v-for="item in plangugaes">{{item}}</li>
        </slot>
      </ul>
    </div>
  </template>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好呀'
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return {
              plangugaes: ['JavaScript', 'C++', 'c#', 'python', 'Go', 'swift']
            }
          },
          created() {
            this.plangugaes.join(' - ')
          }
        }
      }
    })
  </script>
</body>

上面的值是从子组件拿到的,父组件只是将值改了输出格式

重点:slot :data="plangugaes"slot.data.join(' - ')

image-20220524215803969

动态路由的使用

  • 某些事件,一个页面的Path路径可能是不确定的,比如我们进入用户界面时,希望时如下的路径: /user/aaa或/user/bbb 除了有前面的/user之外,后面还跟上了用户的ID 这种path和Component的匹配关系,我们称为动态路由(也是路由传递数据的一种方式)

$route是路由信息对象,每一个路由都会有一个route对象,是一个局部的对象,里面主要包含路由的一些基本信息,包括name、meta、path、hash、query、params、fullPath、matched、redirectedFrom等等

$router是VueRouter的实例,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性

route和router的区别

router/index.js

{
    path: '/user/:abc',
    name: 'user',
    component: User
  },

在router配置文件中,对/user路径进行配置,使得路由响应变为动态的,如果/user后面不加参数,就无法显示页面。

App.vue

<router-link :to="'/user/'+userid" tag="button">用户</router-link>

data() {
    return {
      imgURL:'http://www.baidu.com',
      userid:'lisi'
    }
  },

在App组件中,对路由进行配置,并使用v-bind将userid的值与data属性中的值进行绑定。

Uer.vue

<template>
  <div>
    <h2>我是用户界面</h2>
    <p>我是用户的相关信息,嘿嘿嘿</p>
    <h2>{{userid}} </h2>
    <h2>{{$route.params.abc}}</h2>
  </div>
</template>

<script>
  export default {
    name: 'User',
    computed:{
      userid(){
        //当前活跃的路由route
        return this.$route.params.abc
      }
    }
  }
</script>

在Uer.vue中使用$router(指的是当前活跃的路由器对象).params.abc将地址栏中的动态地址拿到,再返回。

Snipaste_2022-06-01_20-59-49

9.表单输入绑定

修饰符

  1. .lazy

添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步

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

给v-model添加number修饰符:

<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

  1. .trim

过滤用户输入的首尾空白字符,可以给v-mode添加trim修饰符

<input v-model.number="age" type="number">

a.Class 与 Style 绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定HTML Class

对象

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

<div v-bind:class="{ active: isActive }"></div>

或者直接写类名
<div v-bind:class="obj"></div>


<script>
    obj{
        active: isActive
	}
</script>

三元表达式

<div :class="ischange?'red':'green'">三元表达式改变颜色</div>

数组

<div class="box" :class="[border,{bgClor:isColor}]">class绑定--数组语法写法二</div>

border: "border",
isColor: true,

绑定Style

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

对象

<div :style="{
          background: "red",
          fontSize: "30px",
          color: "#fff"
        }">style绑定</div>


或者直接写对象名

<div :style="styObj">style绑定</div>


styObj: {
  background: "red",
  fontSize: "30px",
  color: "#fff"
},

注意属性名称

数组

<div :style="styArr">style绑定--数组</div>

styArr: [
  { "background": "red" },
  { "color": "blue" }
]

b.prop验证

prop在Vue中用来传递数据的。但他同时也可以进行一些数据的验证,下面将进行举例。

我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。

为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象。

Vue.component('my-component', {
  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) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].includes(value)
      }
    }
  }
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 datacomputed 等) 在 defaultvalidator 函数中是不可用的。

类型检查

type 可以是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

你可以使用:

Vue.component('blog-post', {
  props: {
    author: Person
  }
})

来验证 author prop 的值是否是通过 new Person 创建的。

props配置项

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
      	name:{
      	type:String, //类型
      	required:true, //必要性
      	default:'老王' //默认值
      	}
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

c.ref

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 一般用来获取DOM对象,如果用在组件上,获取回来的就是Vue对象。

一般的DOM元素可以使用ref='[属性名]'来进行注册。比如这样:

<input type="text" ref="mytext">

那么我们可以使用this.$refs.mytext对该DOM对象进行获取,就像这样:

console.log(this.$refs.mychild._data.username);

组件我们也可以获取,并可以对内部状态进行修改,但我们一般不用该属性对状态进行修改。

总结:使用方式: 1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School> 2. 获取:this.$refs.xxx

通信方案的优缺点

作用特质
prop自定义事件
ref获取DOM元素缺点:造成数据混乱

d.动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:

Snipaste_2022-08-02_10-04-02

这里用了一个witch来标注显示那个组件,原始写法是这样的:

    <home v-show="witch == 'home'"></home>
    <shop v-show="witch == 'shop'"></shop>
    <my v-show="witch == 'my'"></my>

在上面的三个li里注册点击事件:

    <ul>
      <li @click="witch='home'">首页</li>
      <li @click="witch='shop'">商城</li>
      <li @click="witch='my'">我的</li>
    </ul>

分别修改witch的值,最后组件进行匹配进行展示。

现在,Vue提供了一种更好用的方法,上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:

    <component :is="witch"></component>

注意:

  1. components是Vue内置组件

  2. is是属性值,is的值表示组件的名称,is的值是多个,那个组件就展示。

  3. 动态组件:

    1.通过component,动态绑定is属性,多个组件可以使用同一个挂载点,并且可以进行组件间的动态,删除旧组件重新创建新组件。

  4. keep-alive:在切换组件时将失活的组件进行缓存,而不是销毁。

  5. prop:属性,代表父级传给子组件的数据,不允许子组件进行修改。

    data:状态,数据:组件内部的状态,可以进行任意修改。

keep-alive

前面在vue-router里用到了keep-live,那是因为vue-router本身就是一个组件,并不是只能在vue-router里使用。

    <keep-alive>
      <component :is="witch"></component>
    </keep-alive>

e.mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

     全局混入:```Vue.mixin(xxx)```
     局部混入:```mixins:['xxx']	```
    

f.插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
  4. 使用插件:Vue.use()

g.scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

h.webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

       	该方法接受一个键名作为参数,返回键名对应的值。
      
    3. xxxxxStorage.removeItem('key');

       	该方法接受一个键名作为参数,并把该键名从存储中删除。
      
    4. xxxxxStorage.clear()

       	该方法会清空存储中的所有数据。
      
  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。

Vue全家桶

超过文章最大字符限制了,发不出来啦!

vue底层原理

超过文章最大字符限制了,发不出来啦!

单页面应用与多页面应用

单页面应用(SPA)多页面应用(MPA)
组成一个外壳页面与多个组件有多个页面结构
刷新方式局部刷新整页刷新
用户体验页面组件进行切换,用户体验好页面之间切换,加载缓慢,用户体验稍差
数据传递组件间通信url参数,localStorage
SEO不利于爬虫,不利SEO利于爬虫,利于SEO
适用范围优先考虑用户体验优先考虑搜索引擎优化

思维导图总览

建议下载svg格式进行查看

svg格式

VueS

png格式

VueZ

Footnotes

  1. 总图,图片太大,建议保存后查看

  2. 简单来说就是将输入框中的内容与vue实例中data的数据进行绑定,一方改变,另一方也跟着改变。

  3. reference(引用)