Vue知识点速查

初识Vue

1.前言

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

特点

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

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

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.内置指令

v-model表单绑定

实现双向绑定的本质:

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

  • 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实例中值的修改。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDMyDg9e-1655770046169)(https://s2.loli.net/2022/05/22/qDBCZUQohYW6MFt.png)]

3.组件

复杂问题时,将复杂问题分解成可以处理的小问题。

  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必须是函数

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

4.父子组件的通信

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

  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: '你好呀',
        movies: ['海王', '海贼王', '海尔兄弟']
      },
      components: {
        cpn
      }
    })
  </script>
 </body>

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

  1. 方式二传值
  • 在前面,我写的props选项是一个数组.除了数组之外还可以使用对象,当需要对props进行类型等验证时,就需要使用对象写法了.
  • 支持的数据类型如下,同时也支持我们自己写的类型
StringNumber
BooleanArray
ObjectDate
FunctionSymnbol
 <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: ['海王', '海贼王', '海尔兄弟']
      },
      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. 在子组件中,通过$emti()来触发事件
  2. 在父组件中,通过v-on来监听组件事件
<body>
  <!-- 父组件模板 -->
  <div id="app">
    <!-- 父组件监听子组件发过来的事件 -->
    <cpn @itemclick="cpnClick"></cpn>     这里使用v-on来监听自定义事件,并在vue实例中,做出响应。
  </div>

  <!-- 子组件模板 -->
  <template id="cpn">    
    <div>
      <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
    </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) {
          // 自定义事件,把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>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7gaCVvZ8-1655770046181)(https://s2.loli.net/2022/05/19/YJrDnmGxIBEj68U.png)]

父子通信中的子传父-使用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

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

  • 父组件访问子组件:使用 c c h i l d r e n 或 cchildren或 cchildrenrefs2
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

5.插槽slot

为啥要使用插槽

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

实际案例

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtP4LlkP-1655770046185)(https://s2.loli.net/2022/05/23/29zaUpiWJE1xfHD.png)]

如何去封装这类组件呢?
  • 他们有很多区别,但也有很多共性。
  • 我们单独去封装一个组件,显然不合适:比如每个页面都有返回,这部分内容我们就要重复去封装。
  • 但是,如果我们封装成一个,好像也不合理:有些是左侧菜单,有些是返回,有些中间是搜索,有些是文字,等等。
抽取共性,保留不同。
  • 最好的方法就是将共性抽取到组件中,将不同爆露为插槽。
  • 一旦我们预留了插槽,就可以让使用者根据自己的的需求,决定插槽中的内容。
  • 是搜索框还是文字,还是菜单,由调用者自己来决定.

实例

  • 需求:我想在第三个组件中添加一个按钮.
<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

6.ES模块化的导入和导出

模块化有连个核心:导入和导出

  • CommonJS的导出
//导出方式一
export{
	flag,sum
}
//导出方式二
export var num1 = 1000;
export var height = 1.88;

//info.js
let name = 'why'
let gae 18
let height = 1.88

export {name,age, height}
  • 函数的导入
import {mul} from "./"
  • CommonJs的导入
import {flag,sum} from "./aaa.js"
  • 导出函数/类
export function mul(num1, num2){
	return num1 + num2
}

export default

  • 某些情况下,一个模块中包含某个功能,我们不希望给这个功能命名,而且让导入者自己来命名

    这个时候就可以使用export default

    //info.js
    export default funcation(){
    	console.log('default' funcation)
    }
    

  

- 我们来到main.js中,这样就可以使用了
  
- myFunc是我自己命名的,你可以根据需要命名它对应的名字

​```javascript
import myFunc from './info.js'

myFunc()

  • 另外,需要注意:
    • export default在同一个模块中,不允许存在多个。

export指令导出了模块对外提供的接口

import使用

  • 首先我们在HTML中引入两个js文件,文件类型设置为module
  1. 导入export的function/class
import {mul,Person} from "./aaa.js"
  1. 导入 export default 中的内容
import addr form "./aaa.js"
  1. 统一全部导入
import * as Name form "./aaa.js"

7.Webpack的介绍和安装

  • Webpack中文文档
  • webpack是一个现代的JavaScript应用的静态模块打包工具
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kVIfONx0-1655770046189)(https://s2.loli.net/2022/05/26/uRpLP5vj1YkNIMy.png)]
  • 其他的打包工具grunt,gulp,webpack

前端模块化方案

  • AMD,CMD,CommonJS,ES6

AMD,CMD,CommonJS,ES6的打包

webpack和grunt/fulp的对比

  • grunt / gulp的核心是Task
    • 我们可以配置一系列的task,并且定义task要处理的事务(例如ES6,ts转化,图片压缩,scss转化成css)
    • 之后让grunt/gulp来依次执行这些task,并且让整个流程自动化.
    • 所以grunt/gulp也被称为前端自动画任务管理工具
  • 不同
    • grunt/fulp更加强调前端流程的自动化,模块化不是它的核心.
    • webPcak更加强调模块化的开发管理,而文件压缩合并,预处理等功能,是他附带的功能.
什么时候使用grunt呢?
  • 如果你的项目模块依赖非常简单,甚至没有用到模块化的概念,只需要简单的合并,压缩,就是用grunt/gulp即可,但如果整个项目用到了模块化管理,而且相互以来非常强,就可以使用更加强大的webPcak了.

webpack的安装

Snipaste_2022-05-26_21-07-39

  1. 必须先安装Node.js,查看自己的版本号
node -v

Snipaste_2022-05-26_21-10-22

  1. 安装webpack(全局安装)
npm i webpack -g
  1. 安装webpack(局部安装)
    • –save-dev`是开发时的依赖,项目打包后不需要继续使用的.

Snipaste_2022-05-26_21-14-57

webpack的使用

  • 包含两个文件夹src,dist
  • src:用来存放代码.
  • dist:项目打包后,就放到此文件夹中.
打包代码
webpack ./src/main.js ./dist/bundle.js

使用webpack指令,将src目录下的main.js打包到dist目录下的bundle.js

webpack搭建本地服务器
  • webpack-loder

loader

在前端开发中会有各种文件的转化,比如TypeScript转化成ES5代码,将less转化成css等,需要对应的loder才可以.

  • css-loader/style-loder
  • less-loder/less
  • url-loder/file-loder
  • babel-loder

  • css-loder只负责将css文件进行加载,不负责解析.

  • style-loader负责将样式添加到DOM中

  • 使用多个loder时,是从右向左.

webpack在读取使用loader的过程中,是按照从右向左的顺序读取的.

webpack.config.js的配置如下图所示.

Snipaste_2022-05-26_21-45-46

webpack中图片文件的处理

body{
	background: url("../img/test.jpg")
}
  • 执行命令
npm run bulid

会发现执行报错,提示需要安装合适的loader

Snipaste_2022-05-26_22-14-05

  • 也需要到loder去下载专门的组件.

ES6语法处理

  • 如果希望将ES6语法转换成ES5,那么就需要使用babel.
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
  • 配置webpack.config.js文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0o8odu4p-1655770046191)(https://s2.loli.net/2022/05/26/4kGwvIPozCjymHF.png)]

  • 重新打包,查看bundle.js文件,发现其中的内容变成了ES5的语法.
  1. runtime-only:代码中,不能有任何的template
  2. runtime-compiler代码中,可以有template,因为compiler 可以用来编译template

创建Vue时,template和el的关系

  • 定义template属性:
    • 定义template封装到北部

8.Vue Cli

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

CLI是什么意思?

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

Vue CLI使用的前提 - node

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

  • vue.js官方的脚手架工具就是用了webpack模板
    • 对所有的资源都会压缩等优化操作。
    • 他在开发过程中提供了一套完整的功能,能够使得我们开发过程变得高效。

安装Vue CLI

npm i -g @vue/cli

Vuecli-CLI2的目录结构解析

课程视频

Snipaste_2022-05-27_20-45-27

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

🔴config:关于webpack的配置

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

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

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

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

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

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

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

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

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

Eslint

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

config - index.js - useEslint:false

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

ui配置
vue ui

箭头函数

  • 箭头函数的声明以及使用方法
  1. 多参数声明
    const sum = (num1, num2) => {
      return num1 + num2
    }
  1. 单参数声明
    const power = num => {
      return num * num
    }

  1. 无参数声明
const aaa = ()=>{

}
  1. 函数中的代码数量多
    const test = () => {
      //1. 打印一个hello world
      console.log('Hello world');
      //2. 打印hello Vue.js
      console.log('Hello Vue.js');
    }
  1. 函数中代码数量少,简化写法
    const mul = (num1, num2) => num1 + num2
  • 箭头函数的指向问题

使用场景:一个函数作为另外一个函数的参数时、

结论:箭头函数中的this引用的就是最近作用域中的this

this函数是一层一层查找this,直到有this的定义

<body>
  <script>
    setTimeout(function () {
      console.log(this);
    }, 1000)
    // this指向window

    console.log(this);

    setTimeout(() => {
      console.log(this);
    }, 1000)
    // this指向window,向外找作用域


    const obj = {
      aaa() {

        console.log(this);

        setTimeout(function () {
          console.log(this); //window
        }, 1000)

        setTimeout(() => {
          console.log(this);  //obj对象
        })
      }
    }

    obj.aaa()
  </script>
</body>

Snipaste_2022-05-30_16-58-44

练习

      const bob = {
        aaa() {
          setTimeout(function () {
            setTimeout(function () {
              console.log(this);  //window
            })
  
            console.log(this); //window
  
            setTimeout(() => {
              console.log(this); //window
            })
          })
          setTimeout(() => {
            setTimeout(function () {
              console.log(this);  //window
            })
  
            setTimeout(() => {
              console.log(this);  //obj
            })
          })
        }
      }

Snipaste_2022-05-30_17-14-06

9.vueroter

前置知识体系

什么是路由

路由:路由就是通过互联的网络把信息从源地址传输到目的地址的活动

前端渲染,什么是后端渲染(重要)

后端渲染:类似JSP,网页直接从后端进行渲染(服务端),再向客户展示

Snipaste_2022-05-30_18-16-46

前端渲染:浏览器中显示的网页中的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页。

前后端分离阶段

Snipaste_2022-05-30_18-24-26

Snipaste_2022-05-30_18-33-49

单页面富应用阶段(SPA页面)
  • 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由,也就是前端来维护一套路由规则
  • 整个网页只有一个html页面。

Snipaste_2022-05-30_18-56-10

前端路由,什么是后端路由

后端路由:URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且交给一个Controller进行处理。

总体优缺点:

  • 后端路由:

    • 优点:渲染好的页面不需要加载任何的js和css,可以直接交给浏览器进行展示,有利于SEO的优化。

    • 缺点:整个页面的模块由后端人员来开发和维护的,前端人员要是想要开发页面,需要通过PHP和Java等编程语言来编写代码。

      通常情况下,HTML代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情。

url的hash模式

可以更改页面指向,同时也不会请求资源

location.hash='bbb'

Snipaste_2022-05-30_20-08-11

history.pushState({},'','home')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GEYUNkLp-1655770046195)(https://s2.loli.net/2022/05/30/P71WGRAigjf8vEz.png)]

将url压入栈(先进后出)中

Snipaste_2022-05-30_20-22-35

HTML5 的history模式:go

Snipaste_2022-05-30_20-31-20

HTML5 的history模式 :replaceState

Snipaste_2022-05-30_20-28-26

补充说明:

  1. history.back () 等价于 history.go(-1)
  2. history.forward() 等价于 hisrory.go(1)
  3. 这三个接口等同于浏览器界面的前进后退

认识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属性补充
属性名作用示例
tagtag可以指定< router-link >之后渲染成什么组件,比如上面的代码会被渲染成一个< li >,而不是< a>< roter-link to = ‘/home’ tag=‘li’>
replacereplace不会留下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>
vue3关闭eslint

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-83H1mzzu-1655770046197)(https://s2.loli.net/2022/05/30/gMNtZ65fAvdzEWq.png)]

lintOnSave: false

Vscode创建Vue模板(加快开发速度)

通过代码跳转路由

<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路径可能是不确定的,比如我们进入用户界面时,希望时如下的路径:
    /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

路由懒加载的使用

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

解释

当打包构建应用时,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

r o u t e 和 route和 routerouter是有区别的

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

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

Vue.prototype.name = "李宇"

那么在整个vue环境中,this.name都是可以访问到的。

console.log(this.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

  • 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生命周期

TabBar实现思路(示例)

Snipaste_2022-06-12_10-13-58

  1. 封装单独的TabBar组件

    • 自定义TabBar组件,在APP中使用。
    • 让TabBar出于底部,并且设置相关样式
  2. TabBar中显示的内容由外界决定

    • 定义插槽
    • flex布局平分TabBar
  3. 自定义TabBarItem,可以传入图片和文字

    • 自定义TabBarItem,并且定义两个插槽:图片,文字。

    • 给两个插槽外层包装div,用于设置样式。

    • 填充插槽,实现底部iTabBar的效果

外部taBar里面定义4个小的taBaritem,在taBartiem里定义了两个插槽(slot),一个用来放置图片,另外一个用来放置标题,还的有link标签用来跳转

项目架构

Snipaste_2022-06-13_11-14-53

components:用来存放公共的组件。

其他模块化的组件应该分装进不同的文件,比如cart,category等。


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

  2. reference(引用) ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抗争的小青年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值