Vue知识框架

思维导图

前言

此知识框架为本人独立完成,难免有疏漏之处,请多多包涵。若对你有帮助,不妨给我点个免费的赞。若文章无法正确显示,备用文章链接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

❓ 为啥设置key值?

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

❓设置什么值为key值?

🔑理想型item.id; item

❓虚拟DOM

🔑提升性能,在数据更新前,生成新的虚拟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. 事件冒泡阶段


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

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

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

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

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

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

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

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

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

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

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

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

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

🔑如何切换成捕获型事件流

像这样

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

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

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

🔑 同样,使用.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

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

  • 父组件访问子组件:使用$cchildren或$refs[3]

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全家桶

1.Vuex

从基本的文档看起[vuex文档](开始 | Vuex (vuejs.org))

我也专门写过一篇关于Vuex的文章,传送门

1.概念

	在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.何时使用?

	多个组件需要共享数据时

3.搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

2.Vue-Cli

  • 使用场景:开发大型项目
  • 帮助我们直接生成配置

CLI是什么意思?

  • CLI是Command-Line Interface,翻译为命令行界面,俗称脚手架
  • 使用vue- cli可以快速搭建一个Vue开发环境以及对应的webpack配置。

Vue-CLI使用的前提 - node

webpack是依赖于node.js的,所以需要前置安装webpack

  • vue.js官方的脚手架工具就是用了webpack模板

1.对所有的资源都会压缩等优化操作。
2.他在开发过程中提供了一套完整的功能,能够使得我们开发过程变得高效。

脚手架文件结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

关于不同版本的Vue

  1. vue.js与vue.runtime.xxx.js的区别:
    1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。

vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

安装Vue CLI

npm i -g @vue/cli

Vuecli-CLI2的目录结构解析

🔴build:关于webpack的配置(里面定义了一些变量)

🔴config:关于webpack的配置

🔵node_modules:我们项目中依赖的组件都会放在这个文件夹中

🔴src:开发时,在此文件夹中写代码

🔵static:封装静态资源(图片)

🔵.babelrc:ES代码相关转化配置

🔵.editotconfig:项目文本的相关配置

🔵.postcssrc.js:CSS相关转化的配置

🔵.gitgnore:Git仓库忽略的文件夹配置

🔴package.json:记录引入组件的版本号,用来管理组件

🔴package-lock.json:开发时,在此文件夹中写代码

Eslint代码规范

  • 作用:规范开发。
  • 开发过程中如何关闭

方法一:

或者先让lint识别,自动修复

npm run lint //进行lint修复
npm run serve 

方法二:

在vscode种安装eslint的扩展程序

方法三:

vue.config.js文件种添加配置项lintOnSave: false(先关闭,写完再开启)

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
+  lintOnSave: false
})

注意:

此时修改vue.config.js后,需要重新对项目进行运行,npm run serve

vue脚手架配置代理

方法一

在vue.config.js中添加如下配置:
devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

方法二

编写vue.config.js配置具体代理规则:
module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

3.vue-router

认识vue - router

vue - router是Vue.js官方路由插件,它和vue.js是深度集成的,适用于但页面应用。

router官方教程

安装和使用vue - router

  1. 安装vue - router
npm install vue - router  -- save
  1. 在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
    1. 导入路由对象,调用Vue.use(VueRouter)
    2. 创建路由实例,并且传入路由映射配置
    3. vue实例挂载创造的路由实例

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
//1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
]
//2.创建VueRouter对象
const router = new VueRouter({
  //模式  
  mode: 'history',
  base: process.env.BASE_URL,
  //3.配置路由和组件之间的应用关系(ES6增强写法)
  routes
})
//4.导出router
export default router

src/main.js

import Vue from 'vue'
import App from './App.vue'
//5. 导入router
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  //6.声明router
  router,
  store,
  render: h => h(App)
}).$mount('#app')

  1. 使用vue-router的步骤
    1. 创建路由组件
    2. 配置路由映射:组件和路径映射关系
    3. 使用路由:通过<router - link> < router - view>

<router - link>:该标签是一个vue-router中已经内置的组件,它会被渲染为一个<a>标签,
< router - view>:该标签会根据当前的路径,动态渲染出不同的组件。
网页的其他内容比如顶部的标题/导航,或者底部的一些版权信息会和 < router - view> 处于同一个等级。
在路由切换时,切换的是< router - view>挂载的组件,其他内容不会发生变化。

router属性补充

属性名 作用 示例
tag tag可以指定< router-link >之后渲染成什么组件,比如上面的代码会被渲染成一个< li >,而不是< a> < roter-link to = '/home' tag='li'>
replace replace不会留下hisrory记录,所以会指定replace的情况下,后退键返回不能返回到上一个页面中 < roter-link to = '/home' replace>
active-class 当 < router - link >对应的路由配置成功时,会自动给当前元素设置一个router-link-actice的class,设置active-class可以修改默认名称,在进行高亮显示的导航栏菜单或者底部的tabber时,会用到该类,但是通常不会修改该类的属性,会直接使用默认的router-link-actice即可。
exact-active-class 类似于active-class,只是在精准匹配下才会出现的class,后面看到嵌套路由时,再回来看这个属性

在router的index.js里使用linkActiveClass配置active

1. router/index.js

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
  linkActiveClass: 'active'
})

2. App.vue

<template>
  <div id="app">
    <h2>我是APP组件</h2>
    <router-link to="/home" tag="button" replace>  首页  </router-link>
    <router-link to="/about" tag="button" replace >  关于  </router-link>
    <!-- <router-link to="/home" tag="button" replace active-class="active">  首页  </router-link>
    <router-link to="/about" tag="button" replace active-class="active">  关于  </router-link> -->
    <router-view></router-view>
    
  </div>
</template>

<style>
.active{
  color: red;
}
</style>

配置路由映射:router/index.js

const routes = [
  {
    //路由的默认路径
    path: '/',
    //redirect重定向,路径也会改变
    redirect: '/home'
    //路径不会改变
    // component: Home
  },  
  {
    path: '/home',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    component: About
  },

]

使用路由:App.vue

<template>
  <div id="app">
    <router-link to="/home">  首页  </router-link>
    <router-link to="/about">  关于  </router-link>
    <router-view></router-view>
  </div>
</template>

<script>
  export default{
    name : 'App'
  }
</script>

<style>

</style>

通过代码跳转路由

<script>
export default {
  name: "App",

  methods: {
    homeClick() {
      console.log("homeClick");
      //通过代码方式修该路径 v-router
      //push => pushState             《------------
      this.$router.push('/home')
    },
    aboutClick() {
      //replace =>replaceState        《------------   
      this.$router.replace('/about')
      console.log("aboutClick");
    },
  },
};

</script>

动态路由

  {
    path: '/detal/:id',
    name: 'detal',
    component: Detal,
    props: true //开启props传参
  },

后面有:id 使得其后面可以为任何值。

我们在跳转时可以传值:

  methods: {
    handleDetal(id) {
      console.log(id);
      // this.$router.push(`/detal/${id}`);
      this.$router.push({
        name: "detal",
        params: {
          id,
        },
        query: {
          name: "admin",
          age: 19,
        },
      });
    },
  },

这边时跳转到/detal里,并传了一个id参数,同时query里也有数据。

我们到目标组件里,打印一下this,会输出当前vue对象,我们点开$route,可以看到本条路由规则里相应的内容。

Snipaste_2022-08-04_15-22-17

  1. /detal/1:斜线后面的参数是路径参数
console.log(this.$route.params.id);

this.$route.path:路径
this.$route.fullpath:携带查询字符串的完整路劲

路由懒加载的使用

使用哪里路由,唤醒哪个路由。

解释

当打包构建应用时,JavaScript包会变得非常大,影响页面的加载。如果我们可以把不同的路由对应的组件分割成不同的代码块,然后当路由访问的时候才加载对应组件,这样就更加高效了。

懒加载做了什么

路由懒加载的主要作用就是将路由对应的组件打包成一个个js代码块

只有在这个路由被访问到的时候,才加载对应的组件。

Snipaste_2022-06-01_22-12-50

Snipaste_2022-06-01_22-11-51

使用方式一去代替方式二,实现懒加载,具体体现在项目打包时,dist文件夹中文件的数目。

路由嵌套

嵌套路由是一个很常用的功能

在home页面中,我们希望通过/home/news和/home/message访问一些内容。

一个路径映射一个组件,访问这两个路径也会分别渲染连个组件

路径和租件的关系如下:

Snipaste_2022-06-02_18-06-09

实现嵌套路由的步骤

  1. 创建对应的子组件,并且在路由映射中配置对应的子路由

Snipaste_2022-06-02_18-29-38

  1. 在子组件内部使用< router-view >标签。

Snipaste_2022-06-02_18-30-20

HomeMessage.vue

<template>
  <div>
    <ul>
      <li>消息一</li>
      <li>消息二</li>
      <li>消息三</li>
      <li>消息四</li>
      <li>消息五</li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: '',
  }
</script>

<style lang=scss>
</style>

HomeNews.vue

<template>
  <div>
    <ul>新闻一</ul>
    <ul>新闻二</ul>
    <ul>新闻三</ul>
    <ul>新闻四</ul>
    <ul>新闻五</ul>
  </div>
</template>

<script>
  export default {
    name: '',
  }
</script>

<style lang=scss>
</style>

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
// import Home from '../components/Home'
// import About from '../components/about'
// import User from '../components/User'

const Home = () => import('../components/Home')
const About = () => import('../components/about')
const User = () => import('../components/User')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')

//1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    //redirect重定向,路径也会改变
    redirect: '/home'
    //路径不会改变
    // component: Home
  },
  {
    path: '/home',
    name: 'home',
    component: Home,                      ----------------------------------------------
    children: [                            注意看这里,使用children来定义子路由的一些相关配置
      {
        path: '',
        redirect: 'news'                   
      },
      {
        //子路由不用加`/`
        path: 'news',
        component: HomeNews
      },
      {
        path: 'message',
        component: HomeMessage
      }
    ]
  },                                        --------------------------------------------
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: About
  },
  {
    path: '/user/:abc',
    name: 'user',
    component: User
  },

]
//2.创建VueRouter对象
const router = new VueRouter({
  //模式
  mode: 'history',
  base: process.env.BASE_URL,
  //3.配置路由和组件之间的应用关系(ES6增强写法)
  routes,
  linkActiveClass: 'active'
})
//4.导出router
export default router

Home.vue(父路由)

<template>
  <div>
    <h2>我是首页</h2>
    <h2>我是内容hhh</h2>
    <router-link to="/home/news">    新闻</router-link>
    <router-link to="/home/message">    消息</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: '',
  }
</script>

<style lang=scss>
</style>

Snipaste_2022-06-02_19-44-51

路由参数传递

传递参数主要有两种类型:paramsquery

  • params类型
    配置路由格式:/router/:id
    传递方式:在path后面跟上对应的值
    传递后形成的路径:/router/123,/router/abc

  • query的类型:
    配置路由格式:/router,也就是普通配置
    传递的方式:对象中使用query的key作为传递方式
    传递后形成的路径:/router?id=123,/router?id=abc

url:

协议://主机:端口/路径?查询

scheme://host:port/path?query%fragment

传值方式一
<router-link :to="{path:'/profile',query:{name:'liyu',age:18,height:180}}" tag="button">个人主页</router-link>

在该组件中,通过$route.query.属性值的方式拿值

<template>
  <div class="pro">
    <h2>我是profile组件</h2>
    <p>我叫{{$route.query.name}}</p>
    <p>我今年{{$route.query.age}}岁了</p>
    <p>我身高{{$route.query.height}}厘米</p>
  </div>
</template>

Snipaste_2022-06-02_22-04-13

传值方式二

<button @click="userClick">用户</button>
<button @click="proFileClick">档案</button>


    userClick(){
      this.$router.push('/user/'+this.username)
    },
    proFileClick(){
      this.$router.push({
        path:'/profile',
        query:{name:'kebi',age:38,height:240}
      })
    }

--------------------------------
proFile组件
<template>
  <div class="pro">
    <h2>我是profile组件</h2>
    <p>我叫{{$route.query.name}}</p>
    <p>我今年{{$route.query.age}}岁了</p>
    <p>我身高{{$route.query.height}}厘米</p>
  </div>
</template>

Snipaste_2022-06-02_22-07-51

动态路由:

this.$route:参数对象

this.$router:导航对象

this.$router.push(`/detal/${id}?name=admin&age=19`);
这两种方式等价
this.$router.push({
        name: "detal",
        params: {
          id,
        },
        query: {
          name: "admin",
          age: 19,
        },
      });

props传参:

路由规则:props:true;

使用:在对应路由:props:['id']:{{pid}}

$route和$router是有区别的

  • $router为VueRouter实例,想要导航到不同的URL,则使用$router.push的方法。
  • $route为当前router跳转对象里面可以获取name,path,params等。

Snipaste_2022-08-04_15-27-07

$route:参数对象

$router:路由的导航对象

所有的组件都继承自vue的原型
在main.js中创建一个name属性

全局导航守卫

  • 网页标题时通过< title >来显示的,而SPA只有一个固定的HTML,切换不同页面时,标题并不会改变。
  • 在javaScript里,我们可以使用.window.document.title = ‘新的标题’来对标题进行修改该,但页面一旦多了,也就不好管理了。
  • 在Vue中我们可以使用路由守卫来监听全局的跳转

router/index.js


const routes = [
  {
    path: '/',
    //redirect重定向,路径也会改变
    redirect: '/home'
    //路径不会改变
    // component: Home
  },
  {
    path: '/home',
    name: 'home',
    component: Home,
    meta: {
      title: '首页'
    },
    children: [
      {
        path: '',
        redirect: 'news'
      },
      {
        //子路由不用加`/`
        path: 'news',
        component: HomeNews
      },
      {
        path: 'message',
        component: HomeMessage
      }
    ]
  },
  {
    path: '/about',
    name: 'about',
    meta: {
      title: '关于'
    },
    component: About
  },
  {
    path: '/user/:id',
    name: 'user',
    meta: {
      title: '用户'
    },
    component: User
  },
  {
    path: '/profile',
    component: Profile,
    meta: {
      title: '个人主页'
    },
  }

]

//前置钩子hook(前置回调)
router.beforeEach((to, from, next) => {
  //从from跳转到to
  //存在路由嵌套的化,会显示undifine,所以采取下面的代码
  document.title = to.matched[0].meta.title;
  console.log(to);
  next()
})

在路由中配置元数据meta

前置钩子

beforeEach

后置钩子

如果是后置钩子,也就是afterEach,不需要主动调用next()函数。

//后置钩子(守卫)
router.afterEach((to, from) => {
  console.log('-----');
})

导航守卫

除了上面的全局守卫,还有其他两种守卫

  1. 路由独享守卫
  2. 组件内的守卫

keep-alive

  • keep-alive可以用在组件上,用来对组件的状态进行缓存

  • router-view 也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都会缓存,保持生存

  • keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

属性 描述
include(包含) 字符串或正则表达式,只有匹配的组件会被缓存
exclude(不包含) 字符串或者正则表达式,任何匹配的组件都不会被缓存
    <keep-alive exclude="profile,User">
      <router-view></router-view>
    </keep-alive>

上面这行代码将会把name属性值profile user的两个组件对于keep-alive排除在外,是他们可以正常的被创建和销毁。
当有多个组件是,需要用逗号将他们隔开,切记,不要加空格或者其他符号:第二个组件没有生效,只有第一个组件有用。

使用keep-alive标签将要保存live的组件包裹起来

router -view 也是一个组件,如果直接被包括在keep-alive里面,所有的路劲匹配到的视图组件都会被缓存。

    <keep-alive>
      <router-view></router-view>
    </keep-alive>

小问题(学习老师的解决问题的思路)

问题讲解

<script>
  export default {
    name: '',
    data(){
      return{
        message:'你好呀',
        path:'/home/news'
      }
    },
    //实例创建后开始执行
    created(){
      // document.title='首页'
      console.log(' home created');
      
    },
    //将tamplate等模板相关的东西被挂载到DOM就会回调这个生命周期函数
    // mounted(){
    //   console.log('mounted')
    // },
    // //界面发生刷新后就会回调
    // update(){
    //   console.log('update')
    // }
    destroyed(){
      
      console.log(' home destrotyed')
    },
    // 这两个函数只有该组件使用了keep-alive时才有效
    activated(){
      console.log(this.path);
      this.$router.push(this.path);
    },

    beforeRouteLeave(to,from,next){
      console.log(this.$route.path)
      this.path = this.$route.path;
      next();
    }
  }
</script>

在home组件中使用path变量保存路径,然后使用beforeRouteLeave保存路由跳出的url,使用activated在下一次活跃路由时,将url定向到原来跳出的地点。

Vue生命周期讲解

vue生命周期

Snipaste_2022-08-02_19-33-22

生命周期 描述 使用频率
beforeCreate 初始化事件,没有对事件进行处理 很少
create 完成了状态的初始化,注入响应式。可以初始化状态(但无法实现数据驱动,响应式),挂载属性到当前的实例上。 -
beforeMount 完成了Vue实例的创建,但并没有实例化DOM节点 -
mounted 实例化了DOM节点,拿到真实的DOM节点。发送ajax请求,使用插件(依赖DOM节点),比如swiper。 -
beforeUpdate 状态更新前,获取旧的DOM节点(重复无限次) -
updated 状态更新后,获取新的DOM节点(重复无限次) -
beforeDestroy 销毁前 -
destroyed 销毁后,清除定时器 -

Vue底层原理

1.数据代理

数据代理:通过一个对象代理对另外一个对象中属性的操作:(读/写)

Object.defineProperty

这个方法也是Vue数据双向绑定原理的常见面试题,

Object.defineProperty([属性所在的对象],'[要增加的属性]',{ ...描述对象})

描述对象的相关配置项:

属性 说明
enumerable 能否枚举(遍历),默认值是false
wiritable 能否修改,默认值是false
value 这个属性的值,默认值是undefined
configurable 示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值是false
  //定义一个对象
  let person = {
    name: 'liyu',
    sex: '男'
  }
  //使用Object.defineProperty来给这个对象添加值
  
  Object.defineProperty(person, 'age', {
    value: 18,
    enumerable: true, //控制属性是否可以枚举
    writable: true,//控制属性是否可以修改
    configurable:true //控制属性是否可以删除
  })

注意:

通过上面的方法,我们就可以给person添加一个age属性,值是18.

使用Object.defineProperty中的高级配置项get属性可以实现数据的读取

  let number = 18
  let person = {
    name: 'liyu',
    sex: '男'
    age:number
  }

这种情况下person对象中age属性的值只取决于js编译时给赋的值,之后number再怎么修改age也不会变!若想同步变化,使用Object.defineProperty中的高级配置项get属性就是很好的办法。

  let number = 18
  let person = {
    name: 'liyu',
    sex: '男'
  }
  Object.keys(person)
  //当有人读取person的age属性时,get函数就会被调用,返回值就是age的值
  Object.defineProperty(person, 'age', {
    get: function () {
      return number
    }
  })

这样,每次在访问age属性时,都会调用属性getter,getter会会返回number的值,这样就完成了我们的需求。

同样,如果希望在修改person对象的age属性时,number的值也跟着改,应该怎么办呢?

使用Object.defineProperty中的高级配置项set属性可以实现数据的修改

  let number = 18
  let person = {
    name: 'liyu',
    sex: '男'
  }
  Object.keys(person)

  Object.defineProperty(person, 'age', {
    // value: 18,
    // enumerable: true, //控制属性是否可以枚举
    // writable: true,//控制属性是否可以修改
    //当有人读取person的age属性时,get函数就会被调用,返回值就是age的值
    get: function () {
      console.log("有人读取age属性");
      return number
    },
    //当有人修改person的age属性时,get函数(setter)就会被调用,且会收到修改的具体值
    set: (value) => {
      console.log("有人修改age属性");
      number = value  //将修改的值赋值给number
    }
  })

这样当对person.age进行修改时,也可以对number进行修改。

Snipaste_2022-07-11_11-47-05

Vue中的数据代理

原理:

  1. 通过Object.defineProperty()把data对象中的所有属性添加到vm上。
  2. 为每一个添加到vm上的属性都指定一个getter/setter。
  3. 在getter/setter内部去操作(读/写)data中对应的属性。

先写一个简单的Vue实例

<body>
  <div id="root">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
  </div>
</body>

<script>
  Vue.config.productionTip = false;

  const app = new Vue({
    el: '#root',
    data: {
      name: '学校名称',
      address: '科技园'
    },
  });

</script>

很简单的一个vue实例,现在在控制台将它输出。

Snipaste_2022-07-11_12-05-59

大家是否关注到address和name属性和上章的所讲的获取age的值极为相似?没错Vue中就是使用 了Object.defineProperty中的高级配置项getset来实现数据代理的!我们可以通过app._data.name/app._data.address来访问Vue属性data中的值。

实际上,我们使用的nameaddress都是于app._data里的数据相映射的,修改name的值,其实就是修改app._data.name的值。产生这一奇妙作用的API就是Object.defineProperty

Vue这样做,就是为了程序员更加方便的操作data中的数据

Snipaste_2022-07-11_13-30-07

数据劫持:

2.Vue生命周期

我之间写过一篇专门了解Vue生命周期的文章,传送门

Vue生命周期

3.Vuex和全局事件总线的区别

Vuex和全局事件总线的区别

Vuex状态管理是利用Store仓库进行管理,而事件总线是通过bus 绑定 on和触发($emit)和解绑($off)事件实现的。

相同点 不同点
都使用了Vue数据代理这个内置关系:VueComponent.prototype._ proto _== Vue.prototype vue时间总线不依赖于第三方插件,而Vuex是一个是强大的第三方插件

4.响应式原理

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM树上。

1.自己实现响应式

<body>
  <div class="box">
    {{obj.username}}
  </div>
</body>

</html>

//拦截器
<script>
  var box = document.querySelector(".box")
  var obj = {

  }
  Object.defineProperty(obj, 'username', {
    get: function () {
      console.log("get");
    },
    set: function (value) {
      console.log("set");
      console.log(value);
      box.innerHTML = value
    }
  })
</script>

Snipaste_2022-07-28_08-57-23

可以看到,当执行修改username属性时,Object.definedProperty起了关键作用,它会调用set方法,将当前值进行修改,然后渲染到页面上。这样就实现了响应式。

5.虚拟DOM

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

return createElement('h1', this.blogTitle)

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

  • 虚拟DOM (Virtaul DOM): 用 js 对象模拟的,保存当前视图内所有 DOM 节点对象基本描述属性和节点间关系的树结构。用 js 对象,描述每个节点,及其父子关系,形成虚拟 DOM 对象树结构。
  • 因为只要在 data 中声明的基本数据类型的数据,基本不存在数据不响应问题,所以重点介绍数组和对象在vue中的数据响应问题,vue可以检测对象属性的修改,但无法监听数组的所有变动及对象的新增和删除,只能使用数组变异方法及$set方法。

数组更新检测:

a.变更方法(原数组能够发生变化)

push(),pop(),shift(),unshift(),splice(),sort(),reverse()

b.替换数组:(非变更方法:不能使得原数组发生变化)

filter(),map(),concat(),slice()

可以看到,arrayMethods 首先继承了 Array,然后对数组中所有能改变数组自身的方法,如 pushpop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 pushunshiftsplice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify() 手动触发依赖通知,这就很好地解释了用 vm.items.splice(newLength) 方法可以检测到变化

总结:Vue 采用数据劫持结合发布—订阅模式的方法,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

img

  • Observer 遍历数据对象,给所有属性加上 settergetter,监听数据的变化
  • compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

Watcher 订阅者是 ObserverCompile 之间通信的桥梁,主要做的事情

  • 在自身实例化时往属性订阅器 (dep) 里面添加自己
  • 待属性变动 dep.notice() 通知时,调用自身的 update() 方法,并触发 Compile 中绑定的回调

runtime-compiler和runtime-only的区别

视频解析

区别只在main.js

Snipaste_2022-05-28_21-16-33

vue程序运行过程

Snipaste_2022-05-28_21-20-50

  • runtime-compiler

🔴过程:template ---- ast ---- render ----- vdom ----- UI

  • runtime-only

🔴过程:render ---- vdom ---- UI

  1. 性能更高
  2. 代码量更少
  3. 疑问:
  • 那.vue 文件中的template由谁处理了?
    答 :是由vue-template-compiler,转化成render函数。

render函数

render: funcation (createElement){
	//1. createElemt('标签', {标签属性} ,[''])
	return  createElemt('h2',{class: 'box'}, [‘hello world'])
	//2. 
	return  createElemt('h2',
	{class: 'box'},
    [‘hello world',createElemt('button'),['按钮']])
    //3. 传入组件对象,定义一个组件cpn
    return  createElemt(cpn)                    
}

npm run build

Snipaste_2022-05-29_21-28-51

VueCLI3配置文件的目录结构

Snipaste_2022-05-29_22-13-20

组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅。

nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
  2. 图示:

图示

03.写法:

  1. 准备好样式:

    • 元素进入的样式:
      1. v-enter:进入的起点
      2. v-enter-active:进入过程中
      3. v-enter-to:进入的终点
    • 元素离开的样式:
      1. v-leave:离开的起点
      2. v-leave-active:离开过程中
      3. v-leave-to:离开的终点
  2. 使用<transition>包裹要过度的元素,并配置name属性:

    <transition name="hello">
    	<h1 v-show="isShow">你好啊!</h1>
    </transition>
    
  3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

网络请求

1.fetch

官方文档

在项目开发过程中,我们向服务端发送请求,一般会使用三种方式, XMLHttpRequest(XHR)FetchjQuery实现的AJAX,第三方包axios。其中, XMLHttpRequest(XHR)Fetch是浏览器的原生API,jquery的ajax其实是封装了XHR(这种使用起来就比较复杂了,不推荐使用),而fetch和axios都是封装了promise来处理异步。

fetch api是浏览器内置的api,和axios不同。

基于promise的封装,形成的异步数据请求的标准,兼容性不好

chrome浏览器 103.0.5060.134(正式版本) (64 位) 控制台输入fetch.

Snipaste_2022-08-01_22-06-21

兼容性

Snipaste_2022-08-01_22-20-36

fetch使用


fetch('url', {
    method: 'get'
    //options
}).then(function(response) {
	//返回请求的数据
}).catch(function(err) {
    // 出错了;使用catch进行捕获

});

使用fetch发送get请求

fetch("/json/text.json").then(res => res.json()).then(res => {
    console.log(res);
}).catch(err => console.log(err))

注意:

第一个then里的res返回的是状态码和响应头,第二个里面返回的是数据,使用catch捕获异常。

get请求也可以传参:url?username=zx&password=123456

使用fetch发送post请求

发送application/x-www-form-urlencoded
        fetch("/user.json", {
          method: "post",
          headers: {
            "Content-type": "application/x-www-form-urlencoded"
          },
          body: "username=zx&password=123456"
        }).then(res => res.json).then(res => {
          console.log(res);
        }).catch(err => {
          console.log(err);
        })

真如你所见,我在发起请求时,options并不是必须的,所以在上个例子使用fetch发送get请求里,我并没有给配置options。一般来说options是用来设置调用时的Request对象,使用方法可以参考上面的例子。

  • method - 支持 GET, POST, PUT, DELETE, HEAD
  • url - 请求的 URL
  • headers - 对应的 Headers 对象
  • body - 请求参数(JSON.stringify 过的字符串或’name=jim\u0026age=22’ 格式)
发送application/json
fetch("", {
  method: "post",
  headers: {
    "Content-type": "application/json"
  },
  body: JSON.stringify({ "name": "zs", "password": "123456" })
}).then(res => res.json).then(res => {
  console.log(res);
}).catch(err => console.log(err))

注意:

因为fetch默认是get请求,所以这里要标清楚请求方式。

同时也要包含,post.body里的内容,比如请求头,请求体等。

2.axios

官方文档

优点:异步无刷新进行局部数据的更新,用户体验好
缺点:需要三方包引入

使用axios发送get请求

axios.get("/day3/json/mi1.json").then(res => {
  this.list = res.data.data.sections
  // console.log(this.list);
}).catch(err => {
  console.log(err);
})

单页面应用与多页面应用

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

思维导图总览

建议下载svg格式进行查看

svg格式

VueS

png格式

VueZ


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

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

  3. reference(引用) ↩︎

posted @ 2022-08-08 09:55  抗争的小青年  阅读(126)  评论(0编辑  收藏  举报