Vue知识点整理(待更新)

Vue知识点整理(待更新)

参考Vue.js中文官网Vue 知识点汇总(上)–附案例代码及项目地址Vue 知识点汇总(下)–附案例代码及项目地址Vue知识点汇总【持更】

一、基础知识

  • 【问】JS中attribute和property有什么区别?(attribute属性在HTML上,property属性值在js上;两者的区别是attribute不会同步property的值,而property可以同步attribute的值)

    Note

    • attributeHTML标签上的特性,它的值只能够是字符串attribute可以简单理解成dom节点自带的属性,例如html中常用的id、class、title、alignattributes是属于property的一个子集。attribute的赋值:

          //注意参数1,2均是字符串
       div1.setAttribute('class', 'a');
       div1.setAttribute('title', 'b');
       div1.setAttribute('title1', 'c');
       div1.setAttribute('title2', 'd');
      
    • property是DOM中的属性,是JavaScript里的对象;property取值如下:

      //取任何属性的只,用“.”即可 
      var id = div1.id;
      var className = div1.className;
      var childNodes = div1.childNodes;
      var attrs = div1.attributes;
      
      //此处再次强调:
      //1) class特性在变成属性时,名字改成了“className”,因此div1.className和div1.getAttrbute('class')相同。
      //2) 上面代码中的div1.attributes是取的attributes这一属性,取出来保存到attrs变量中,attrs就成了一个NamedNodeList类型的对象,里面存储了若干个Attr类型。
      
    • 在js中,attribute和property关系如下:

      • property能够从attribute中得到同步

      • attribute不会同步property上的值;

      • attribute和property之间的数据绑定是单向的,attribute->property;

      • 更改property和attribute上的任意值,都会将更新反映到HTML页面中;

  • 【问】Vue.js是什么?(只关注于视图层,渐进式框架)

    Note

    • Vue (读音类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用

    • Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

  • 【问】MVVM是什么?谈谈Vue的工作原理?

    Note

    • MVVM 是 Model - View - ViewModel 的缩写 可以看到他和之前的MVC很像,的确有人称之为一个加强优化版的MVC。 是一种模块化开发代码分层的思想或者框架!

      • 模型(Model):模型和业务数据绑定,方便数据的使用和传递。

      • 视图(View): 视图是应用程序中用户界面相关的部分,是用户看到并与之交互的界面

      • ViewModel:首先它的创建需要将Model中的数据绑定在他身上(为模型到视图的映射提供桥梁)。将原来MVC中的业务逻辑剥离出来写在ViewModel中,简化viewcontroller
        在这里插入图片描述

    • 使用步骤
      1、模块中需要的数据,通过网络层请求得到 ,然后将数据绑定到Model层中
      2、将model层中的数据转化到ViewModel中,然后在ViewModel中处理一些逻辑问题
      3、将ViewModel中的数据绑定到控制器的View上,然后更新界面。

      MVVM模型适合作为前端的框架,用来实现前后端分离。

    • Vue官网对Vue的MVVM模型做出了解释:参考Vue中的MVVM

      在这里插入图片描述

      各层的作用:

      • View层

        ①视图层
        ②在前端开发中就是DOM层
        ③主要作用是给用户展示各种信息

      • Model层

        ①数据层
        ②数据可能是固定的死数据,更多是来自于服务器,从网络上请求下来的数据。

      • ViewModel层

        ①视图模型层
        ②视图模型层是View和Model沟通的桥梁
        ③一方面它实现了Data Bindings来进行数据的绑定,将Model的改变实时反映到View中
        ④另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击,滚动,touch)时,可以监听到,并在需要的情况下改变对应的Data。

  • 【问】Vue实例的生命周期分为哪几个阶段?(创建、运行和销毁;常见的生命周期函数包括createdmountedupdated等)

    Note

    • 生命周期概念:生命周期是指一个组件从创建 > 运行 > 销毁的整个过程,强调的是一个时间段

      生命周期函数概念:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行

      注意:生命周期强调的是时间段,生命周期函数强调的是时间点

    • 组件生命周期函数的分类:

      在这里插入图片描述

    • 官网提供的Vue实例生命周期解释如下:
      在这里插入图片描述

  • 【问】关于Vue的特性有哪些?(数据驱动视图,双向数据绑定)

    Note

    • 数据驱动视图:数据(model)的变化会驱动视图(View)自动更新

    • 双向数据绑定:在网页中,form负责采集数据,Ajax负责提交数据。数据源的变化,会被自动渲染到页面上;页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到数据源中。

  • 【问】如何编写一个简单的Vue应用?(Vue实例挂载到某个元素上)

    Note

    • Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <!-- 开发环境版本,包含了有帮助的命令行警告 -->
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      </head>
      <body>
          <div id="app">  
            {{ message }}  <!-- 声明式渲染 -->
          </div>
      </body>
          <script>
              var app = new Vue({  //创建Vue实例
                el: '#app',  //挂载点为id = "app"的DOM元素
                data: {
                  message: 'Hello Vue!'
                }
              })
          </script>
      </html>
      

      现在数据和 DOM 已经被建立了关联,所有东西都是响应式的,即在当前页面的控制台上修改app.message = "Hello world",html页面会更新内容。
      在这里插入图片描述

二、基础语法(渲染和绑定)

  • 【问】Vue实例对象中常用属性有哪些?

    Note

    • el:"" :指定vue所操作的dom范围,属性值是你获取的节点;

    • data:{}:就是vue的model,是存放数据的,属性值是一个对象或者是一个函数,在组件中的data是一个函数

    • watch:{}vue中的侦听器

    • computed:{}vue中的计算属性,看起来像methods,用起来像data

    • methods:{}:是vue中的事件方法

  • 【问】内容渲染指令有哪些?(插值表达式,v-text=‘’,v-html=‘’)

    Note

    • 内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:

      • {{}}插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容。注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中;

      • v-text:会覆盖元素内部原有的内容

      • v-html:不仅会覆盖原来的内容,而且可以把带有标签的字符串,渲染成真正的HTML内容

      比如:

      <div id = "box1">
         <p v-text="text2">这是我原本的内容</p>
         <div v-html="text3">这是我原本的内容</div>
      </div>
      <script>
         const vm1=new Vue({
            el:'#box1',
            data:{
              text1:'插值表达式的结果',
              text2:'v-text的结果',
              text3:'a href="http://www.baidu.com">v-html的结果</a>'
            }
      </script>
      
  • 【问】属性绑定指令如何使用?(v-bind:属性=,常配合class,style,src和href属性使用)

    Note

    • v-bind:可为元素的属性动态绑定属性值,可简写为:v-bind:语法糖)。

    • 在开发中,有哪些属性需要动态进行绑定:比如图片的链接src、网站的链接href动态绑定一些类、样式等等,比如

      <!--html-->
      <a :href="'https://www.runoob.com/vue2/'+url">点击跳转vue菜鸟教程</a>
      
      <!--script-->
      const vm2=new Vue({
           el:'#box2',
           data:{
               url:'vue-tutorial.html'
           }
      })
      
    • v-bind:class=:动态切换class,比如:

      • 数据为某个状态时,字体显示红色。
      • 数据另一个状态时,字体显示黑色。

      绑定class有两种方式:

      • 对象语法class后面跟的是一个对象

        用法一:直接通过{}绑定一个类
        <h2 :class="{'active': isActive}">Hello World</h2>
        
        用法二:也可以通过判断,传入多个值
        <h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
        
      • 数组语法class后面跟的是一个数组

        用法一:直接通过{}绑定一个类
        <h2 :class="['active']">Hello World</h2>
        
        用法二:也可以传入多个值
        <h2 :class="['active', 'line']">Hello World</h2>
        
    • v-bind:style来绑定一些CSS内联样式,绑定style有两种方式:

      • 对象语法:style后面跟的是一个对象类型,对象的key是CSS属性名称,
        对象的value是具体赋的值
        ,值可以来自于data中的属性:

        :style="{color: currentColor, fontSize: fontSize + 'px'}"
        
      • 数组语法:style后面跟的是一个数组类型,多个值以“,“分割即可

        <div v-bind:style="[baseStyles, overridingStyles]"></div>
        
  • 【问】事件绑定指令如何使用?(v-on:事件=方法

    Note

    • vue提供了v-on事件绑定指令,用于为DOM元素绑定事件监听。原生DOM对象有onclickoninputonkeyup原生事件替换为vue的事件绑定形式后,分别为v-on:clickv-on:inputv-on:keyup

    • v-on:可以用@进行简写(v-on:语法糖)

    • v-on指令所绑定的事件处理函数,可以接收事件参数对象event

    • $event是vue提供的特殊变量,用来表示原生的事件参数对象event

    • 参考代码如下:

      <button @click="add">自增</botton>
      <button @click="changeColor">变色</botton>
      data(){
          return{
              count:'',
          }
      }
      methods:{
          add(){
              this.count++;    
          },
          changeColor(e){
                  e.target.style.backgroundColor='red';
          }
      }
      
  • 【问】事件修饰符,按钮修饰符是什么?

    Note

    • 事件修饰符:在事件处理函数中调用event.preventDefault()取消在浏览器中事件的默认动作)或event.stopPropagagation()阻止事件冒泡和捕获)是非常常见的需求。参考stopPropagation, preventDefault的区别
      vue提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的5个事件修饰符如下:参考[事件冒泡概念]((https://baike.baidu.com/item/%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1/4211429?fr=aladdin)

      .prevent阻止默认行为(例如:阻止a连接的跳转、阻止表单的提交等)
      .stop阻止事件冒泡(即阻止传播到责任链中下一个事件处理器)
      .capture以捕获模式触发当前的事件处理函数
      .once绑定的事件只触发1次
      .self只有在event.target是当前元素自身时触发事件处理函数
  • 按钮修饰符:在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如

    <!--只有在"key"'Enter'时调用'vm.submit()'...>
    <input @keyup.enter="submit">
    
    <!--只有在'key''Esc'时调用'vm.clearInput()'...>
    <input @keyup.esc="clearInput">
    
  • 【问】双向绑定指令如何使用?(v-model 原理为v-on:input + v-bind:value;v-model对radio,checkbox,select等进行双向绑定)

    Note

    • vue提供了v-model双向数据绑定指令,用来辅助开发者在不操作DOM的前提下,快速获取表单的数据

      v-model其实是一个语法糖,它的背后本质上是包含两个操作:

      1. v-bind绑定一个value属性

      2. v-on指令给当前元素绑定input事件在普通input对应的input事件中,vue已经帮我们写好逻辑了

      也就是说下面的代码:等同于下面的代码:

      <input type="text" v-model="message">
      等同于
      <input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
      
    • v-model:radio复选框分为两种情况:单个勾选框和多个勾选框

      单个勾选框v-model即为布尔值。此时inputvalue并不影响v-model的值。参考https://jiuaidu.com/jianzhan/832729/

      <template>
           <div id="app">
               <input type="radio" id="male" value="male" v-model="gender"> male
               <input type="radio" id="female" value="female" v-model="gender"> femalea
      
              <p>{{gender}}</p>
           </div>
      </template>
      
      <script>
      export default {
           name: 'app',
           data () {
               return {
                gender: ''
               }
           }
      }
      </script>
      
    • v-model:checkbox

      • 单个勾选框:

        • v-model即为布尔值。

        • 此时input的value并不影响v-model的值。

      • 多个复选框:当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。当选中某一个时,就会inputvalue添加到数组中

        代码:

        <div id = "app">
          <!--单个复选框-->
          <label for="check">
              <input type="checkbox" v-model="checked" id="check">同意协议
          </label>
          <p>是否选中:{{checked}} <p>
        
          <!--多个复选框-->
          <label><input type="checkbox" v-model="hobbies"value="篮球">篮球</Label>
          <label><input type="checkbox" v-model="hobbies"value="足球">足球</Label>
          <label><input type="checkbox" v-model="hobbies"value="台球">台球</Label>
          <p>您选中的爱好:{{hobbies}}</p>
        </div>
        
        <script>
        let app = new Vue ({
          el:'#app',
          data:{
              checked : false ,
              hobbies: []
          }
        })
        </script >
        
    • v-model:select:和checkbox一样,select也分单选和多选两种情况。

      • 单选:只能选中一个值

        • v-model绑定的是一个值

        • 当我们选中option中的一个时,会将它对应的value赋值到mySelect中

        代码:

        <!--选择一个值-->
        <select v-model="mySelect">
            <option value="apple">苹果</option>
            <option value="orange">橘子</option>
            <option value="banana">香蕉</option>
        </select>
        <p>您最喜欢的水果:{{mySelect}} </p>
        ...
        
      • 多选:可以选中多个值

        • v-model绑定的是一个数组

        • 当选中多个值时,就会将选中的option对应的value添加到数组mySelects中

        代码:

        <select v-model="mySelect" multiple>
            <option value="apple">苹果</option>
            <option value="orange">橘子</option>
            <option value="banana">香蕉</option>
        </select>
        <p>您最喜欢的水果:{{mySelect}} </p>
        
        <script>
        let app = new Vue ({
            el:'#app',
            data:{
                mySelect:'apple',
                mySelects : []
            }
        })
        </script>
        
  • 【问】条件渲染指令如何使用?它们之间有什么区别(v-if动态添加DOM元素,有更高的切换开销;v-show动态添加样式,有更高的初始渲染开销)

    Note

    • 条件渲染指令包括:v-ifv-show

    • 实现原理不同:

      • v-if指令会动态地创建或移除DOM元素,从而控制元素在页面上的显示与隐藏;

      • v-show指令会动态为元素添加或移除style="display:none;"样式,从而控制元素的显示与隐藏;

    • 性能消耗不同:

      v-if更高的切换开销,而v-show更高的初始渲染开销。因此:

      • 如果需要非常频繁地切换,则使用v-show较好
      • 如果在运行时条件很少改变,则使用v-if 较好
    • 两者使用的区别:

      • v-ifv-elsev-else-if条件性的渲染某元素,判定为true时渲染,否则不渲染;

      • v-show条件不满足令其displaynone

      <div v-if="score<60">不及格</div>
      <div v-else-if="60<=score&&score<90">中等</div>
      <div v-else="score>=90">优秀</div>
      
      <div v-show="true">display:block显示</div>
      <div v-show="false">display:none隐藏</div>
      
  • 【问】列表渲染指令如何使用?(v-for常配合checkbox、options、li标签使用)

    Note

    • vue提供了v-for列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构v-for指令需要使用item in items形式的特殊语法,其中:

      • items是待循环的数组
      • item是被循环的每一项

      代码:

      <ul id='app'>
          <li v-for="item in list"> 姓名是:{{item.name}} </li>
      </ul>
      
      <script>
      let app = new Vue ({
          el:'#app',
          data:{
              list: [
                  {'id' : 1, 'name': 'wang'},
                  {'id' : 2, 'name': 'liu'}
              ]
          }
      })
      </script>
      
    • v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为(item,index) in items,示例代码如下(itemindex变量名可随便取):

      <ul id='app'>
          <li v-for="(item,index) in list">{{index + 1}}个同学,姓名是:{{item.name}} </li>
      </ul>
      
    • 【问】如何提高列表渲染指令的性能?(v-for在vue2.2+之后必须使用:key来提高渲染效率),参考列表渲染 — Vue.js

      Note

      • 当列表的数据变化时,默认情况下,vue会尽可能的复用已存在的DOM元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。

      • 为了给vue一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的key属性:

        <ul id='app'>
            <li v-for="(user,i) in list" :key="user.id"> 姓名是:{{item.name}} </li>
        </ul>
        
  • 【问】keep-alive的基本使用?

    Note

    • keep-alive是什么

      • 组件被缓存时,会自动触发组件的deactivated生命周期函数。

      • 组件被激活时,会自动触发组件的activated生命周期函数。

      • 当组件第一次被创建,会执行created生命周期函数,也会执行activated生命周期函数。之后组件再被激活,只会触发activated而不会触发created

    • keep-alive的基本使用以及基本属性:

      <keep-alive>
          <组件名></组件名>
      <keep-alive>
      
      • include包含的组件(可以为字符串,数组,以及正则表达式,只有名称匹配的组件会被缓存)。

        // 只缓存组件name为a和b的组件
        <keep-alive include="a,b"> 
          <component />
        </keep-alive>
        
      • exclude排除的组件(可以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。

        // 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染)
        <keep-alive exclude="c"> 
          <component />
        </keep-alive>
        
        // 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件
        <keep-alive include="a,b" exclude="b"> 
          <component />
        </keep-alive>
        
      • max缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数);

        // 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
        <keep-alive exclude="c" max="5"> 
          <component />
        </keep-alive>
        
  • 【问】侦听器watch的作用及如何使用?(侦听器主要用于监听data中存储的数据对象是否发生变化;设置deep得到嵌套侦听器;设置immediate得到即时回调侦听器),参考侦听器 | Vue.js《Vue 入门教程》Vue 侦听器

    Note

    • watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。有两种创建方式:

      • 方法格式的侦听器:watch : { key : function(new,old){} }

      • 对象格式的侦听器:watch : {key : {handler(new,old){}, deep, immediate }支持创建嵌套侦听器和回调侦听器

    • 侦听器简单例子:

      • 侦听器 watch 实际是 vue 实例上的一个对象属性。当我们需要对 vue 实例上某个属性进行侦听时,我们以需要被侦听的属性名作为 watch 对象的键,以一个函数 function 作为该键的值

      • 函数 function 接收两个参数:侦听数据变化之后的值newValue;侦听数据变化之前的值oldValue

      var vm = new Vue({
        el: '#app',
        data() {
          return {
            count: 0
          }
        },
        watch: {
          count: function(newVal, oldVal) {
            // 具体处理逻辑
          },
        }
      })
      
    • 深层侦听器watch 默认是浅层的,仅在被赋新值时,才会触发回调函数。如果想侦听所有嵌套的变更需要将deep选项设置为true,得到使用深层侦听器;

      export default {
        watch: {
          question : {
            handler(newValue, oldValue) {
              // 注意:在嵌套的变更中,
              // 只要没有替换对象本身,
              // 那么这里的 `newValue` 和 `oldValue` 相同
            },
            deep: true
          }
        }
      }
      
    • 即时回调的侦听器watch 默认是懒执行的,仅当数据源变化时,才会执行回调。但在某些场景中,举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据,即在创建侦听器时,立即执行一遍回调。这里可以为侦听器设置 immediate: rue强制回调函数立即执行

      export default {
        // ...
        watch: {
          question: {
            handler(newQuestion) {
              // 在组件实例创建时会立即调用
            },
            // 强制立即执行回调
            immediate: true
          }
        }
        // ...
      }
      
    • 侦听器综合案例(对字典、整型进行监听):参考Vue基础之侦听器详解

      const app = createApp({
        data() {
          return {
            a: 1,
            b: 2,
            c: {
              d: 4
            },
            e: 5,
            f: 6
          }
        },
        watch: {
          // 侦听顶级 property
          a(val, oldVal) {
            console.log(`new: ${val}, old: ${oldVal}`)
          },
          // 字符串方法名
          b: 'someMethod',
          // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
          c: {
            handler(val, oldVal) {
              console.log('c changed')
            },
            deep: true
          },
          // 侦听单个嵌套 property
          'c.d': function (val, oldVal) {
            // do something
          },
          // 该回调将会在侦听开始之后被立即调用
          e: {
            handler(val, oldVal) {
              console.log('e changed')
            },
            immediate: true
          },
          // 你可以传入回调数组,它们会被逐一调用
          f: [
            'handle1',
            function handle2(val, oldVal) {
              console.log('handle2 triggered')
            },
            {
              handler: function handle3(val, oldVal) {
                console.log('handle3 triggered')
              }
              /* ... */
            }
          ]
        },
        methods: {
          someMethod() {
            console.log('b changed')
          },
          handle1() {
            console.log('handle 1 triggered')
          }
        }
      })
      const vm = app.mount('#app')
      vm.a = 3 // => new: 3, old: 1
      
  • 【问】计算属性computed是什么?(如果相关数据发生改变,computed会重新计算并返回结果,有点类似watch侦听的意思,应该也是采用观察者设计模式;默认走缓存),参考计算属性

    Note

    • 关于computed计算属性的理解:

      1、computed 和 data同级,计算属性写在computed中

      2、写起来像方法,用起来像属性

      3、计算属性虽然称为属性,但其本质是一个函数;

      4、虽然计算属性本质是一个函数,但是在页面中使用计算属性时,不要加()

      5、一定要有返回值

      6、可以使用data中的已知值;

      7、只要跟计算属性相关的数据发生了变化,计算属性就会重新计算,不相关的属性无论如何变化,都不会导致计算属性变化;

      8、计算属性名不能和data中的数据重名(因为要使用data中的数据)。

    • 使用例子:

      <template>
          {{reversedMsg}}
      </template>
      
      export default {
      
        data(){
          return{
              msg : ""
          }
        },
      
        computed: {
          reversedMsg(){
            return this.msg.split('').reverse().join('')
          }
        }
        // ...
      }
      
  • 【问】computed与watch的区别和使用场景?,参考Computed 和 Watch 的区别

    Note

    • computed计算属性的作用:

      • 1)解决模板中放入过多的逻辑会让模板过重且难以维护的问题。例如两个数据的拼接或字体颜色的判断。

      • 2)它支持缓存,只有依赖的数据发生了变化,才会重新计算。例如模板中多次用到数据拼接可以用计算属性,只执行一次计算,除非数据发生变化。

      • 3)不支持异步,如果有异步操作,无法监听数据的变化。

      • 4)如果属性值是函数,默认使用get方法,函数的返回值就是属性的属性值。还有一个set方法,当数据变化时就会调用set方法

      • 5)computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。

    • watch侦听器的作用:

      • 1)它不支持缓存,数据变化时,它就会触发相应的操作。

      • 2)支持异步监听

      • 3)接受两个参数,第一个是最新的值,第二个是变化之前的值。

      • 4)监听data或者props传来的数据,发生变化时会触发相应操作。有两个参数:

        • immediate:立即触发回调函数。

        • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

    • computedwatch的使用场景:

      • computed:是多对一,多个数据影响一个。当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算(缓存多个值)。

      • watch:是一对多,一个数据发生变化,执行相应操作会影响多个数据。当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

三、组件化 & 模块化开发(组件化系统、全局和局部组件、父子组件通讯 - 传参 & 事件监听与回调、兄弟通讯-共享变量绑定多个事件、插槽的使用)

Note:不使用VueCDN源也可以创建父子组件,并实现通信,参考不用vue-cli 创建vue组件

  • 【问】Vue组件化系统的基础概念
    Note

    • 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。其实在HTML中,几乎任意类型的应用界面都可以抽象为一个组件树
      在这里插入图片描述

    • Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例

  • 【问】Vue中如何注册组件?(Vue.extend()创建组件,Vue.component()注册组件)

    Note

    • 组件的使用分成三个步骤:

      • 创建组件构造器;
      • 注册组件;
      • 使用组件;
    • 组件使用的简单案例(这里的组件是全局的组件):Vue.extend()这个方法 如果发现vue.extend不是方法,换其他cdn源试试,之前试了https://unpkg.com/vue@3/dist/vue.global.js没效果。

      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
      <body>
          <div id="app1">
              <my-cpn></my-cpn>
          </div>
      
          <div id="app2">
              <my-cpn></my-cpn>
          </div>
      </body>
      
      <script>
      //1.创建组件构造器
      const myComponent = Vue.extend({
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是组件中的一个段落内容</p>
              </div>`
          });
      
      //2.注册组件,并且定义组件标签的名称
      Vue.component('my-cpn',myComponent)
      
      let app = new Vue({
         el:'#app1'
      })
      
      let app1 = new Vue({
         el:'#app2'
      })
      </script>
      

      效果如下:
      在这里插入图片描述

    • Vue.extend():调用Vue.extend()创建的是一个组件构造器

      • 通常在创建组件构造器时,传入template代表我们自定义组件的模板

      • 该模板就是在使用到组件的地方,要显示的HTML代码。

      • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。

    • Vue.component():调用Vue.component()将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。所以需要传递两个参数:

      • 注册组件的标签名

      • 组件构造器

      • 组件必须挂载在某个Vue实例下,否则它不会生效。

  • 【问】什么是全局组件和局部组件?(局部组件在Vue实例中通过components :{组件标签 : 组件名}注册,全局组件通过Vue.component()全局注册)

    Note

    • 当我们通过调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

    • 全局组件看上一问代码;

    • 局部组件代码如下(效果为app2对应的div没有被渲染):

      <!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> -->
      
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
      <body>
          <div id="app1">
              <my-cpn></my-cpn>
          </div>
      
          <div id="app2">
              <my-cpn></my-cpn>
          </div>
      </body>
      
      <script>
      //1.创建组件构造器
      const myComponent = Vue.extend({
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是组件中的一个段落内容</p>
              </div>`
          });
      
      let app = new Vue({
         el:'#app1',
         components : {
            'my-cpn' : myComponent
         }
      })
      
      let app1 = new Vue({
         el:'#app2'
      })
      </script>
      

      效果如下:my-cpn只在挂载了app1Vue实例上可见
      在这里插入图片描述

  • 【问】什么是父组件和子组件?(在父组件创建(Vue.extend())时通过components属性引入子组件;实例注册了父组件,父组件中注册了子组件为child-cpn,此时实例访问不到子组件child-cpn

    Note

    • 父组件和子组件:组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。我们来看通过代码如何组成的这种层级关系(在父组件创建(Vue.extend())时通过components属性引入子组件):

      <!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> -->
      
      <!-- vue的template中只能有一个根节点 -->
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
      <body>
          <div id="app1">
              <parent-cpn></parent-cpn>
              <p>Hello world</p>
              <child-cpn></child-cpn>
          </div>
      
          <div id="app2">
              <child-cpn></child-cpn>
          </div>
      </body>
      
      <script>
      //1.创建组件构造器
      const childComponent = Vue.extend({
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是子组件</p>
              </div>`
          });
      
      const parentComponent = Vue.extend({
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是父组件</p>
                  <child-cpn></child-cpn>
              </div>`,
          components : {
              "child-cpn" : childComponent
          }
      });
      
      let app = new Vue({
         el:'#app1',
         components : {
            'parent-cpn' : parentComponent
         }
      })
      
      let app1 = new Vue({
         el:'#app2',
         components : {
            "child-cpn" : childComponent
         }
      })
      </script>
      

      效果如下:app1无法识别<child-cpn>app2可以识别<child-cpn>
      在这里插入图片描述

    • 父子组件错误用法:以子标签的形式在Vue实例中使用

      • 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块

      • 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)

      • <child-cpn></child-cpn>是只能在父组件中被识别的。类似这种用法,<child-cpn></child-cpn>是会被浏览器忽略的。

  • 【问】父组件和子组件之间如何通讯?(渲染父组件时会同时渲染子组件;父传子用props,子组件通过props定义要接收到参数;子传父用$emit:子组件定义事件,父组件定义方法)

    Note

    • 父传子用props:子组件通过props定义要接收到参数

      • 第一步:引入子组件。

      • 第二步:在数据源中定义要传入子组件的数据parentMsg

      • 第三步:在使用child组件时传入parentMsg<child :自定义属性名="parentMsg"></child>

      • 第四步:在子组件中,要 props:['自定义属性名']来接收传过来的参数。

      父组件参考代码

      <template>
        <div>
          <h2>parent</h2>
          <!--3、传入parentMsg-->
          <child :visible="visible"></chld>
        </div>
      </template>
      <script>
      //1、引入子组件
      import child from './child.vue'
      export default {
          data() {
              return {
                  //2、定义要传入子组件的数据parentMsg
                  visible:'true'
              }
          },
          components:{
              child
          }
      }
      </script>
      

      子组件参考代码

      <template>
        <div>
          {{visible}}
        </div>
      </template>
      <script>
      export default {
          name:'child',
          props:['visible']//接收
      }
      </script>
      

      如果使用vuecdn源,将所有Vue实例(封装着template属性)写在一个html里,则可以在同一个html页面上实现父组件向子组件传值

      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
      <body>
          <div id="app1">
              <parent-cpn></parent-cpn>
          </div>
      
          <div id="app2">
              <child-cpn></child-cpn>
          </div>
      </body>
      
      <script>
      //1 创建子组件
      const childComponent = Vue.extend({
          name : 'child',
          props : ['visible'],
          template:
              `<div>
                  <h3>{{visible}}</h3>
                  <p>我是子组件</p>
              </div>`
          });
      
      //2 创建父组件
      const parentComponent = Vue.extend({
          data() {
              return {
                  //2、定义要传入子组件的数据parentMsg
                  visible : '进入子组件'
              }
          },
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是父组件</p>
                  <child-cpn :visible=visible></<child-cpn>>
              </div>`,
      
          components : {
              "child-cpn" : childComponent
          }
      });
      
      let app = new Vue({
         el:'#app1',
         components : {
            'parent-cpn' : parentComponent
         }
      })
      
      let app1 = new Vue({
         el:'#app2',
         components : {
            "child-cpn" : childComponent
         }
      })
      </script>
      
      

      效果图如下:
      在这里插入图片描述

    • 子传父用$emit:子组件定义事件标识(sendmsg),通过this.$emit(‘sendmsg’,所需要传的值)向父组件发送该事件标识,父组件通过方法(getmsg)来监听指定事件标识中绑定的值(事件监听)。

      emit使用方法:this.$emit(‘自定义事件名’,所需要传的值)

      • 第一步:首先在子组件中定义一个事件,并且使用emit发送给父组件,在示例中子组件使用的click事件触发发送自定义事件(sendmsg)

      • 第二步:在父组件中需要定义方法(getmsg)接受自定义事件(sendmsg)

      • 第三步:在使用子组件时,<child @sendmsg="getmsg">

      【子组件】发送值代码:

      <template>
        <div>
          <button @click="childmsg">点我试试</button>
        </div>
      </template>
      <script>
      export default {
          name:'child',
          data() {
              return {
                   msg:"This is the first word from child"
              }
          },
          methods:{
              //点击按钮则向父组件传自定义事件sendmsg,childmsg
              childmsg(){
                  this.$emit('sendmsg',this.msg)
              }
          }
      }
      </script>
      

      【父组件】接收值代码:

      <template>
        <div>
          <child @sendmsg="getmsg"></child>
        </div>
      </template>
      <script>
      import child from './child.vue'
      export default {
          data() {
              return {
              }
          },
          components:{
              child
          },
          methods:{
              getmsg(val){
                  console.log(val)
              }
          }
      }
      </script>
      

      如果使用vuecdn源,将所有Vue实例(封装着template属性)写在一个html里,则可以在同一个html页面上实现子组件向父组件传值

      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
      <body>
          <div id="app1">
              <parent-cpn></parent-cpn>
          </div>
      
          <div id="app2">
              <child-cpn></child-cpn>
          </div>
      </body>
      
      <script>
      //1 创建子组件
      const childComponent = Vue.extend({
          name : 'child',
          props : ['visible'],
          template:
              `<div>
                  <h3>{{visible}}</h3>
                  <p>我是子组件</p>
                  <button @click="sendMsg">点击我向父组件传值</button>
              </div>`,
          methods : {
              sendMsg(){
                  this.$emit("getChildMsg","child出来了");  //"getChildMsg"是子组件向父组件暴露的参数方法标识
              }
          }
      });
      
      //2 创建父组件
      const parentComponent = Vue.extend({
          data() {
              return {
                  //2、定义要传入子组件的数据parentMsg
                  childMsg : "",
                  visible : '进入子组件'
              }
          },
          template:
              `<div>
                  <h2>组件标题</h2>
                  <p>我是父组件</p>
                  <child-cpn :visible=visible @getChildMsg="getMsg"></child-cpn>
                  <p>{{childMsg}}</p>
              </div>`,
      
          components : {
              "child-cpn" : childComponent
          },
      
          methods : {
              getMsg(val){
                  this.childMsg = val;
              }
          }
      });
      
      let app = new Vue({
         el:'#app1',
         components : {
            'parent-cpn' : parentComponent
         }
      })
      
      let app1 = new Vue({
         el:'#app2',
         components : {
            "child-cpn" : childComponent
         }
      })
      </script>
      

      实现效果如下:
      在这里插入图片描述

  • 【问】兄弟组件是什么?其之间如何进行数据共享?(可通过EventBus事件总线对象实现,即$emit发送数据(等价notify),$on监听数据(等价wait),基于事件驱动通信,参考Vue 兄弟组件之间的通信

    Note

    • Vue中实现兄弟组件的通讯一般为2种方式:

      1. 一种方法是让父组件允当两个子组件之间的中间件(中继)

      2. 另一种就是使用EventBus(事件总线),它允许两个子组件之间直接通讯,而不需要涉及父组件。

      由于中继方式比较简单,这里不做赘述,只讲EventBus方式。

    • EventBus事件总线方式:

      • 第一步:在兄弟组件同目录下创建eventBus.js,然后创建vue实例:

        import Vue from 'vue'
        export default new Vue()
        
      • 第二步:在【兄弟组件A】中,引入eventBus.js(定义为bus对象),接着定义数据msg,编写方法用于在监听到share事件后发送msg

        import bus from './eventBus.js'
        <button @click="sendMsg">
        export default{
         data(){
           return{
                   msg:'hello'  
             }  
         },
         methods:{
             sendMsg(){
                 bus.$emit('share',this.msg); 
            } 
          }
        }
        
      • 第三步:在【兄弟组件B】中,引入eventBus.js,定义数据newMsg,编写方法用于接收msg和赋值给newMsg

        import bus from './eventBus.js'
        <button @click="sendMsg">
        export default{
        data(){
            return{
                newMsg:[] 
            } 
        },
        created:{
          bus.$on('share',val=>{
            this.newMsg=val;  
          }) 
        }
        }
        
      • 如果使用vuecdn源,将所有Vue实例(封装着template属性)写在一个html里,则可以在同一个html页面上实现子组件向父组件传值

         <!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> -->
      
         <!-- vue的template中只能有一个根节点 -->
         <html lang="en">
         <head>
             <meta charset="UTF-8">
             <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
         </head>
         <body>
             <div id="app1">
                 <bro1-cpn></bro1-cpn>
             </div>
         
             <div id="app2">
                 <bro2-cpn></bro2-cpn>
             </div>
         </body>
         
         <script>
         //1. 创建总线对象
         bus1 = new Vue();  //兄弟1向兄弟2传值 或者 兄弟2向兄弟1传值
         // bus2 = new Vue();  //兄弟2向兄弟1传值
         
         //事件监听
         bus1.$on('getMsg1', function (val) {
                 alert(val)
         })
         bus1.$on('getMsg2', function (val) {
                 alert(val)
         })
         
         //2. 创建兄弟组件1
         const brotherComp1 = Vue.extend({
             template:
                 `<div>
                     <p>我是兄弟组件1</p>
                     <button @click="sendMsg">点击我向兄弟组件2传值</button>
                 </div>`,
             methods : {
                 sendMsg(){
                     bus1.$emit("getMsg1","兄弟2,我来了");  //"getChildMsg"是子组件向父组件暴露的参数方法标识
                 }
             }
         });
         
         //3. 创建兄弟组件2
         const brotherComp2 = Vue.extend({
         
             template:
                 `<div>
                     <p>我是兄弟组件2</p>
                     <button @click="sendMsg">点击我向兄弟组件1传值</button>
                 </div>`,
         
             methods : {
                 sendMsg(){
                     bus1.$emit("getMsg2","兄弟1,我来了");
                 }
             }
         });
         
         let app = new Vue({
            el:'#app1',
            components : {
               'bro1-cpn' : brotherComp1
            }
         })
         
         let app1 = new Vue({
            el:'#app2',
            components : {
               "bro2-cpn" : brotherComp2
            }
         })
         </script>
      

      实现效果如下:
      在这里插入图片描述

    • 父子组件通信 vs 兄弟组件通信

      • 这里声明事件share字符串, 兄弟组件之间通过Vue实例对象进行通信,通过obj.$emit向其他兄弟组件发送关于share事件的消息,其他兄弟组件通过obj.$on监听share事件的消息(即对象的waitnotify操作)
      • 和父子组件通信不同,兄弟组件可以通过一个共享Vue实例(bus),该实例可以绑定多个事件标识(share1share2)来实现通信,而父组件向子组件传参时,由于其自身嵌套关系
        • 父组件传值给子组件时相当于函数调用,父组件不用使用$emit,子组件不用使用$on
        • 子组件传值给父组件时相当于函数回调,父组件通过对指定的事件标识进行监听(不用使用$on),而子组件通过$emit进行回调,告知父组件已经处理好值了。
  • 【问】插槽是什么?插槽的用法(需要先理解父子组件之间如何通讯;默认插槽,具名插槽,作用域插槽(默认,具名,解构),动态插槽名),参考插槽 Slots | Vue.js

    Note

    • 在学习插槽之前需要先理解父子组件之间如何通讯
    • 总体思想是父组件在子组件的指定槽位中定制自己的模板(template),然后由子组件渲染模板内容(子组件中的slot起到占位符的作用)。
    • v-slot的基本用法:父组件可以通过子组件中的插槽显示自定义的内容

      • vue 官方规定:每一个 slot 插槽都要有一个name名称
        如果省略了 slotname则有一个默认名称 default
        默认情况下,使用组件时提供的内容会被填充到 namedefault 的插槽内。

      • 使用 v-slot:xxx, 其中 xxx为插槽 name 值,只能放在父组件标签内

      • v-slot是一个虚拟标签,只起到包裹性质的作用,不会被渲染为实质性的 html 元素,v-slot:xxx 可以简写为 #xxx

      • v-slot在父子组件中的使用:

        【父组件声明了子组件为Left,并使用具名插槽】
        <Left>
            <template v-slot:mySlot>
                <p>这是在 Left 组件声明的p标签</p>
            </template>
        </Left>
        
        【子组件定义了mySlot插槽】
        <div style="color:#33e;background:#ee2">
            <slot name="mySlot"></slot>
        </div>
        

        在这里插入图片描述

    • 默认插槽(default):

      • 当使用组件指定了插槽内容时,优先显示指定的内容
        当没有指定内容时,渲染 slot 标签内的默认内容

      • 简单演示:父组件显式提供的内容会取代子组件的默认内容

        //父组件声明子组件为SubmitButton
        <SubmitButton>Save</SubmitButton>
        
        //子组件默认内容
        <button type="submit">
          <slot>
            Submit <!-- 默认内容 -->
          </slot>
        </button>
        
    • 具名插槽(name):

      • 一个组件中包含多个插槽出口是很有用的,为了将内容置入组件的不同插槽中,要用带有name属性的插槽

      • 简单演示:没有提供 name<slot> 出口会隐式地命名为 “default”(默认插槽) 。

        //子组件定义多个插槽
        <div class="container">
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        </div>
        

        在父组件中使用 <BaseLayout> 时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了

        //父组件将子组件声明为<BaseLayout>
        <BaseLayout>
          <template v-slot:header>
            <!-- header 插槽的内容放这里 -->
          </template>
        </BaseLayout>
        
      • 具名插槽传入内容时需要使用一个含 v-slot 指令的 <template> 元素;v-slot 有对应的简写#,因此 <template v-slot:header> 可以简写为 <template #header>,意思是:将这部分模板片段传入子组件的 header 插槽中
        在这里插入图片描述

    • 渲染作用域 & 作用域插槽(通过v-slot传入props获取子组件插槽的属性):

      • 渲染作用域插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。

        <span>{{ message }}</span>
        <FancyButton>{{ message }}</FancyButton>
        

        这里的两个 {{ message }} 插值表达式渲染的内容都是一样的。

        插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

        父组件模板中的表达式只能访问父组件的作用域子组件模板中的表达式只能访问子组件的作用域

      • 作用域插槽:在上面的渲染作用域可知,插槽的内容无法访问到子组件的状态。然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。这里可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes

        • 条件:在封装组件时,为预留的 <slot> 提供属性对应的值

        • 格式:

          1)子组件:<slot v-bind:username='username' name='box1'></slot>

          2)父组件:<template v-slot:box1='username_props'>
          在封装组件时,为预留的 <slot> 提供属性对应的值,叫做作用域插槽。这些属性对应的值可以在父组件中访问到,默认为空对象。

        • 默认作用域插槽演示:

          //父组件
          <MyComponent v-slot="slotProps">
              {{ slotProps.text }} {{ slotProps.count }}
          </MyComponent>
          

          在这里插入图片描述

          v-slot="slotProps" 可以类比这里的函数签名,和函数的参数类似,我们也可以在 v-slot 中使用解构

          //父组件
          <MyComponent v-slot="{text,count}">
              {{ text }} {{ count }}
          </MyComponent>
          
        • 具名作用域插槽演示:
          具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps",当使用缩写时是这样#name="slotProps"

          //子组件
          <slot name="header" message="hello"></slot>
          
          //父组件
          <MyComponent>
            <template #header="headerProps">
              {{ headerProps }}
            </template>
          
            <template #default="defaultProps">
              {{ defaultProps }}
            </template>
          
            <template #footer="footerProps">
              {{ footerProps }}
            </template>
          </MyComponent>
          

          最终 headerProps 的结果是 { message: 'hello' }

      • 作用域插槽的例子:

        子组件在其作用域下完成与user对象的绑定

        <div>
            <slot v-bind:user="user" name="box3"></slot>
            <slot v-bind:msg="hello world" name="box4"></slot>
        </div>
        
        data(){
            return:{
                user:{
                      firstname:'lan',
                      lastname:'chun'
                    }    
            }
        }
        

        父组件通过向插槽传props,可以获取到子组件的user对象值

        <子组件名>
            <template v-slot:box3="slotProps1">
                {{slotProps1.user.firstname}}
                {{slotProps1.user.lastname}}
            </template>
            <template v-slot:box4="slotProps2">
                {{slotProps2.msg}}
            </template>
        </子组件名>
        
      • 动态插槽名(在插槽v-slot上使用动态参数)

        • 动态参数:参考模板语法 | Vue.js

          同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

          <!--
          注意,参数表达式有一些约束,
          参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
          -->
          <a v-bind:[attributeName]="url"> ... </a>
          
          <!-- 简写 -->
          <a :[attributeName]="url"> ... </a>
          

          这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

          相似地,你还可以将一个函数绑定到动态的事件名称上:

          <a v-on:[eventName]="doSomething"> ... </a>
          
          <!-- 简写 -->
          <a @[eventName]="doSomething">
          

          在此示例中,当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus

        • 动态插槽名

          动态指令参数v-slot 上也是有效的,即可以定义下面这样的动态插槽名

          <base-layout>
            <template v-slot:[dynamicSlotName]>
              ...
            </template>
          
            <!-- 缩写为 -->
            <template #[dynamicSlotName]>
              ...
            </template>
          </base-layout>
          

          注意这里的表达式和动态指令参数受相同的语法限制

四、Vue Cli和Webpack(打包压缩静态资源&依赖模块,热加载)

参考webpack看这一篇就够了webpack详解webpack官网

  • 【问】什么是前端的模块化开发?

    Note

    • 为什么要进行前端的模块化开发? 参考Vue 知识点汇总(上)

    • 前端模块化的一些方案:AMD、CMD、CommonJSES6

    • ES6之前,要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发,并且在通过模块化开发完成了项目后,还需要处理模块化间的各种依赖,并且将其进行整合打包。

    • 此时出现webpack,其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系,而不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用。

  • 【问】什么是Webpack?Webpack能做什么?

    Note

    • webpack本身是node的一个第三方模块包, 用于打包代码

    • webpackjavascript 应用程序的静态资源模块的打包器 (module bundler),打包的过程中,还可对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。

    • 把很多文件打包整合到一起, 缩小项目体积, 提高加载速度,常见功能如下:

      • less/sass 处理成 css

      • ES6/7/8 处理成 ES5

      • 支持js模块化

      • 处理css兼容性

      • html/css/js进行压缩合并

  • 【问】如何理解前端的打包过程?(文件压缩,模块间的依赖关系处理)

    Note

    • 前端打包工具包括:webpackgruntgulp

    • 前端的打包过程可以通过webpack来理解:webpack帮助我们进行模块化,并且处理模块间的各种复杂关系

  • 【问】Webpack的基本使用?(打包命令npm run build

    Note

    • Webpack项目结构

      • 新建项目目录,目录结构和之前的规范不同

      • 根目录创建public,创建index.html

      • 创建src存放代码资源文件,创建index.js

      • 将逻辑进行模块化,并在index.html引入模块化的js文件

    • Webpack的使用

      • 初始化包环境

        yarn init
        
      • 安装依赖包

        yarn add webpack webpack-cli -D
        
      • 配置scripts(自定义命令)

        scripts: {
         "build": "webpack"
        }
        
      • 运行打包命令

        yarn build
        #或者 npm run build
        
  • 【问】Webpack打包的入口和出口?打包的流程?

    Note

    • webpack打包时指定的入口文件是哪个?默认情况下,webpack 会将项目根目录下的 src/index.js 文件作为入口文件。可以在 webpack 的配置文件 webpack.config.js 中通过 entry 属性来指定入口文件(ChatGPT给出的答案):

      module.exports = {
      	entry: './src/index.js'
      };
      

      如果项目中存在多个入口文件,可以entry属性设置为一个对象,并为每一个入口文件指定对应的名称:

      module.exports = {
        entry: {
          app: './src/app.js',
          login: './src/login.js'
        }
      };
      

      通过这种方式,webpack将多个入口文件打包成多个捆绑(bundle),并生成对应的静态资源文件。
      总之,webpack打包时的入口文件可以通过配置文件中的 entry 属性来指定,默认情况下会将根目录下的 src/index.js 文件作为入口文件进行打包。

    • webpack的入口和出口:告诉webpack从哪开始打包, 打包后输出到哪里

      • 默认入口为./src/index.js,默认出口为./dist/main.js

      • webpack配置:在src的并列处新建webpack.config.js,填入配置项

        const path = require("path")
        
        module.exports = {
            entry: "./src/main.js", // 入口
            output: { 
                path: path.join(__dirname, "dist"), // 出口路径
                filename: "bundle.js" // 出口文件名
            }
        }
        

        修改package.json,自定义打包命令 ,让webpack使用配置文件

        "scripts": {
            "build": "webpack"
        }
        
    • 打包流程图:所有要被打包的资源都要跟入口产生直接/间接的引用关系

      在这里插入图片描述

  • 【问】Webpack如何在打包后生成html,并自动引入打包好的js?(html-webpack-plugin

    Note

    • html-webpack-plugin插件, 让webpack打包后生成html文件并自动引入打包后的jshtml-webpack-plugin插件地址

    • 配置步骤如下:

      • 下载插件

        yarn add html-webpack-plugin  -D
        
      • 配置webpack.config.js

        // 引入自动生成 html 的插件
        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        module.exports = {
            // ...省略其他代码
            plugins: [
                new HtmlWebpackPlugin()
            ]
        }
        
      • 打包后的index.html自动引入打包后的js文件

      • 自定义打包的html模版,和输出文件名字

        plugins: [
          new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html'
          })
        ]
           ]
        }
        
  • 【问】Webpack打包的模式(mode)有哪几种?

    Note

    • mode模式分为开发阶段和发布阶段

      • development 开发阶段,简易打包,打包速度快

      • production 发布阶段,打包精细,打包速度慢(但是没关系不会经常production)

      mode: 'development || production'
      
  • 【问】什么是Webpack开发服务器?(Webpack-dev-server对更新的文件进行打包,其他内容用缓存的即可)

    Note

    • webpack-dev-server 是一个小型的 Node.js Express 服务器,可以用来快速开发应用程序。它主要提供了以下功能(ChatGPT的答案):

      • 自动重新加载:当 webpack 打包后的文件发生变化时,webpack-dev-server会自动重新加载页面。
      • 热更新:当项目中的源代码发生变化时,webpack-dev-server会自动将变更的代码模块替换成新的版本,而不需要手动重新加载页面。
      • 支持HTTPS:可以通过配置选项来启用 HTTPS 协议。

      使用 webpack-dev-server 可以提高开发效率,但是它并不适用于生产环境。因为它不会将打包后的文件写入硬盘,而是存储在内存中,所以无法直接访问打包后的文件。
      如果需要在生产环境中使用 webpack 打包后的文件,则应该使用 webpack 的默认模式来打包文件,而不是使用 webpack-dev-server

    • Webpack-dev-server打包服务器要解决的问题:

      • 每次在修改代码时, 都需要重新 yarn build 打包, 才能看到最新的效果, 实际工作中, 打包 yarn build 非常费时 (30s - 60s) 之间。之所以耗时,是因为:

        1. 构建依赖
        2. 磁盘读取对应的文件到内存, 才能加载
        3. 将处理完的内容, 输出到磁盘指定目录
      • 因此可以起一个开发服务器, 在电脑内存中打包, 缓存一些已经打包过的内容, 只重新打包修改的文件, 最终运行加载在内存中给浏览器使用。

      • webpack-dev-server的目的:启动本地服务, 可实时更新修改的代码, 打包变化代码到内存中, 然后直接提供端口和网页访问

    • Webpack-dev-server如何配置:

  • 【问】Webpack如何对css文件进行加载和打包?(配置css加载器:style-loadercss-loaderless-loader

    Note

    • 如果直接将准备好的css文件, 引入到webpack入口,使用webpack进行打包会存在问题:

      在这里插入图片描述

      原因webpack默认只认识 js 文件和json文件

    • css加载器配置步骤:style-loader文档css-loader文档

      • 安装依赖

        yarn add style-loader css-loader -D
        
      • webpack.config.js配置

        const HtmlWebpackPlugin = require('html-webpack-plugin')
        
        module.exports = {
            // ...其他代码
            module: { 
                rules: [ // loader的规则
                  {
                    test: /\.css$/, // 匹配所有的css文件
                    // use数组里从右向左运行
                    // 先用 css-loader 让webpack能够识别 css 文件的内容并打包
                    // 再用 style-loader 将样式, 把css插入到dom中
                    use: [ "style-loader", "css-loader"]
                  }
                ]
            }
        }
        
      • 实现原理:当模块引到入口时才会被webpack打包;css打包进js中, 然后被嵌入在style标签插入dom

    • less加载器步骤:less-loader文档

      • 安装less-loader

        yarn add less less-loader -D
        
      • webpack.config.js 配置

        module: {
          rules: [ // loader的规则
            // ...省略其他
            {
                test: /\.less$/,
                // 使用less-loader, 让webpack处理less文件, 内置还会用less翻译less代码成css内容
                use: [ "style-loader", "css-loader", 'less-loader']
            }
          ]
        }
        
  • 【问】Webpack如何对图片文件进行加载和打包?(webpack5使用asset modulewebpack4使用url-loaderfile-loader

    Note

    • 使用asset module方式(webpack5版本新增)对图片文件进行加载,asset module文档

      {
          test: /\.(png|jpg|gif|jpeg)$/i,
          type: 'asset'
      }
      
    • 如果是webpack4及以前,使用url-loaderfile-loader,配置如下:url-loader文档file-loader文档

      • 下载依赖包

        yarn add url-loader file-loader -D
        
      • webpack.config.js 配置

        {
          test: /\.(png|jpg|gif|jpeg)$/i,
          use: [
            {
              loader: 'url-loader', // 匹配文件, 尝试转base64字符串打包到js中
              // 配置limit, 超过8k, 不转, file-loader复制, 随机名, 输出文件
              options: {
                limit: 8 * 1024,
              },
            },
          ],
        }
        
      • src/assets/准备2个图文件

      • css/less/index.less中,把小图片用做背景图

        body{
            background: url(../assets/logo_small.png) no-repeat center;
        }
        
      • src/main.js中,把大图插入到创建的img标签上, 添加body上显示

        // 引入图片-使用
        import imgUrl from './assets/1.gif'
        const theImg = document.createElement("img")
        theImg.src = imgUrl
        document.body.appendChild(theImg)
        
      • 打包运行dist/index.html观察2个图片区别

      • 实现原理url-loader 把文件转base64打包进js中, 会有30%的增大, file-loader把文件直接复制输出。

  • 【问】Webpack如何对字体文件进行加载和打包?(webpack5使用asset modulewebpack4使用url-loaderfile-loader

    Note

    • webpack5中,可以使用asset module技术,asset/resource直接输出到dist目录下,配置步骤:asset module文档

      • src/assets/ 中放入字体库fonts文件夹

      • main.js引入iconfont.css

        // 引入字体图标文件
        import './assets/fonts/iconfont.css'
        
      • public/index.html使用字体图标样式

        <i class="iconfont icon-weixin"></i>
        
    • webpack4及以前使用下面的配置

      • 配置webpack.config.js

         { // 处理字体图标的解析
             test: /\.(eot|svg|ttf|woff|woff2)$/,
                 use: [
                     {
                         loader: 'url-loader',
                         options: {
                             limit: 2 * 1024,
                             // 配置输出的文件名
                             name: '[name].[ext]',
                             // 配置输出的文件目录
                             outputPath: "fonts/"
                         }
                     }
                 ]
         }
        
      • 执行打包命令,观察打包后网页效果

  • 【问】Webpack加载文件的优缺点

    Note

    • 8kb进行区分,小于8kb图片转成 base64 字符串

      • 好处就是浏览器不用发请求了,直接可以读取
      • 坏处就是如果图片太大,再转base64就会让图片的体积增大 30% 左右
  • 【问】Webpack如何处理高版本的js?(babel-loader

    Note

    • 实现原理:babel-loader通过调用babelapi让webpack对高版本的js代码, 降级处理后打包

    • 写代码演示: 高版本的js代码(箭头函数、类), 打包后, 直接原封不动打入了js文件中, 遇到一些低版本的浏览器就会报错

      • 原因:webpack默认仅内置了模块化的兼容性处理import export

      • 解决: babel的介绍可以用于处理高版本js语法的兼容性babel官网)让webpack配合babel-loaderjs语法做处理。babel-loader文档

        @babel/core@babel/corebabel的核心库,所有的核心Api都在这个库里,这些Api供babel-loader调用;

        @babel/preset-env:这是一个预设的插件集合,包含了一组相关的插件,Bable中是通过各种插件来指导如何进行代码转换。该插件包含所有es6转化为es5的翻译规则

    • 配置过程如下:

      • 安装依赖包:

        yarn add -D babel-loader @babel/core @babel/preset-env
        
      • 配置规则:

        module: {
          rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'] // 预设:转码规则(用bable开发环境本来预设的)
                    }
                }
            }
          ]
        }
        
  • 【问】Webpack对ES6语法进行处理?(处理高版本的js使用babel-loader将ES6降级为ES5)

  • 【问】Vue Cli是什么?怎样创建一个项目?(以Vue-Cli2为例),参考Vue-cli官网从零搭建vue2项目

    Note

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

    • Vue CLI使用前提

      • 安装Node.jsNPM的全称是Node Package Manager
        一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。

      • 使用了webpack模板:完成对所有的资源压缩等优化的操作。

    • 安装Vue cli过程如下

      • 1)先下载node和npm,并分别使用node -vnpm -v查看相应版本。(如果npm下载慢,参考npm下载缓慢解决方法
      • 2)安装vue-cli后,通过vue —version查看vue-cli版本
      • 3)创建一个新项目(创建时可以选择是Vue3或者Vue2):vue create hello-world
      • 4)运行项目:cd hello-world后,npm run serve启动前端服务,通过http://localhost:8080/访问首页
        Note:究竟是npm run serve还是npm run dev得看package.json中scripts配置vue-cli-service serve命令的key是什么,参考npm run dev 和 npm run serve区别)。
  • 【问】Vue Cli2和Vue Cli3有什么区别?

    Note

    • vue-cli 3vue-cli 2 版本有很大区别(项目目录结构不同):

      • vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3

      • vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录

      • vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化:
        移除了static文件夹,新增了public文件夹,并且index.html移动到public中

    • Vue CLI2初始化项目vue init webpack my-project,目录结构如下:

      vue-cli2项目的结构如下(ChatGPT给出的答案):

      	
      	├── build
      	│   ├── build.js
      	│   ├── check-versions.js
      	│   ├── dev-client.js
      	│   ├── dev-server.js
      	│   ├── utils.js
      	│   ├── vue-loader.conf.js
      	│   ├── webpack.base.conf.js
      	│   ├── webpack.dev.conf.js
      	│   └── webpack.prod.conf.js
      	├── config
      	│   ├── dev.env.js
      	│   ├── index.js
      	│   └── prod.env.js
      	├── index.html
      	├── package.json
      	├── src
      	│   ├── App.vue
      	│   ├── assets
      	│   ├── components
      	│   ├── main.js
      	│   ├── router
      	│   └── store
      	├── static
      	└── test
      

      下面是每个文件的作用:

      • build/:用于存放构建相关的文件。
      • config/:用于存放项目配置文件。
      • index.html:项目的入口文件。
      • package.json:项目的配置文件,用于定义项目的依赖和脚本
      • src/:用于存放源代码
      • src/App.vue:项目的根组件。
      • src/assets/:用于存放静态资源
      • src/components/:用于存放组件。
      • src/main.js:项目的入口文件。
      • src/router/:用于存放路由配置
      • src/store/:用于存放状态管理
      • static/:用于存放静态资源
      • test/:用于存放测试相关文件。

      Vue CLI3初始化项目vue create my-project,目录结构如下:

    • vue-cli2vue-cli3 在搭建 Vue 项目时存在一些差异。主要区别如下(chatGPT给的答案):

      • vue-cli2 是基于 webpack 1webpack-simple 模板搭建 Vue 项目,项目结构比较简单,默认不支持单文件组件。
        vue-cli3 是基于 webpack 4webpack-chain 模板搭建 Vue 项目,项目结构比较复杂,默认支持单文件组件。
      • vue-cli2 中,webpack 配置文件在项目根目录下,可以直接修改配置文件进行定制化配置。
        vue-cli3 中,webpack 配置文件分为开发环境和生产环境两部分,位于项目根目录下的 vue.config.js 文件中,可以通过链式语法修改配置。
      • vue-cli2 中,默认使用的是 webpack-dev-server,支持热更新。
        vue-cli3 中,默认使用的是 webpack-dev-server,也支持热更新。但是,它还支持 webpack-dev-middleware,可以在服务端进行开发。

      总之,vue-cli2vue-cli3 在搭建 Vue 项目时存在一些区别,包括模板类型、项目结构、webpack 配置、开发服务器等方面。

  • 【问】Webpack如何配置vue.js?(使用babel-loader,style-loader,css-loader,less-loader,file-loader,url-loader等进行打包;搭建webpack-dev-server),参考webpack打包Vue项目
    Note

    • webpack 通过 vue-loadervue-template-compiler 等插件来配置 vue.jsChatGPT给出的答案)。
      • vue-loader 是一个webpack的加载器,用于将Vue组件的代码转换为JavaScript模块。它可以解析Vue组件中的 HTML 模板、CSS 样式和 JavaScript 代码,并将它们打包为 JavaScript模块,安装命令如下:
        npm install vue-loader vue-template-compiler --save-dev
        
        然后在配置文件webpack.config.js中配置vue-loader
        module.exports = {
            module: {
              rules: [
                {
                  test: /\.vue$/,
                  loader: 'vue-loader'
                }
              ]
            }
          };
        
      • vue-template-compiler是一个 Vue模板编译器,用于将Vue模板转换为JavaScript代码。它可以在编译时解析 Vue 模板中的语法,并生成对应的JavaScript代码
        npm install vue-template-compiler --save-dev
        
        然后在配置文件webpack.config.js中配置vue-template-compiler
         const VueLoaderPlugin = require('vue-loader/lib/plugin');
         const HtmlWebpackPlugin = require('html-webpack-plugin');
         
         module.exports = {
           module: {
             rules: [
               {
                 test: /\.vue$/,
                 loader: 'vue-loader'
               }
             ]
           },
           plugins: [
             new VueLoaderPlugin(),
             new HtmlWebpackPlugin({
               template: './src/index.html'
             })
           ]
         };
      
      上面的配置中,VueLoaderPlugin 用于启用 vue-loaderHtmlWebpackPlugin 用于将 Vue 组件打包成 HTML 文件。
      总之,webpack通过vue-loadervue-template-compiler等插件来配置vue.js,可以解析 Vue 组件的 HTML 模板、CSS 样式和 JavaScript 代码,并将它们打包成 HTML 文件。
  • 【问】Vue-cli2项目中App.vue 、main.js和 index.html的关联关系(vue-cli2项目结构小总结:App.vue主组件,router组件在App.vue中完成注册;main.js入口文件用于初始化vue实例)

    Note

    • main.js入口文件,主要作用是初始化vue实例App主组件对象,router对象),并通过Vue.use()使用需要的插件

      import Vue from 'vue'
      import ElementUI from 'element-ui';
      import'element-ui/lib/theme-chalk/index.css';
      import App from './App.vue'
      import router from '@/router/index.js'
      import Main from '@/components/Main.vue'
      
      Vue.config.productionTip=false
      
      Vue.use(ElementUI);
      
      new Vue ( {
        render:h=>h(App),
        router
        //router
      }).mount('#app')  //"el"等价于$mount
      
    • App.vue是我们的主组件,所有页面都是在App.vue下进行切换的。其实你也可以理解为所有的路由也是App.vue的子组件,即所有的组件都在App.vue上完成注册。所以这里router(hello)标示为App.vue的子组件。

      <template>
        <div id="app">
          <img src="./assets/logo.png">
          <hello></hello>
        </div>
      </template>
      
      <script>
      import Hello from './components/Hello'
      
      export default {
        name: 'app',
        components: {
          Hello
        }
      }
      </script>
      
      <style>
      #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
    • Vue项目结构小总结:

      • 1)index.html文件入口

      • 2)src放置组件和入口文件

      • 3)node_modules为依赖的模块;

      • 4)config中配置了路径端口值等;

      • 5)build中配置了webback基本配置、开发环境配置、生产环境配置等。

      • 6)main.js是我们的入口文件,主要用于初始化vue实例,并使用需要的插件。

      • 7)App.vue是我们的主组件,所有的页面都在App.vue下进行切换。我们可以router标示为App.vue的子组件

      • 8)在每个模板中style设置一个scoped属性,意为style的属性只对这个组件起作用,不会影响到其他组件中含有相同class的元素。

五、vue-router的使用(路由跳转、路径与组件的映射、嵌套路由、动态路由、路由传参、路由守卫、promise异步操作与链式编程)

参考 Vue Router官网异步Promise及Async/Await可能最完整入门攻略ES6 Promise 和 Async/await的使用在 Vue 中用 Axios 和异步模式请求API

  • 【问】前端路由的概念和原理?(前端路由通过监听urlhash值的变化情况(href属性),渲染对应的组件)

    Note

    • 一般来说,在浏览器中,我们通过输入URL并点击回车来访问不同的页面。这种方式称为后端路由ChatGPT的回答)

      • 但是,在单页应用中,所有的页面都是由同一个HTML文件加载的,因此不能通过更改URL来实现页面切换。这时,就需要在前端实现路由功能。
      • 前端路由通常是通过js来实现的,它会监听浏览器的地址栏,并在用户点击浏览器的前进或后退按钮时,动态渲染页面内容。
      • 通过前端路由,可以让单页应用具有与多页应用(在应用的每个页面中,为每个链接添加一个href属性,指向对应的HTML文件。这样,用户点击链接时,就可以跳转到目标页面)相同的路由功能,提高用户体验。

      此外,前端路由还可以提供其他一些优点,比如:

      • 可以使用history模式,使url看起来更美观,不会出现hash
      • 可以通过路由重定向(redirect)实现页面重定向。
      • 可以通过路由别名(alias)为某个路由定义多个别名。
    • 路由器提供了两种机制: 路由和转送

      • 路由是决定数据包从来源到目的地的路径.
      • 转送将输入端的数据转移到合适的输出端.

      路由中有一个非常重要的概念叫路由表,路由表本质上就是一个映射表, 决定了数据包的指向

    • 前端路由:前端路由的本质是一个关于路径和组件的映射表,其核心是改变URL,但是页面不进行整体的刷新,具体流程如下:

      1)用户点击页面上的路由链接

      2)导致 url地址的Hash值变化

      3)前端路由监听到Hash地址的变化

      4)前端路由把当前Hash地址对应的组件渲染到浏览器中

    在这里插入图片描述

    • 实现原理urlhash也就是锚点(#), 本质上是改变window.locationhref属性,可以通过直接赋值location.hash来改变href, 但是页面不发生刷新。
  • 【问】路由的基本使用?(创建路由组件对象;创建路由对象;使用路由对象),参考Router-view路由出口Vue 知识点汇总(下)

    Note

    • 使用vue-router的基本步骤:

      • 1)创建路由组件

      • 2)配置路由映射: 组件和路径映射关系;

      • 3)使用路由: 通过<router-link to="">(路由入口,渲染成<a>标签)和<router-view>(路由出口)实现页面跳转;

    • 配置步骤如下:

      1)安装vue-router

      npm install vue-router --save
      

      2)创建路由组件Prize.vueMain.vueparent.vue(省略)

      3)router文件夹下建立index.js,定制路由规则。

      //1、导入Vue和VueRouter的包,定义 (路由) 组件
      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import Prize from '@/components/Prize.vue'
      import Home from "@/components/Main.vue";
      import parent from '@/components/parent'
      
      //2、调用Vue.use()函数,把VueRouter安装为vue插件
      Vue.use(VueRouter);
      
      //3、创建路由的实例对象
      const router =new VueRouter({
          routes:[
              {
                  path:'/home',
                  component:Home
              },
              {
                  path:'/prize',
                  component:Prize,
              },
              {
                  path:'/parent',
                  component:parent
              }
          ]
      })
      
      //4、向外共享路由的实例
      export default router
      

      包括如下步骤:a.导入Vue路由组件对象;b.调用Vue.use()函数使用VueRouter插件;c.创建路由的实例对象;d.向外共享路由的实例。

      4)把router组件对象挂载main.js上:

      import Vue from 'vue'
      import ElementUI from 'element-ui';
      import'element-ui/lib/theme-chalk/index.css';
      import App from './App.vue'
      import router from '@/router/index.js'
      import Main from '@/components/Main.vue'
      
      Vue.config.productionTip=false
      
      Vue.use(ElementUI);
      
      new Vue ( {
        render:h=>h(App),
        router
        //router
      }).mount('#app')  //"el"等价于$mount
      

      5)在App.vue中通过<router-link to=""><router-view>(路由出口),来使用router实例对象,参考Router-view路由出口

      <template>
          <div id="app">
              <h1>我是网站的标题</h1>
              <router-link to="/Home">首页</router-link>
              <router-Link to="/Prize">价位</router-link>
              <router-view></router-view>
              <h2>我是APP中一些底部版权信息</h2>
          </div>
      </template>
      
      <script>
      export default {
          name : ' App ' ,
          components : {
          }
      }
      </script>
      
      • <router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件

      • 网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级。

      • 在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.

  • 【问】路由的重定向(在配置路径和组件时,使用redirect实现重定向,跳转至其他path,进而加载相应component

    Note

    • 路由重定向(redirect)是指在访问某个路由时,自动跳转到另一个路由ChatGPT的回答)。
      Vue中,可以通过在路由配置中配置redirect属性来实现路由重定向。

        const router = new VueRouter({
          routes: [
            {
              path: "/user/:id",
              redirect: "/user/:id/profile",
            },
            {
              path: "/user/:id/profile",
              component: UserProfile,
            },
          ],
        });
      

      在上面的例子中,当用户访问/user/:id路由时,会自动重定向到/user/:id/profile路由,并加载对应的组件

    • 路由重定向指的是:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面;通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

      const router = new VueRouter({
          //在routes数组中,声明路由的匹配规则
          routes: [
              //当用户访问/的时候,通过redirect属性跳转到/home对应的路由规则
              { path : '/', redirect:'/home'},
              { path : '/home' , component : Home},
              { path : '/movie', component : Movie},
              { path : '/about', component : About}
          ]
      })
      
  • 【问】什么是路由的懒加载(打包js时将路由对应的组件打包成一个个的js代码块,在组件被访问到的时候才加载,=>异步加载)

    Note

    • 为什么要进行路由的懒加载:当打包构建应用时,Javascript包会变得非常大,影响页面加载。 如果将路由对应的组件打包成一个个的js代码块,只有在这个路由被访问到的时候, 才加载对应的组件
      在这里插入图片描述

    • 懒加载的方式

      • 结合Vue异步组件Webpack的代码分析:

        const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
        

        在 Vue.js 中,=>符号是一种箭头函数(arrow function)的简写形式(lambda表达式用来表示函数)。它的完整写法是:

        const getters = {
        	sidebar: state => state.app.sidebar
        }
        等价于 
        const getters = { 
            sidebar : (state) => {
                 return state.app.sidebar
            }
        }
        
      • AMD写法:

        const About = resolve => require(['../components/About.vue'], resolve);
        
      • 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割:

        const Home = () => import('../components/Home.vue')
        
  • 【问】什么是嵌套路由(在主组件、子组件下的<router-link to=“”>中使用index.js配置好的路径)

    Note

    • Vue 嵌套路由又称子路由,是指在 Vue 应用中,一个路由组件会渲染出其他路由组件。在实际应用中,通常由多层嵌套的组件组合而成。

    • 需求在apptest.vue下路由到about,在about组件下路由到tab1tab2

      • 第一步:路由配置index.js

        {
              path: '/about',
              component: About,
              // redirect: '/about/tab1',
              children: [
                // 子路由规则
                // 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
                { path: '', component: Tab1 },
                { path: 'tab2', component: Tab2 }
              ]
        },
        
      • 第二步:在apptest.vue下路由到/about,则会加载About组件

        <router-link to="/about">关于</router-link>
        <!-- 作用很单纯:占位符,给要显示的组件预留位置的 -->
        <router-view></router-view>
        
      • 第三步:在about组件下路由到/about/tab1/about/tab2,则会加载Tab1 、Tab2 组件

        <router-link to="/about/tab1">关于</router-link>
        <router-link to="/about/tab2">关于</router-link>
        <router-view></router-view>
        
  • 【问】什么是动态路由匹配(通过:定义路径的参数项,动态渲染组件,提高路由规则的复用性)

    Note

    • Vue的动态路由指的是在定义路由时,可以使用参数的形式定义一个路由,并且在路由跳转时可以传递参数来动态渲染对应的组件,这样做可以让同一个组件显示不同的内容

    • vue-router中使用英文的冒号(:)来定义路由的参数项。index.js示例代码如下:假设路由地址为/user/:id,则可以动态匹配hash值为/user?id=xxxurl

      const User = {
        template: '<div>User</div>'
      }
      
      const router = new VueRouter({
        routes: [
          // 动态路径参数 以冒号开头
          { path: '/user/:id', component: User }
          //相当于/user?id=xxx
        ]
      })
      
    • 动态路由的使用场景ChatGPT回答)

      • 举个例子,假设我们有一个用户管理系统,其中需要根据用户的 id来显示不同的用户信息。我们可以使用动态路由实现这个功能。例如,我们可以定义一个路由规则 /user/:id:id就是动态部分。这样,当用户访问 http://www.example.com/user/123时,Vue Router会自动将:id的值设为123,并将用户导航到对应的路由组件中
      • 比如,在一个博客应用中,用户可能想要访问不同的文章,每一篇文章都对应一个不同的URL,如 /articles/:articleId。通过使用动态路由,开发者可以让应用根据用户输入的 :articleId匹配到相应的文章组件,并展示给用户
  • 【问】什么是声明式导航和编程式导航(声明式导航即点击<a>标签上的链接;编程式导航则通过调用router对象api实现(pushreplace)),参考vue-router的push和replace的区别VUE的两种跳转push和replace对比区别

    Note

    • 在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
      普通网页中点击<a>链接、vue项目中点击<router-link:to="...">都属于声明式导航;

      在浏览器中,调用API方法实现导航的方式,叫做编程式导航。例如:
      普通网页中调用location.href跳转到新页面的方式,属于编程式导航router.push(...)方法的参数可以是一个字符串路径,或者一个描述地址的对象。

      // 字符串
      router.push('home')
      // 对象
      this.$router.push({path: '/login?url=' + this.$route.path});
      // 带查询参数,变成/backend/order?selected=2
      this.$router.push({path: '/backend/order', query: {selected: "2"}});
      // 命名的路由
      router.push({ name: 'user', params: { userId: 123 }}) 
      
    • vue-router中的编程式导航API,常用的导航API有:

      • 1)this.$router.push('hash地址'):跳转到不同的url,但这个方法会向history栈添加一个记录,点击后退会返回到上一个页面。

      • 2)this.$router.replace('hash地址'):同样是跳转到指定的url,但是这个方法不会向history里面添加新的记录,点击返回,会跳转到上上一个页面,上一个记录是不存在的

      • 3)this.$router.go(数值n):相对于当前页面向前或向后跳转多少个页面,类似window.history.go(n)。n可为正数可为负数。正数返回上一个页面

      • 4)如果在行内使用编程式导航跳转的时候,this必须要省略,否则会报错!比如:

        <button @click="$router.back()">back 后退</button>
        <button @click="$router.forward()">forward 前进</button>
        

      参考代码:

      <template>
        <div class="movie-container">
          <button @click="gotoLk">通过 push 跳转到“洛基”页面</button>
          <button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button>
      
          <button @click="goback">后退</button>
          <!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
          <button @click="$router.back()">back 后退</button>
          <button @click="$router.forward()">forward 前进</button>
        </div>
      </template>
      
      <script>
      export default {
        name: 'Movie',
        methods: {
          gotoLk() {
            // 通过编程式导航 API,导航跳转到指定的页面
            this.$router.push('/movie/1')
          },
          gotoLk2() {
            this.$router.replace('/movie/1')
          },
          goback() {
            // go(-1) 表示后退一层
            // 如果后退的层数超过上限,则原地不动
            this.$router.go(-1)
          }
        }
      }
      </script>
      
  • 【问】vue中 $router$route 的区别,参考vue中$router$route的区别
    Note

    • this.$route:当前激活的路由的信息对象。每个对象都是局部的,封装着当前路由的path, name, params, query 等属性,比如:
      <script>
      export default {
        created() {
          const { params, query } = this.$route  //获取`$route`对象中的`params`和`query`
          const { path } = params
          //编程式导航,但不会添加到历史记录中
          this.$router.replace({ path: '/' + path, query }) 
        },
        render: function(h) {
          return h() // avoid warning message
        }
      }
      </script>
      
      • $route对象中的params路由参数和query查询参数有什么区别(ChatGPT的回答):
        $route对象封装的queryparams主要用于获取URL中的查询参数和路由参数。
        • 查询参数指的是URL中以?开头的部分,以&符号分隔的多个键值对。比如,在URL为http://www.example.com/user?id=123&type=admin中,idtype就是查询参数。
        • 路由参数指的是路由规则中定义的动态部分,例如,在路由规则 /user/:id 中,:id就是一个路由参数。
        • $route对象的query属性包含了URL中的查询参数,以对象的形式存储。比如,在上面的例子中,$route.query对象的值就是{ id: '123', type: 'admin' }
        • $route对象的params属性包含了路由参数,以对象的形式存储。比如,在上面的例子中,$route.params对象的值就是{ id: '123' }
    • this.$router全局的 router 实例。通过 vue 根实例中注入 router 实例,然后再注入到每个子组件,从而让整个应用都有路由功能。其中包含了很多属性和对象(比如 history对象),任何页面也都可以调用其push(), replace(), go()等方法。
  • 【问】什么是路由守卫?(通过路由守卫函数和token进行路由跳转前的访问权限控制:tofromnext,参考导航守卫

    Note

    • Vue 的路由守卫是指在某些情况下,路由跳转前会触发指定的函数,用来拦截路由,或者在路由跳转后做一些额外的操作。它可以用来实现权限验证、路由拦截等功能。Vue 提供了 beforeEach(全局前置守卫)和 beforeResolve (全局后置守卫) 等守卫函数,它们可以在路由跳转前进行拦截和额外处理。路由守卫可以控制路由的访问权限ChatGPT答案)。

    • 全局前置守卫beforeEach:在index.js

      1. to 表示将要访问的路由的信息对象

      2. from 表示将要离开的路由的信息对象

      3. next() 函数表示放行的意思

      4. 具体逻辑如下:

        • 1)拿到用户将要访问的 hash地址
        • 2)判断 hash 地址是否等于 /main
          • 如果等于 /main,证明需要登录之后,才能访问成功
          • 如果不等于 /main,则不需要登录,直接放行 next()。(除了main页面其他都是白名单)
        • 3)如果访问的地址是 /main。则需要读取 localStorage 中的 token 值。
          • 如果有 token,则放行;
          • 如果没有 token,则强制跳转到 /login 登录页

      实例代码如下:

      // 为 router 实例对象,声明全局前置导航守卫
      // 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
      router.beforeEach(function(to, from, next) {
        // to 表示将要访问的路由的信息对象
        // from 表示将要离开的路由的信息对象
        // next() 函数表示放行的意思
        // 分析:
        // 1. 要拿到用户将要访问的 hash 地址
        // 2. 判断 hash 地址是否等于 /main。
        // 2.1 如果等于 /main,证明需要登录之后,才能访问成功
        // 2.2 如果不等于 /main,则不需要登录,直接放行  next()
        // 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
        // 3.1 如果有 token,则放行
        // 3.2 如果没有 token,则强制跳转到 /login 登录页
        if (to.path === '/main') {
          // 要访问后台主页,需要判断是否有 token
          const token = localStorage.getItem('token')
          if (token) {
            next()//如果有token(登录)就放行
          } else {
            // 没有登录,强制跳转到登录页
            next('/login')
          }
        } else {
          next()
        }
      })
      
    • next 函数的 3 种调用方式
      在这里插入图片描述

      • 当前用户拥有后台主页的访问权限,直接放行:next()

      • 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next("/login')

      • 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

  • 【问】怎样进行路由传参?(queryparamsprops传参),参考Vue 知识点汇总(下)

    Note

    • query传参(pathquery属性,拼接路径带?):

      • 配置路由格式: /router, 也就是普通配置
      • 传递的方式: 对象中使用querykey作为传递方式
      • 传递后形成的路径: /router?id=123, /router?id=abc
       //方式1:路由跳转并携带query参数,to的字符串写法 messageData是一个变量
       <router-link :to="`/home/news?id=001&message=${messageData}`" ></router-link>
      
       //方式2:路由跳转并携带query参数,to的对象
       <router-link :to="{
       path:"/home/news",  //index.js配置的路径
       query:{  //将query的k-v作为参数拼接到path中
            id:001,
            message:messageData 
       }
       }" >
       </router-link>
      

      获取参数:this.$route.query.idthis.$route.query.message

    • params传参(nameparams属性,拼接后端path不带?):

      路由跳转并携带param参数,to的字符串写法 ,首先我们要在路由文件(index.js)中定义我们要传递的参数

      • 配置路由格式: /router/:id

      • 传递的方式: 在path后面跟上对应的值

      • 传递后形成的路径: /router/123, /router/abc

      index.js配置:

       {
          name:'HomeNews'
          path:'news/:id/:message',//二级路由,定义参数,表示第一个参数是id,第二个是message
          component:News
       },
      

      传参:

      //方式1:路由跳转并携带query参数,to的字符串写法 messageData是一个变量
      <router-link :to="`/home/news?id=001&message=${messageData}`" ></router-link>
      
      //方式2:路由跳转并携带params参数,to的对象写法,不需要在路由文件中定义参数
      <router-link :to="{
          name:"HomeNews", //使用params传参时,必须使用name属性进行路由跳转,不能使用path配置项跳转
          params:{
              id:001,
              message:messageData
          }
      }" ></router-link>
      

      获取参数:this.$route.params.idthis.$route.params.message

    • 路由props配置:

      传参配置: src/router/index.js

       {
          name:'HomeNews'
          path:'news/:id/:message',//二级路由,定义参数,表示第一个参数是id,第二个是message
          component:News,
          // 第一种写法:props值为对象,该对象中所有的key-value最终都会通过props传递给组件news
          // props:{a:1},
          // 第二种写法(只能params):props值为Boolean,为true时把路由收到的`params`参数通过props传递给组件news
          // props:true,
          // 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传递给组件news
         props:function(route){
         return {
         id:route.query.id,
         message:route.query.message
         }
         },
       },
      

      使用: New.vue

      export default{
          prors:['id','message']
      }
      
  • 【问】什么是Promise呢?如何使用Pomise?(一个异步请求对象,配合async/await调用回调函数获得返回值,实现异步操作;Promise处理成功调用resolvethen,处理失败调用rejectcatch),实现链式编程

    Note

    • PromiseJavaScript中的一个对象,它用于表示一个异步操作的最终完成(或失败)及其结果值。 当一个异步操作成功完成时,会将结果值作为参数传递给Promise对象的回调函数 。 当一个异步操作失败时,会将失败的原因作为参数传递给Promise对象的回调函数。Promise对象配合async/await调用回调函数获得返回值,实现异步操作。

    • async/awaitJavaScript 中的一种语法,可以让异步操作看起来像同步操作。它通过在函数前面加上 async关键字来定义一个异步函数,在异步函数内部,可以使用await关键字来等待异步操作完成,并在操作完成后获取结果。例如(ChatGPT答案):

      async function getData() {
        const response = await fetch('https://example.com/data.json');
        const data = await response.json();
        return data;
      }
      
      // 使用异步函数
      getData().then(data => {
        console.log(data);
      });
      

      上面的代码定义了一个异步函数getData,在函数中使用await关键字来等待fetch(也可以使用axios等其他http库)和 response.json() 操作完成,然后在调用该函数时使用 then方法来处理函数的返回值

    • 在处理异步请求时,容易出现回调地狱的问题:

      在这里插入图片描述

      如果要获取最终的数据data4

      • 我们需要通过一个url1从服务器加载一个数据data1data1中包含了下一个请求的url2

      • 我们需要通过data1取出url2,从服务器加载数据data2data2中包含了下一个请求的url3

      • 我们需要通过data2取出url3,从服务器加载数据data3data3中包含了下一个请求的url4

      • 发送网络请求url4,获取最终的数据data4

      而在使用axios处理异步请求时,可以将异步处理结果封装成一个Promise对象

    • promise的基本使用:

      new Promise((resolve,reject)=>{
         setTimeout(function(){
            resolve('Hello World')
            reject('Error Data')
         },1000)
      }).then(data =>{
         console.log(data);
      }).catch(error =>{
         console.log(error);
      }
      

      定时器异步事件解析

      • new Promise很明显是创建一个Promise对象

      • 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数

        在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
        resolvereject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个

        • 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调

        • 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调

  • 【问】Promise有哪三种状态?(pendingfulfillreject

    Note

    • 当我们进行异步操作时,可以给异步操作包装一个Promise,异步操作之后会有三种状态。

      • pending:等待状态(进行中),比如正在进行网络请求,或者定时器没有到时间。

      • fulfill:满足状态(已完成),当我们主动回调了resolve时,就处于该状态,并且会回调.then()

      • reject:拒绝状态(已失败),当我们主动回调了reject时,就处于该状态,并且会回调.catch()

      在这里插入图片描述

  • 【问】Promise的链式调用

    Note

    • Pomise的链式调用简化写法如下(将return Promise.resovle(data)改成了return data):

      new Promise((resolve, reject
         setTimeout(function(){
             resolve('Hello World')
         },1000)
      }).then(data =>{
        console.log(data);//=>Hello World
        return data +'111'
      }).then(data =>{
        console.log(data);//=>Hello World111
        return data+'222'
      }).then(data=>{
        console.log(data);//=>Hello World111222
        return Promise.reject(data+'error'
      }).then(data=>{
        console.log(data);//这里没有输出,这部分代码不会执行
        return data + '333'
      }).catch(data =
        console.log(data);//=>Hello World111222error
        return data +'444'
      }).then(data=>{
        console.log(data);//=>Hello World111222error444
      })
      

六、状态管理(全局状态store和事件驱动,函数式编程,lambda表达式)

参考Vuex详解,一文彻底搞懂VuexVuex详解(五种状态)Vuex 是什么? | Vuex

  • 【问】什么是状态管理?Vuex是什么?(通过维护一个全局的变量,简化不同组件之间的传参过程)

    Note

    • 在状态管理的实际场景中,有哪些状态可以由vuex来管理:比如用户的登录状态、用户名称、头像、地理位置信息等等;比如商品的收藏、购物车中的物品等等。这些状态信息,我们都可以放在统一的地方,对它进行保存和管理。

    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式库

      • 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

      • Vuex也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

    • 组件之间的传值有哪些?有父子通讯,兄弟组件通讯…但是传参对于多层嵌套就显得非常繁琐,代码维护也会非常麻烦。因此 vuex就是把组件共享状态抽取出来以一个全局单例模式管理 ,把共享的数据函数放进vuex中,任何组件都可以进行使用。

  • 【问】单界面和多界面的状态管理的区别?

  • 【问】Vuex中核心状态有哪些?怎样进行管理的?(Vuex的核心思想:action异步提交mutation任务,mutation修改state状态实现页面更新;mutation相似于methodsstage相似于datagetter相似于computed,参考Vuex 是什么? | VuexVue知识点汇总【持更】

    Note

    • 存储在store里的都是全局变量,可以通过方法提交更新,其他页面和组件也会同步更新,拿到最新的值。

    • vuex中一共有五个状态 StateGetterMutationActionModule 下面进行详细讲解:

      • store文件夹中新建index.js,进行如下配置,并在main.js(初始化vue实例)中进行引入。

        • index.js配置如下:

          import Vue from 'vue'
          import Vuex from 'vuex'
          
          Vue.use(Vuex)  //使用Vuex插件
          
          export default new Vuex.Store({
            //数据,相当于data
            state: {
          
            },
            getters: {
          
            },
            //里面定义方法,操作state方发
            mutations: {
          
            },
            // 操作异步操作mutation
            actions: {
          
            },
            modules: {
          
            },
          })
          
        • main.js中配置如下:

          import Vue from 'vue'
          import App from './App.vue'
          import router from './router'
          import store from ' ./store '
          
          Vue.config.productionTip = false
          new Vue ( {
              router,
              store,
              render:h=>h(App)
          }).$mount('#app')
          
      • 1)State:提供唯一的公共数据源(驱动应用的数据源),所有共享的数据统一放到storestate进行储存,相似与data

        import Vue from 'vue'
        import Vuex from 'vuex'
        
        Vue.use(Vuex)
        
        export default new Vuex.Store({
          //数据,相当于data
          state: {
            name:"张三",
            age:12,
            count:0
          },
        })
        

        如何在组件中调用vuexstate的属性值:

        • 方法一:使用插值表达式{{}}

          <p>{{ $store.state.name }}</p>
          <p>{{ $store.state.age }}</p>
          
        • 方法二this.$store.state.全局数据名称

        • 方法三:从vuex中按需导入辅助函数mapstate()

          import { mapState } from "vuex";
          

          当前组件需要的全局数据,映射为当前组件computed属性

          computed:{
              ...mapState(["name","age","sex"]),
          },
          

          通过插值表达式直接获取值

          <p>辅助函数 {{ name }}</p>
          <p>辅助函数 {{ age }}</p>
          
      • 2)Mutation更改 Vuexstore 中的状态唯一方法是提交 mutation类似vue中的methods;每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state作为第一个参数;

        mutations : {
           addcount(state,num){
             state.count=+state.count+num
           },
           reduce(state){
              state.count--
           }
        },
        

        如何在组件中使用vuexmutation定义的方法:

        • 在组件中定义两个按钮进行加减操作

          <button @click="btn">点我增加store仓库中的数据</button>
          <button @click="btn1">点我减少store仓库的数据</button>
          
        • 方法一:使用commit触发Mutation操作

          methods:{
             //加法
             btn(){
               this.$store.commit("addcount",10)     //每次加十
             }
             //减法
             btn1(){
               this.$store.commit("reduce") 
             }
          }
          
        • 方法二:使用辅助函数mapMutations()进行操作,具体方法stage

          methods : {
             ...mapMutations(["addcount","reduce"]),
             btn(){
                this.addcount(10);
             },
             btn1(){
                this.reduce();
             },
          }
          
      • 3)Action响应在 view 上的用户输入导致的状态变化,action可以提交mutation,在action中可以执行store.commit(),而且action中可以有任何的异步操作。

        action处理异步操作,由于mutation都是同步事务,在 mutation 中混合异步调用会导致你的程序很难调试。action 类似于 mutation,不同在于Action 提交的是mutation,而不是直接变更状态

        //操作异步操作mutation
        actions : {
           asyncAdd(context){
              //异步
              setTimeout(()=>{
                 context.commit("reduce")
              },1000);
           }
        },
        

        如何在组件中使用vuexaction定义的方法:

        • 方法一:直接使用dispatch触发Action函数

          methods : {
             ...mapMutations(["addcount","reduce"]),
             btn(){
                this.addcount(10);
             },
             btn1(){
                this.reduce();
             },
             btn2(){
                this.$store.dispatch("asynAdd")
             },
          }
          
        • 方法二:使用辅助函数mapActions()

          methods : {
             ...mapActions(["asynAdd"]),
             btn2(){
                this.asynAdd();
             },
          }
          
      • 4)Getter类似于vue中的computed,当我们需要对数据进行处理可以使用gettergetter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算(Vue 3.0后不支持缓存);参考Getter | Vuex

        如果我们想在Store中获取学生年龄大于20的个数,getter中定义state.students处理方法如下。

        const store = new Vuex.Store({
           state:{
             students:[
                {id:110,name:'why',age:18},
                {id:111,name:'kobe',age:21}
                {id:112,name:'lucy',age:25}
                {id:113,name:'lilei',age:30},
             ]
           }
          getters:{
            greaterAgesCount:  state => {
               return state.students.filter(s=>s.age>=20).length
            }
          }
        })
        

        如何在组件中使用vuexgetter定义的方法:

        • 方法一:通过store.getters访问

          computed: {
            doneTodosCount () {
              return this.$store.getters.doneTodosCount
            }
          }
          
        • 方法二:通过辅助函数`mapGetters()``访问

          import { mapGetters } from 'vuex'
          
          export default {
            // ...
            computed: {
              // 使用对象展开运算符将 getter 混入 computed 对象中
              //...mapGetters([
              //  'doneTodosCount',
              //  'anotherGetter',
              ])
              
              //或者想重新命名
              ...mapGetters({
                 // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
                doneCount: 'doneTodosCount'
              })
              
              click(){
                 return doneCount();
              }
            }
          }
          
      • 5)Modules:当遇见大型项目时,数据量大,store就会显得很臃肿,为了解决以上问题,Vuex允许我们将 store 分割成模块(module)每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块——从上至下进行同样方式的分割;

        modules :
          cityModules:{
            namespaced:true,
            state:{
              cityname:"中国",
            },
            mutations:{
               cityfunction(state){
                 state.cityname="日本"
               }
            }
          },
          userinfo:{
             state:{
                username:"张启楠",
             },
          },
        

        默认情况下,模块内部的actionmutation仍然是注册在全局命名空间的,这样使得 多个模块能够对同一个 actionmutation 作出响应。

        如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名

        获取指定模块中的state的属性值:

        methods : {
           btn3(){
              console.log(this.$store.state.cityModules.cityname);
           },
        }
        
  • 【问】Vuex核心思想是什么?(State是提供唯一的公共数据源(相当于data);通过提交mutation是更改 Vuexstore 状态的唯一方法(相当于methods

    NoteVuex的核心思想是:当我们在页面上点击一个按钮:

    • 它会触发(dispatch)一个action

    • action随后会执行(commit)一个mutation

    • mutation立即会改变state, state改变以后,我们的页面会state 获取数据,页面发生了变化。

  • 【问】Vuex在项目中的基本使用?

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值