0%

Vue入门

Vue

一. 邂逅Vue.js开发

1.1. Vue介绍

  • Vue的介绍
  • Vue在前端的地位
    • react
    • angular

1.2. Vue下载和使用

  • CDN引入

    • ```html
      1
      2
      3
      4
      5

      - 本地引入

      - ```html
      <script src="../vue.js"></script>
  • 初体验Vue开发

1.3. Vue的三个案例

1.3.1. 动态数据展示

1.3.2. 动态展示列表

  • v-for

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <body>
    <div id="app"></div>
    </body>
    <script>
    const app = Vue.createApp({
    template:`
    <h2>电影列表</h2>
    <ul>
    <li v-for="item in movies">{{item}}</li>
    </ul>
    `,
    data(){
    return {
    message:'你好啊,世界',
    movies:['你好,李焕英','大话西游','扫黑风暴']
    }
    }
    })
    app.mount('#app')
    </script>

1.3.3. 计数器案例

  • counter

  • increment

  • decrement

  • ```html

    Document

    当前计数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32



    ### 1.4. 命令式和声明式编程的区别

    - 命令式编程关注的是 “how to do”自己完成整个how的过程;
    - 声明式编程关注的是 “what to do”,由框架(机器)完成 “how”的过程;
    - 在原生的实现过程中,我们是如何操作的呢?
    - 我们每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令;
    - 这样的编写代码的过程,我们称之为命令式编程;
    - 在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的;
    - 在Vue的实现过程中,我们是如何操作的呢?
    - 我们会在createApp传入的对象中声明需要的内容,模板template、数据data、方法methods;
    - 这样的编写代码的过程,我们称之为是声明式编程;
    - 目前Vue、React、Angular、小程序的编程模式,我们称之为声明式编程;

    ### 1.5. MVC和MVVM的模型区别

    - MVC和MVVM都是一种软件的体系结构
    - MVC是Model – View –Controller的简称,是在前期被使用非常框架的架构模式,比如iOS、前端;
    - MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式;
    - 通常情况下,我们也经常称Vue是一个MVVM的框架。
    - Vue官方其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的。

    ### 1.6. options api的data详解

    - data必须是一个函数,函数返回一个对象

    - data返回的对象,会被Vue进行劫持(放到响应式系统中),所以data的数据发生改变时,界面会重新渲染
    - ```
    - 所以我们在template或者app中通过 {{counter}} 访问counter,可以从对象中获取到数据;
    - 所以我们修改counter的值时,app中的 {{counter}}也会发生改变;

1.7. options api的methods详解

  • methods属性是一个对象,通常我们会在这个对象中定义很多的方法:
    • 这些方法可以被绑定到 模板中;
    • 在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性;
  • 对于有经验的同学,在这里我提一个问题,官方文档有这么一段描述:
    • 问题一:为什么不能使用箭头函数(官方文档有给出解释)?
      • 我们在methods中要使用data返回对象中的数据:
        • 那么这个this是必须有值的,并且应该可以通过this获取到data返回对象中的数据。
      • 那么我们这个this能不能是window呢?
        • 不可以是window,因为window中我们无法获取到data返回对象中的数据;
        • 但是如果我们使用箭头函数,那么这个this就会是window了;
      • 为什么是window呢?
        • 这里涉及到箭头函数使用this的查找规则,它会在自己的上层作用于中来查找this;
        • 最终刚好找到的是script作用于中的this,所以就是window;
    • 问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)
      • 事实上Vue的源码当中就是对methods中的所有函数进行了遍历,并且通过bind绑定了this

二. 基础 - 模板语法

2.1. 添加代码片段

2.2. mustache语法(插值语法)

  • 表达式

  • ```html

    Document

    当前计数:

        <!-- 2.表达式 -->
        <h2>计数双倍:{{counter * 2}}</h2>
        <h2>展示的信息:{{info.split(" ")}}</h2>
    
        <!-- 3.三元运算符 -->
        <h2>{{age >= 18?"成年人":"未成年人"}}</h2>
    
        <!-- 4.调用methods中的函数 -->
        <h2>{{formateData(time)}}</h2>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    ### 2.3. 不算常用的指令

    - v-once

    - ```html
    <body>
    <!-- 指令v-once 用于指定元素或者组件只渲染一次:
    当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过;
    该指令可以用于性能优化;
    如果是子节点,也是只会渲染一次
    -->
    <div class="app">
    <h2 v-once>{{message}}</h2>
    <button @click="change">修改内容</button>
    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    message:'Hello World'
    }
    },
    methods:{
    change:function(){
    this.message = "你好 世界"
    }
    }
    })
    app.mount(".app")
    </script>
  • v-text

    • ```html

      aa

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29



      - v-html

      - ```html
      <body>
      <div class="app">
      <h2>{{content}}</h2>
      <!--默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。
      如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;
      -->
      <h2 v-html="content"> aaa</h2>
      </div>
      </body>
      <script src="../vue.js"></script>
      <script>
      const app = Vue.createApp({
      data:function(){
      return{
      content:'<span style="color:red">哈哈哈</span>'
      }
      },
      methods:{

      }
      })
      app.mount(".app")
      </script>
  • v-pre

    • ```html

      当前计数:

              <!-- v-pre 用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
                      跳过不需要编译的节点,加快编译的速度;
                      显示 {{}}-->
                      

      {{}}</p> </div> </div>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46



      - v-cloak

      - ```html
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
      [v-cloak]{
      display:none;
      }
      </style>
      </head>
      <body>
      <!-- 这个指令保持在元素上直到关联组件实例结束编译。
      和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。
      -->
      <div class="app" v-cloak>
      <h2>{{message}}</h2>
      <h2>当前计数:{{counter}}</h2>
      </div>
      </body>
      <script src="../vue.js"></script>
      <script>
      setTimeout(function(){
      const app = Vue.createApp({
      data:function(){
      return{
      message:'Hello World',
      counter:0
      }
      },
      methods:{

      }
      })
      app.mount(".app")
      },3000)
      </script>
      </html>

2.4. 新的指令 v-memo

2.5. v-bind 绑定属性

  • 某些属性我们也希望动态来绑定。
    • 比如动态绑定a元素的href属性;
    • 比如动态绑定img元素的src属性
    • 动态绑定一些类、样式等等

2.5.1. v-bind绑定基本属性

  • src

  • href

  • ```html

    Document
        <!-- 2.绑定a的href属性 -->
        <a v-bind:href="imgUrl">百度一下</a>
    
        <div>
            <!-- v-on绑定点击事件 -->
            <button v-on:click="changeImg">切换图片</button>
            <button @click="changeImg">切换图片</button>
        </div>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10



    #### 2.5.2. v-bind绑定class

    - 基本绑定方式:

    - 对象语法
    - ```
    所以我们修改counter的值时,app中的 {{counter}}也会发生改变;
    • 数组语法
  • ```html

    Document
        <!-- 2.1对象语法的基本使用 -->
        <button @click="change" :class="{a:temp}">我是按钮</button>
        <!-- 2.2对象语法的多个键值对 -->
        <button :class="{a:temp,b:true,c:false}" @click="change">我是按钮</button>
        <!-- 2.3动态绑定的class是可以和普通的class同时使用的 -->
        <button class="a" :class="{a:temp,b:true,c:false}" @click="change">我是按钮</button>
        <!-- 2.4通过函数返回对象来动态绑定 -->
        <button :class="getClass()" @click="change">我是按钮</button>
    
        <!-- 4.动态class可以写数组语法 -->
        <h2 :class="["a","b"]">你好 世界</h2>
        <h2 :class="["a",className]">你好 世界</h2>
        <h2 :class="["a",className,{a:temp}]">你好 世界</h2>
    
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55



    #### 2.5.3. v-bind绑定style

    CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名;

    - 对象语法

    - ````
    {cssname:cssvalue}
    ````

    - 数组语法

    - [obj1,obj2]

    - ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <div class="app">
    <!-- 1.style中的某些值,来自data中 -->
    <!-- 1.1动态绑定style,在后面跟上对象类型(驼峰fontSize或者加引号) -->
    <h2 :style="{color:fontColor,'font-size':'30px'}">哈哈哈</h2>

    <!-- 2.数组语法 -->
    <h2 :style="[objStyle,{backgroundColor:'red'}]">哈哈哈</h2>

    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    fontColor:"red",
    objStyle:{
    fontSize:'30px'
    }
    }
    },
    methods:{

    }
    })
    app.mount(".app")
    </script>
    </html>

2.6. 动态绑定属性名

  • 在某些情况下,我们属性的名称可能也不是固定的:
    • 前端我们无论绑定src、href、class、style,属性名称都是固定的;
    • 如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义;
    • 这种绑定的方式,我们称之为动态绑定属性;

:[name]=” “

2.7. v-bind绑定对象

  • 如果我们希望将一个对象的所有属性,绑定到元素上的所有属性,应该怎么做呢?

    • 非常简单,我们可以直接使用 v-bind 绑定一个 对象;
  • ```html

    Document

    你好 世界

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    ### 2.8. 事件绑定 v-on的用法

    #### 2.8.1. v-on的各种写法

    - v-on:click="counter++"

    - v-on:click

    - @click

    - 别的事件

    - ```
    v-on="{click:xxxx}"
  • ```html

    Document
        <!-- 2.语法糖写法 -->
        <div class="box" @click="divClick"></div>
    
        <!-- 3.绑定方法的位置,也可以写成一个表达式 -->
        <h2>{{counter}}</h2>
        <button @click="increment">+1</button>
        <button @click="counter++">+1</button>
    
        <!-- 4.绑定其他事件 -->
        <div class="box" @mousemove="divMousemove"></div>
    
        <!-- 5.绑定多个事件 -->
        <div class="box" @click="divClick" @mousemove="divMousemove"></div>
    
        <div class="box" v-on="{click:divClick,mousemove:divMousemove}"></div>
        <div class="box" @="{click:divClick,mousemove:divMousemove}"></div>
        
    
    
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    #### 2.8.2. 各种参数方式

    - 默认传递event

    - 自定义参数

    - name,age,$event

    - ```html
    <body>
    <div class="app">
    <!-- 1.无参数 -->
    <button @click="btn1Click">按钮1</button>

    <!-- 2.只有自己的参数 -->
    <button @click="btn2Click('wt',age)">按钮2</button>

    <!-- 3.想拿到自己的参数和event对象 -->
    <!-- 在模板中想要明确的获取event对象:$event -->
    <button @click="btn3Click('wt',age,$event)">按钮3</button>

    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    age:18,
    }
    },
    methods:{
    // 1.默认参数 event对象
    // 总结:如果在绑定事件的时候,没有传递任何的参数,那么event对象会被默认传递进来
    btn1Click(event){
    console.log(event);
    },

    // 2.调用参数
    btn2Click(name,age){
    console.log(name,age);
    },
    // 3.自己的参数和evet对象
    btn3Click(name,age,event){
    console.log(name,age,event);

    }
    }
    })
    app.mount(".app")
    </script>

2.8.3. 修饰符

image-20230201170940037

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
width: 100px;
height: 100px;
background-color: aquamarine;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="app">
<div class="box" @click="divClick">
// 阻止事件冒泡
<button @click.stop="btnClick">按钮1</button>
</div>
</div>
</body>
<script src="../vue.js"></script>
<script>
const app = Vue.createApp({
data:function(){
return{

}
},
methods:{
btnClick(){
console.log('btnClick');
},
divClick(){
console.log('divClick');
}
}
})
app.mount(".app")
</script>
</html>

三、条件渲染和列表渲染

3.0. 条件渲染

3.0.1. v-if/else/else-if

  • v-if的渲染原理:

    • v-if是惰性的;

    • 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉;

    • 当条件为true时,才会真正渲染条件块中的内容;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div class="app">
<ul v-if="names.length > 0">
<li v-for="item in names">{{item}}</li>
</ul>
<h2 v-else>names没有数据</h2>
</div>
</body>
<script src="../vue.js"></script>
<script>
const app = Vue.createApp({
data:function(){
return{
names: ["aaa","bbb","ccc"]
}
},
methods:{

}
})
app.mount(".app")
</script>

3.0.2. template元素

  • v-if

  • v-for

  • ```html

        <template v-else>
            <h2>无个人信息</h2>
        </template>
    </div>
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

    #### 3.0.3. v-show

    - if用法区别:

    - v-show不能和template元素结合
    - v-show不能和v-else结合

    - if的本质区别:

    - v-if为false元素会销毁、不存在
    - v-show为false时元素的display为none

    - 选择

    - 切换频繁使用v-show
    - 不频繁使用v-if

    - ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    img{
    width: 1500px;
    height: 1000px;
    }
    </style>
    </head>
    <body>
    <div class="app">
    <!-- v-show 不支持template 不能和v-else一起使用 -->
    <!-- v-show本质是改变display的属性来实现元素是否隐藏 -->
    <!-- 如果元素需要频繁的显示和隐藏切换 使用v-show 反之用v-if-->
    <div>
    <button @click="change">切换</button>
    </div>
    <div v-show="isShowImg">
    <img src="https://img.dpm.org.cn/Uploads/Picture/2022/11/30/s6386b28128664.jpg" alt="">
    </div>

    <div v-if="isShowImg">
    <img src="https://img.dpm.org.cn/Uploads/Picture/2022/11/30/s6386b28128664.jpg" alt="">
    </div>
    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    isShowImg:true
    }
    },
    methods:{
    change(){
    this.isShowImg = !this.isShowImg
    }
    }
    })
    app.mount(".app")
    </script>
    </html>

3.1. 列表渲染

3.1.1. v-for的基本使用

  • item in 数组

    • 数组通常是来自data或者prop,也可以是其他方式;
    • item是我们给每项元素起的一个别名,这个别名可以自定来定义;
  • (item,index) in 数组 拿到数组的索引

  • (item,index) of 数组

  • ```html

    Document

    电影列表

    • 第NaN部电影:
        <h2>个人信息</h2>
        <div v-for="item in products" class="box">
            <h3>姓名:{{item.name}}</h3>
            <h3>爱好:{{item.hobby}}</h3>
        </div>
    </div>
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59

    #### 3.1.2. v-for其他类型

    - 对象

    - (value,key,index) in obj

    - 数字

    - item in 数字

    - 可迭代对象(字符串)

    - ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <div class="app">
    <!-- 1.遍历对象 -->
    <ul>
    <li v-for="(value,key,index) in info">{{key}}--{{value}}--{{index}}</li>
    </ul>
    <!-- 2.遍历字符串 -->
    <ul>
    <li v-for="str in message">{{str}}</li>
    </ul>

    <!-- 3.遍历数字 -->
    <ul>
    <li v-for="i in 10">{{i}}</li>
    </ul>
    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    info:{
    name:"李华",
    age:18,
    hobby:"烫头"
    },
    message:"I love you"
    }
    },
    methods:{

    }
    })
    app.mount(".app")
    </script>
    </html>

3.2. v-for绑定key属性

3.2.1. VNode/虚拟DOM

  • template元素 -> VNode
  • 虚拟DOM作用之一:
    • 跨平台

3.2.2. key的作用

  • 有key的操作

    • 根据key找到之前的VNode尽量先进行复用
    • 没有VNode可以复用,在创建新的VNode
    • image-20230201172104856
    • image-20230201172117623
    • image-20230201172127578
  • 没有key操作:

    • diff算法,后续VNode复用性不强
    • image-20230201172046248
  • 认识VNode

    • image-20230201171926346
  • 虚拟DOM

    • image-20230201171954846

3.3.0. key绑定id

四、Options API

4.1. 计算属性 computed

4.1.1. 复杂数据的处理方式

  • mustache插值语法 自己写逻辑
  • method完成逻辑
  • 使用计算属性computed

4.1.2. 计算属性的用法

  • computed:{ function() {} }

  • ```html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57

    - 注意:计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;

    - 我们会发现无论是直观上,还是效果上计算属性都是更好的选择;

    - 并且计算属性是有缓存的;

    #### 4.1.3. computed和methods区别

    - computed底层会进行缓存 性能更高
    - ![image-20230201173215791](Vue入门/image-20230201173215791.png)

    #### 4.1.4. computed的完整写法

    - set

    - get

    - ```html
    <body>
    <div class="app">
    <h2>{{ fullName }}</h2>
    <button @click="setFullname">修改内容</button>
    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    firstName:"Kobe",
    lastName:"brayt",

    }
    },

    computed:{
    fullName:{
    get:function(){
    return this.firstName + " " + this.lastName
    },
    set:function(value){
    const names = value.split(" ")
    this.firstName = names[0]
    this.lastName = names[1]
    }
    }
    },
    methods:{
    setFullname(){
    this.fullName = "kobe brant"
    }
    },

    })
    app.mount(".app")
    </script>

4.2. 侦听器 watch

  • 什么是侦听器呢?
    • 开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;
    • 当数据变化时,template会自动进行更新来显示最新的数据;
    • 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了;
    • 常用语搜索框的值得侦听
    • image-20230201174806683

4.2.1. 基本侦听watch

  • watch: { message(oldValue,newValue) { } }

  • 注意:对象类型

    • Proxy对象 -> Vue.toRaw(newValue)
  • ```html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60

    4.2.2. 侦听选项

    - 我们先来看一个例子:

    - 当我们点击按钮的时候会修改info.name的值;
    - 这个时候我们使用watch来侦听info,可以侦听到吗?答案是不可以。

    - 这是因为默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:

    - 这个时候我们可以使用一个选项deep进行更深层的侦听;
    - 注意前面我们说过watch里面侦听的属性对应的也可以是一个Object;

    - 还有另外一个属性,是希望一开始的就会立即执行一次:

    - 这个时候我们使用immediate选项;
    - 这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;

    - deep

    - immediate

    - ```html
    <body>
    <div class="app">
    <h2>{{info.name}}</h2>
    <button @click="change">修改</button>
    </div>
    </body>
    <script src="../vue.js"></script>
    <script>
    const app = Vue.createApp({
    data:function(){
    return{
    info:{name:"why",age:18}
    }
    },
    methods:{
    change(){
    // this.info = {name:"kobe"}
    this.info.name = "kobe"
    },
    },
    watch:{
    // info(newvalue,oldvalue){
    // console.log("能侦听到info改变",newvalue,oldvalue)
    // }
    info:{
    handler(newvalue,oldvalue){
    console.log("能侦听到info改变",newvalue,oldvalue)
    },
    // 进行深度监听 对于内部属性的变化做出响应
    deep:true,
    // 第一次进行渲染就执行一次监听器
    immediate:true
    }
    }
    })
    app.mount(".app")
    </script>

4.2.3. 其他写法

  • “info.name”
  • 别的写法
  • created -> this.$watch

五、阶段案例-购物车

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border-collapse: collapse;
background-color: #f5f5f5;
text-align: center;
}

th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
.active{
background-color: aquamarine;
}
</style>
</head>

<body>
<div class="app">
<template v-if="books.length">
<!-- 1.搭建界面内容 -->
<h1>书籍购物车</h1>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(book,index) in books" :key="book.id" @click="currentIndex(index)" :class="{active: index === CurrentIndex}">
<td>{{ index + 1 }}</td>
<td>{{ book.name }}</td>
<td>{{ book.date }}</td>
<td>{{ formatPrice(book.price) }}</td>
<td><button :disabled="book.count <= 1" @click="decrement(index,book)">-</button> {{ book.count }} <button @click="increment(index,book)">+</button></td>
<td><button @click="removeBook(index)">移除</button></td>
</tr>
</tbody>

</table>
<h1>商品总价:{{ formatPrice(sum) }}</h1>
</template>
<template v-else>
<h1>购物车为空</h1>
</template>
</div>
</body>
<script src="../vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
books: [
{ id: 1, name: "《算法导论》", date: "2016-9", price: 85, count: 1, },
{ id: 2, name: "《Unix编程艺术》", date: "2006-9", price: 35, count: 1, },
{ id: 3, name: "《Python从入门到放弃》", date: "2026-9", price: 25, count: 1, },
{ id: 4, name: "《如何让代码跑起来》", date: "2036-9", price: 185, count: 1, }],
CurrentIndex:-1
}
},
methods: {
decrement(index,item) {
// this.books[idnex].count--
item.count--
},
increment(index,item) {
// this.books[idnex].count++
item.count++

},
removeBook(index) {
this.books.splice(index,1)
},
formatPrice(price) {
return "¥" + price
},
currentIndex(index){
this.CurrentIndex = index
}
},
computed: {
sum() {
// 方法一:
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price

// 方法二:
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
}
}
})
app.mount(".app")
</script>

</html>

六、v-model的双向绑定

6.1. v-model的基本使用

  • v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定;

    • 它会根据控件类型自动选取正确的方法来更新元素;
    • 尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理;
  • 原理

    • v-bind绑定value属性的值;
    • v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;

6.2. v-model绑定其他类型

  • textare

    • image-20230207181935107
  • checkbox

    • 单选
      • v-model即为布尔值。
      • 此时input的value属性并不影响v-model的值。
      • image-20230207182013153
    • 多选
      • 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
      • 当选中某一个时,就会将input的value添加到数组中。
      • image-20230207182017413
  • radio

    • image-20230207182027797
  • select

    • 单选
      • v-model绑定的是一个值;
      • 当我们选中option中的一个时,会将它对应的value赋值到fruit中;
    • 多选
      • v-model绑定的是一个数组;
      • 当选中多个值时,就会将选中的option对应的value添加到数组fruit中;

6.3. v-model的值绑定

6.4. v-model修饰符

​ 默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步;

  • lazy

    • 如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发;
    • image-20230207182240346
  • number

    • image-20230207182324401
  • trim

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

七、组件化基础

7.1. 组件化思想

  • 我们需要通过组件化的思想来思考整个应用程序:
    • 我们将一个完整的页面分成很多个组件;
    • 每个组件都用于实现页面的一个功能块;
    • 而每一个组件又可以进行细分;
    • 而组件本身又可以在多个地方进行复用;

7.2. 注册全局组件

  • 全局组件需要使用我们全局创建的app来注册组件;

  • 通过component方法传入组件名称、组件对象即可注册一个全局组件了;

  • 之后,我们可以在App组件的template中直接使用这个全局组件:

  • ```javascript
    app.component(“my-comp”,{})

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    - ![image-20230207182548417](Vue入门/image-20230207182548417.png)

    - ![image-20230207182556551](Vue入门/image-20230207182556551.png)

    ### 7.3.组件的名称

    - 在通过app.component注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种:
    - 方式一:使用kebab-case(短横线分割符)
    - 当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>;
    - ![image-20230207182710146](Vue入门/image-20230207182710146.png)
    - 方式二:使用PascalCase(驼峰标识符)
    - 当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。
    - 也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的;
    - ![image-20230207182739698](Vue入门/image-20230207182739698.png)

    ### 7.4. 注册局部组件

    - 全局组件往往是在应用程序一开始就会全局组件完成,那么就意味着如果某些组件我们并没有用到,也会一起被注册:
    - 比如我们注册了三个全局组件:ComponentA、ComponentB、ComponentC;
    - 在开发中我们只使用了ComponentA、ComponentB,如果ComponentC没有用到但是我们依然在全局进行了注册,那么就意味着类似于webpack这种打包工具在打包我们的项目时,我们依然会对其进行打包;
    - 这样最终打包出的JavaScript包就会有关于ComponentC的内容,用户在下载对应的JavaScript时也会增加包的大小;

    - 所以在开发中我们通常使用组件的时候采用的都是局部注册:
    - 局部注册是在我们需要使用到的组件中,通过components属性选项来进行注册;
    - 比如之前的App组件中,我们有data、computed、methods等选项了,事实上还可以有一个components选项;
    - 该components选项对应的是一个对象,对象中的键值对是 组件的名称: 组件对象;
    - ![image-20230207182835272](Vue入门/image-20230207182835272.png)
    -

    ````html
    const app = {
    components:{
    "my-comp":{}
    }
    }
    ````



    ## 八、Vue脚手架

    ### 8.1. Vue的开发模式

    - html
    - .vue

    ### 8.2. Vue CLI安装和使用

    - 安装Vue CLI(目前最新的版本是v5.0.8)

    - 我们是进行全局安装,这样在任何时候都可以通过vue的命令来创建项目;

    - ```nginx
    npm install @vue/cli -g
  • 升级Vue CLI:

    • 如果是比较旧的版本,可以通过下面的命令来升级

    • ```nginx
      npm update @vue/cli -g

      1
      2
      3
      4
      5

      - 通过Vue的命令来创建项目

      - ```nginx
      Vue create 项目的名称
  • 创建项目的过程

    • image-20230207183225477
    • image-20230207183258053
    • Vue CLI的运行原理
      • image-20230207183322649

8.3. Vue项目的目录结构

8.4. 从main.js入口开始,如何一步步创建自己的组件

  • App.vue

8.5. jsconfig文件的作用

8.6. vue不同版本的作用

  • runtime:运行时
  • runtime + compile:运行 + 编译

8.7. css的scoped作用域

8.8. npm init vue @latest创建项目

九、组件间的通信

9.1.组件的嵌套关系

  • 父组件、子组件

9.2. 父传子 - props(重要)

  • 在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:

    • 这个时候我们可以通过props来完成组件之间的通信;
  • 什么是Props呢?

    • Props是你可以在组件上注册一些自定义的attribute;
    • 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
  • Props有两种常见的用法:

    • 方式一:字符串数组,数组中的字符串就是attribute的名称;

    • 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;

    • image-20230207184437027

9.3. 非prop得attribute

  • 数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的写法是如何让

    我们的props变得更加完善的。

  • 当使用对象语法的时候,我们可以对传入的内容限制更多:

    • 比如指定传入的attribute的类型;

    • 比如指定传入的attribute是否是必传的;

    • 比如指定没有传入时,attribute的默认值;

  • 那么type的类型都可以是哪些呢?

    • image-20230207184549979
  • 对象类型的其他写法

    • image-20230207184626775
  • Prop 的大小写命名

    • HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符;

    • 这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名)

      命名;

    • image-20230207184715128

9.4. 子传父 - $emit(重要)

  • 什么情况下子组件需要传递内容到父组件呢?

    • 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;

    • 子组件有一些内容想要传递给父组件的时候;

  • 我们如何完成上面的操作呢?

    • 首先,我们需要在子组件中定义好在某些情况下触发的事件名称;

    • 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;

    • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件;

9.5. 阶段案例练习 - TabControl的封装

9.6. 非父子组件 的通信

9.6.1. Provide/Inject

  • 基本使用
  • 函数写法
  • 数据的响应式
    • computed

9.6.2. 事件总线hy-event-store

  • 在event-bus.js中创建eventBus对象
  • 监听事件
    • eventBus.on()
  • 发出事件
    • eventBus.emit()

十、组件的插槽slot

10.1. 认识Slot的作用

  • 前面我们会通过props传递给组件一些数据,让组件来进行展示;
    • 但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素;
    • 比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片;
    • 我们应该让使用者可以决定某一块区域到底存放什么内容和元素;

10.2. Slot的基本使用和默认值(重要)

  • 这个时候我们就可以来定义插槽slot:

    • 插槽的使用过程其实是抽取共性、预留不同;

    • 我们会将共同的元素、内容依然在组件内进行封装;

    • 同时会将不同的元素使用slot作为占位,让外部决定到底显示什么样的元素;

  • 如何使用slot呢?

    • Vue中将 元素作为承载分发内容的出口;

    • 在封装组件中,使用特殊的元素就可以为封装组件开启一个插槽;

    • 该插槽插入什么内容取决于父组件如何使用;

  • 插槽的默认值

    • image-20230207185313304
  • 插槽的基本使用

    • image-20230207185331555

10.3. Slot的具名插槽 (重要)

  • 事实上,我们希望达到的效果是插槽对应的显示,这个时候我们就可以使用 具名插槽:

    • 具名插槽顾名思义就是给插槽起一个名字, 元素有一个特殊的 attribute:name;

    • 一个不带 name 的slot,会带有隐含的名字 default;

1
2
3
<template>
<slot name="kobe"></slot>
</template>
1
2
3
<nav-bar>
<template v-slot:kobe></template>
</nav-bar>

10.4. 动态插槽名

  • 什么是动态插槽名呢?

    • 目前我们使用的插槽名称都是固定的;

    • 比如 v-slot:left、v-slot:center等等;

    • 我们可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称;

    • image-20230207185445549

10.5. Vue编译作用域

10.6. 作用域插槽使用

  • 核心:将子组件中的数据传递给父组件的插槽来使用

10.7.具名插槽使用的时候缩写

  • 具名插槽使用的时候缩写:

    • 跟 v-on 和 v-bind 一样,v-slot 也有缩写;

    • 即把参数之前的所有内容 (v-slot:) 替换为字符 #;

    • image-20230207185546897

十一、生命周期

11.1. 生命周期函数

  • created
  • mounted
  • unmounted

11.2. refs引入元素/组件

  • 在元素/组件中添加ref属性
  • this.$refs.属性

11.3. 动态组件的属性

  • component is

11.4. keep-alive

  • 让组件缓存起来 存货下来
  • include/exclude/max
  • 存活生命周期函数
    • activated
    • deactivated

11.5. 异步组件的使用

11.5.1. webpack分包处理

  • import()

11.5.2. 异步组件

1
defineAsyncComponent(() => {})

11.6. v-model组件上

1
2
3
4
5
6
7
<counter v-model="appCounter" />

<counter v-bind:modelValue="appCounter" @update:modelValue="appCounter = $event" />


<counter v-model:counter="appCounter" />

11.7. 混入Mixin

  • 组件通过mixins:[]
  • 全局混入:app.mixin({})

十二、Composition API (一)

12.1. 认识组合API

  • options API -> Composition API

12.2. 使用reactive定义响应式数据

  • 复杂类型

12.3. 使用ref定义响应式数据

  • 基本类型
  • 复杂数据
  • template自动解包

12.4. 开发中如何选择reactive/ref

12.5. readonly的使用

12.5.1. 单向数据流

  • vue/react

12.6. reactive函数补充

  • isProxy
  • isReactive
  • isReadonly
  • shallowReactive
  • shallowReadonly

12.7. ref函数的补充

  • toRefs
  • toRef
  • unref
  • toRaw

12.8. setup中不能使用this

十三、Composition API (二)

13.1. computed计算属性 (重点)

13.2. ref获取元素/组件(重点)

13.3. 生命周期注册函数(重点)

  • beforeCreate/Created -> setup

13.4. provide/inject

13.5. watch / watchEffect

  • 区别
    • watch必须指定数据源 effect自动收集依赖
    • watch监听到改变,可以拿到改变前后的value
    • effect默认直接执行一次,watch在不设置immediate为true的情况下,第一次不执行

13.6. 自定义hook练习

13.6.1. useCount练习

13.6.2. useTitle练习

13.6.3. useScrollPosition

13.7. setup语法糖(重点)

  • defineProps
  • defineEmits
  • defineExpose

十四、Vue-Router

14.1. 前端路由的发展历程

  • 后端路由
  • 前后端分离
  • 单页面富应用
    • SPA:single page web application
    • 前端路由

14.2. 改变URL,页面不刷新的两种模式

  • hash模式
  • history模式

14.3. VueRouter的使用过程

  • 安装
    • npm install vue-router
  • 使用:
    • 创建router对象
      • createRouter
      • routes:映射关系
      • history:createWebHashHistory()
    • app.use(router)
    • 使用路径:
      • router-view:占位
      • router-link
        • 编程式导航

14.4. Vue-Router知识点补充

14.4.1. 默认路径

  • path -> redirect

14.4.2. history模式

  • createWebHistory()

14.4.3. router-link其他属性

  • to
  • replace
  • active-class
  • exact-active-class

14.4.4. 路由懒加载-分包处理

14.4.5. 其他属性

  • name
  • meta
    • route.meta

14.5. 动态路由的使用

  • path:/user/:id

14.6. NotFound页面匹配

  • path:/:pathMatch(.*)

14.7. 路由的嵌套使用

  • 在一层路由中添加children属性:
    • {path:“recommend”,component: () => import(“./“)}
    • 在HOme组件中添加
    • 路径跳转

14.8. 编程式导航

14.8.1. 跳转的方式

  • push(路径)
  • push({path/query})
  • replace()

14.8.2. 路径的切换

  • back()
  • forward()
  • go(number)

十五、Vuex

什么是状态管理:

在开发中,我们的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为 状态管理

在前面我们是如何管理自己的状态呢?

  • 在Vue开发中,我们使用组件化的开发方式
  • 而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state
  • 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View
  • 在模块中,我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions

JavaScript开发的应用程序,已经变的越来越复杂了:

  • JavaScript需要管理的状态越来越多,越来越复杂
  • 这些状态包括服务器返回的数据,缓存数据,用户操作产生的数据等
  • 也包括一些UI状态,比如某些元素是否被选中,是否显示加载动效,当前分页

当我们的应用越到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同意状态
  • 来自不同视图的行为需要变更为同意状态

我们是否可以通过组件数据的传递来完成呢?

  • 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态

  • 但是对于复杂的状态管理来说,显然单纯的通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据?、

这就是Vuex背后的思想,它借鉴了Flux,Redux,Elm(纯函数语言,redux有借鉴它的思想):

  • 管理不断变化的state本身是非常困难的

    • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也可能会引起状态的变化

    • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪

  • 因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?

    • 在这种模式下,我们的组件树构成了一个巨大的 视图View

    • 不管在树的哪个位置,任何组件都能获取状态或者触发行为

    • 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码会变得更加结构化和易于维护、跟踪

创建Store:

  • 每一个Vuex应用的核心就是Store仓库:
    • store本质上是一个容器,它包含着你的应用中大部分的状态(state);
  • Vuex的对象和单纯的全局对象有什么区别呢?
    • 第一:Vuex的状态存储时响应式的
      • 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么响应的组件也会被更新
    • 第二:你不能直接改变store中的状态
      • 改变store中的状态的唯一途径就是提交(commit)mutation
      • 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态
  • 使用步骤:
    • 创建store对象
    • 在app中通过插件安装
      • app.use(store)

什么是Module?

  • 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变的非常复杂时,store对象就有可能变的相当臃肿
  • 为了解决以上问题,Vuex允许我们将store分割成模块module
  • 每个模块拥有自己的state,mutation,action,getter,甚至是嵌套子模块

十六、Pinia

什么是Pinia

  • Pinia开始于2019年,最初作为一个实验为Vue重新设计状态管理,让它用起来像组合式API(Composition API)
  • 从那时到现在,最初的设计原则依然是相同的,并且目前兼容的Vue2、Vue3,也并不要求使用Composition API
  • Pinia本质上依然是一个状态管理库,用于跨组件、页面进行状态共享(这点和Vuex,Redux一样)

Pinia和Vuex的区别

  • Pinia最初为了探索Vuex的下一次迭代会是什么样子,结合了Vuex5核心团队讨论中的许多想法
  • 最终,团队终于意识到Pinia已经实现了Vuex5中大部分内容,所以最终决定用Pinia来替代Vuex
  • 与Vuex相比,Pinia提供一个更简单的API,具有更少的仪式,提供了Composition-API 风格的API
  • 最重要的是,在与TypeScript一起使用时具有可靠的类型推断支持

Pinia的优点

  • mutation不再存在
    • 他们经常被认为是非常冗长的
    • 他们最初带来了devtools集成,但这不再是问题
  • 更友好的TypeScript支持,Vuex之前对TypeScript的支持不是很友好
  • 不再有modules的嵌套结构
    • 你可以灵活使用每一个store,它们是通过扁平化的方式相互使用
  • 已不再有命名空间的概念,不需要记住他们的复杂关系

什么是Store?

  • 一个Store是一个实体,他会持有为绑定到你组件树的状态和业务逻辑,也就是保存了全局的状态
  • 它始终存在,并且每个人都可以读取和写入组件
  • 你可以在你的应用程序中定义任意数量的Store来管理你的状态

Store有三个核心概念:

  • state getters actions
  • 等同于组建的data computed methods
  • 一旦store被实例化,你就可以直接在store上访问state、getters、actions中定义的任何属性