Vue2速成手册(原创不易,转载请注明出处)

文章目录

Vue速成手册

0 前言

本文使用的是Vue2.6.14

  • 环境如下:
C:\Users\z004abwh>node -v
v16.14.2

C:\Users\z004abwh>npm -v
8.5.0
  • IDE

    • Vscode
    • 插件Vetur、ES6、HTML和CSS等基础插件
  • 调试工具

    vue devtools

    两种安装方式:

    • 第一种:github下载zip包,

      https://github.com/vuejs/devtools#vue-devtools
      

      最新版本的调试工具要用yarn命令

      npm install -g yarn

      解压从github上下载的vue-devtools,进入目录

      执行:yarn install

      执行:yarn run build

      最后把打包的结果导入到浏览器的扩展程序中

    • 第二种:直接在一个空目录中使用npm i vue-devtools(亲测可用)

      在vue-devtools文件夹下:shells>chrome>manifest.json,将配置里的persistent的值修改为true;

      打开chrome,进入扩展程序管理界面(chrome://extensions/),加载已解压的扩展程序。选择vue-devtools>shells>chrome。加载完成就会出现以下的Vue.js devtools扩展程序。然后重启浏览器,再打开vue项目页面就可以在chrome的Devtools(就是console的那一栏,一般都排在最后面的。)找到Vue面板了。

1 基础篇

项目的目录结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzZauMtX-1663494649201)(D:\markdown\Vue速成手册.assets\image-20220720104855387.png)]

1.1 Hello world

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--创建一个容器-->
<div id="app">
    <!--插值表达式-->
    {{ message }}
</div>

<script src="./vue.js"></script>
<script>
<!--    实例化一个Vue对象-->
    const vm = new Vue({
        // 挂在一个容器
        el: "#app",
        // 创建组件内的数据
        data: {
            message: "Hello Vue 2.6.14"
        }
    })

</script>
</body>
</html>

浏览器调试窗口预留:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EJGdfUx-1663494649202)(D:\markdown\Vue速成手册.assets\image-20220720104954011.png)]

1.1.1 小结

  • 想让Vue工作,必须要创建一个Vue实例,Vue构造函数接收一个唯一参数就是配置对象。
  • app这个div内依然遵守html规范,只是扩展了一些Vue语法
  • id为app这个div被称为Vue的模板
  • Vue实例和容器是一一对应的
  • 真实开发中,一个项目一般只会有一个Vue实例,Vue实例配合组件一起使用
  • data中的数据改变,模板中使用data的位置会自动更新

1.2 模板语法

vue中的模板语法分为两大类

  • 插值语法
    • 用于解析标签内容
    • 写法:{{ xxx }}, xxx是js表达式,可以直接读取到data中的所有区域

1.3 v-bind数据绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-bind数据绑定</title>
</head>
<body>
<div id="app">
<!--    一般写法-->
    <input type="text" v-bind:value="message"> <br><br>
<!--    简写方法-->
    <input type="text" :value="message">
</div>

<script src="./vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            message: "数据绑定"
        }
    })
</script>
</body>
</html>

1.4 v-model双向数据绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-bind数据绑定</title>
</head>
<body>
<div id="app">
<!--    一般写法-->
    <input type="text" v-model:value="message"> <br><br>
</div>

<script src="./vue.js"></script>
<script>
    const vm = new Vue({
        el: "#app",
        data: {
            message: "数据绑定"
        }
    })
</script>
</body>
</html>

1.5 el的两种写法

  • 第一种写法:el做为Vue构造函数的参数对象中的一个key,其值是一个符合css选择器的一个容器

    const vm = new Vue({
        el: '#app'
    })
    
  • 第二种写法:先创建Vue实例,最后通过vm.$mount(‘#app’) 挂载对象

    const vm = new Vue({})
    vm.$mount('#app')
    

1.6 data的两种写法

  • 对象形式

    const vm = new Vue({
    	el: '#app', 
    	data: {
    		message: "hello"
    	}
    })
    
  • 函数形式(在组件中必须是函数形式的)

    const vm = new Vue({
    	el: '#app', 
    	// 正常写法
        data: function(){
    		return {
    			message: "hello world"
    		}
    	}
    	// 简写形式
    	data(){
    		return {
    			message: "hello world"
    		}
    	}
    	// 错误写法
    	data: ()=>{
                console.log(this) # 这个this指向的是Window 
                return{message: 'Hello'}
            }
    })
    

    由Vue管理的函数,一定不要写箭头函数,否则this指向就不再是Vue实例了

1.7 MVVM 模型

img

  • M-Model 指data中的数据

  • V-View 指模板

  • VM-ViewModel 视图模型,Vue实例

  • vm对象

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-knIl6gzX-1663494649202)(D:\markdown\Vue速成手册.assets\image-20220720120634191.png)]

    vm对象里面的属性和其原型对象上面的属性都可以在模板中直接使用。

1.8 数据代理的本质

1.8.1 JS基础知识复习

Object对象是原型链的尽头,也可以说是所有对象的超类,在Object对象上有一个defineProperty方法。下面看下这个方法的具体使用流程:

// 定义一个临时变量,初始化为空
let temp = ''
// 定义一个对象,设置两个属性分别是name和age
let obj = {
    name: '张三',
    age: 18
}
// 调用Object的defineProperty方法
// 1 参数1: 是需要监听的对象
// 2 参数2: 是需要监听的对象里面的某一个属性
// 3 参数3: 用于定制get方法和set方法,get方法的返回值是obj.xxx的结果值,set方法接收一个参数,这个参数是obj.xxx = yyy 中的yyy
Object.defineProperty(obj, 'name', {
    
    get(){
        console.log('obj的name属性被读取了')
        return '小乌龟'
    },
    set(value){
        console.log('obj的name属性被改写了')
        temp = value
    }
})
// 改变name属性的值
obj.name = '张无忌' // 输出: obj的name属性被改写了
// 临时变量被赋值
console.log(temp)  // 输出: 张无忌
// 获取对象属性值方法一
console.log(obj['name']) // 输出: obj的name属性被读取了  小乌龟
// 获取对象属性值方法二
console.log(obj.name)    // 输出: obj的name属性被读取了  小乌龟
  • 一个对象代理另一个对象的读和写
 let obj1 = {
      name: '张三',
      age: 19
  }
  let obj2 = {
      name: '里斯',
      age: 24
  }
  Object.defineProperty(obj1, 'name', {
      get(){
          return obj2.name
      },
      set(value){
          obj2.name = value
      }
  })

  Object.defineProperty(obj1, 'age', {
      get(){
          return obj2.age
      },
      set(value){
          obj2.age = value 
      }
  })
/*
	测试代码
	obj1.name
    '里斯'
    obj1.age
    24
*/

1.8.2 vue中数据代理的解读

vue中的数据代理是通过vm对象来代理data对象中属性的操作

使用vm对象代理data对象的好处是更方便,在模板中可以直接操作data中的数据,在vm实例中可以通过this.xxx直接获取到xxx属性。

new Vue({…})创建vm实例成功后,会把data中的值赋值到vm的_data属性下,vm.xxx本质上会调用vm._ _ data.xxx,_ data.xxx又会调用defineProperty的get()方法,进行实现使用vm对象代理data对象的行为。

img

  • 通过object.defineProperty()把data中所有属性添加到vm上
  • 为每一个添加到vm上的属性都指定一个setter和getter
  • 在getter、setter内部去操作data中对象的属性
  • vue将data中的数据拷贝一份到_data属性中,又将_data里面的属性提到Vue实例中,通过defineProperty实现数据代理,通过getter、setter操作属性,进而操作_data中的属性,_ _ data对data进行数据劫持,实现响应式。

1.9 处理事件

1.9.1 基础用法

  • 以点击事件为例演示Vue中如何绑定事件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>绑定事件练习</title>
    </head>
    <body>
    
    <div id="app">
        <!--    v-on普通写法-->
        <button v-on:click="test">绑定事件练习</button>
        <!--    v-on简单写法-->
        <button @click="test">绑定事件简单写法</button>
        <!--    参数传递@click="sendParams"等价于@click="sendParams($event)" $event保存着绑定的事件-->
        <button @click="sendParams">绑定事件默认传参</button>
        <!--    如果有多个参数-->
        <button @click="sendMultiParams($event, 123)">绑定事件传递多个参数</button>
    </div>
    
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                msg: 'hello'
            },
            methods: {
                test() {
                    alert(this.msg)
                },
                sendParams(e) {
                    console.log(e) // 输出点击事件
                },
                sendMultiParams(e, x) {
                    console.log(e) // 输出点击事件
                    console.log(x) // 输出123
                }
            }
        })
    </script>
    </body>
    </html>
    
    • 简单总结一下:

      • 绑定事件的正常写法: v-on:event = “callback”; 绑定事件的简单写法:@event = “callback”

      • @click=“callback” 等价于 @click=“callback($event)” ,如果回调不需要传其他参数那么前面的两种写法都可以

      • 如果回调需要用到事件对象和其他参数,那必须这样写:@click=“callback($event, args)”

      • 回调函数定义在methods上

  • methods函数中的this指向问题

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>methods中this的指向问题</title>
    </head>
    <body>
    <div id="app">
        <button @click="getMsg">获取data中的msg</button>
        <button @click="getMsg2" style="background-color: red">获取data中的msg</button>
    </div>
    <script src="./vue.js"></script>
    
    <script>
        msg = '全局我最大'
    
        const vm = new Vue({
            el: '#app',
            data: {
                msg: '嘻嘻哈哈好开心'
            },
            methods: {
                getMsg: function () {
                    console.log(this)     // 输出 : Vue实例对象 也就是vm
                    console.log(this.msg) // 输出 : 嘻嘻哈哈好开心
                },
                getMsg2: () => {
                    console.log(this)     // 输出:Window 全局对象
                    console.log(this.msg) // 输出: 全局我最大
                }
            }
    
        })
    </script>
    </body>
    </html>
    
    • 简单总结:
      • methods中定义的函数最终会出现在vm对象或者组件实例对象中
      • methods中的函数必须使用普通的函数定义方式,不可以使用箭头函数,如果使用箭头函数,会改变函数内this的指向

1.9.2 常用事件修饰符

  • prevent 阻止默认事件
  • stop 阻止事件冒泡
  • once 事件只触发一次
  • capture 使用事件的捕获模式
  • self 只有event.target是当前操作的元素才触发事件
  • passtive 事件的默认行为立即执行,无需等待事件回调执行完毕

事件描述符可以连续写

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>事件修饰符</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
    <style>
      * {margin-top: 20px;}
      .demo1 {height: 50px;background-color: skyblue;}
      .box1 {padding: 5px;background-color: skyblue;}
      .box2 {padding: 5px;background-color: white;}
      .list {width: 200px;height: 200px;background-color: skyblue;overflow: auto;}
      li {height: 100px;}
    </style>
  </head>
  <body>

    <div id="root">
      <h2>欢迎来到{{ name }}学习</h2>
      <!-- 阻止默认事件(常用) -->
      <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

      <!-- 阻止事件冒泡(常用) -->
      <div class="demo1" @click="showInfo">
        <button @click.stop="showInfo">点我提示信息</button>
        <!-- 修饰符可以连续写 -->
        <!-- <a href="http://www.qq.com" @click.prevent.stop="showInfo">点我提示</a> -->
      </div>

      <!-- 事件只触发一次(常用) -->
      <button @click.once="showInfo">点我提示信息</button>

      <!-- 使用事件的捕获模式 -->
      <div class="box1" @click.capture="showMsg(1)">
        div1
        <div class="box2" @click="showMsg(2)">
          div2
        </div>
      </div>

      <!-- 只有event.target是当前操作的元素时才触发事件; -->
      <div class="demo1" @click.self="showInfo">
        <button @click="showInfo">点我提示信息</button>
      </div>

      <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
      <!-- scroll是滚动条滚动,passsive没有影响 -->
      <!-- wheel是鼠标滚轮滚动,passive有影响 -->
      <ul @wheel.passive="demo" class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
      </ul>
    </div>

    <script type="text/javascript">
      Vue.config.productionTip = false

      new Vue({
        el: '#root',
        data: {
          name: '尚硅谷'
        },
        methods: {
          showInfo(e) {
            alert('同学你好!')
            // console.log(e.target)
          },
          showMsg(msg) {
            console.log(msg)
          },
          demo() {
            for (let i = 0; i < 100000; i++) {
              console.log('#')
            }
            console.log('累坏了')
          }
        }
      })
    </script>
  </body>
</html>

1.9.3 监听键盘事件

  • Vue定制的常用按键别名

    • enter 回车
    • delete 删除
    • esc 退出
    • space 空格
    • up
    • down
    • left
    • right
    • tab 必须配合keydown使用
  • 自定义按键别名

    Vue.config.KeyCodes.自定义键名 = 键码
    
  • 案例

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>键盘事件</title>
        <!-- 引入Vue -->
        <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
    
        <div id="root">
          <h2>欢迎打开{{name}}笔记</h2>
          <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"><br/>
          <input type="text" placeholder="按下tab提示输入" @keydown.tab="showInfo"><br/>
          <input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo"><br/>
        </div>
    
        <script type="text/javascript">
          Vue.config.productionTip = false	// 阻止 vue 在启动时生成生产提示。
          Vue.config.keyCodes.huiche = 13		// 定义了一个别名按键
    
          new Vue({
            el: '#root',
            data: {
              name: 'cess'
            },
            methods: {
              showInfo(e) {
                // console.log(e.key,e.keyCode)
                console.log(e.target.value)
              }
            },
          })
        </script>
      </body>
    </html>
    

1.10 计算属性

1.10.1 一个小例子引入计算属性

  • 使用插值表达式实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性引入小例子</title>
    </head>
    <body>
    
    <div id="app">
        first name: <input type="text" v-model:value="firstName"/> <br>
        last name: <input type="text" v-model:value="lastName"/>
        <h3 style="color: red">full name: {{ firstName + '-' + lastName }}</h3>
    </div>
    
    <script src="./vue.js"></script>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三',
            },
        })
    
    </script>
    
    </body>
    </html>
    
  • 使用methods实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性引入小例子</title>
    </head>
    <body>
    
    <div id="app">
        first name: <input type="text" v-model:value="firstName"/> <br>
        last name: <input type="text" v-model:value="lastName"/>
        <h3 style="color: red">full name: {{ getFullName() }}</h3>
    
    
    </div>
    
    <script src="./vue.js"></script>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三',
            },
            methods: {
                getFullName() {
                    console.log('@getFullName被执行了。。。')
                    return this.firstName + this.lastName
                },
            }
        })
    
    </script>
    
    </body>
    </html>
    
  • 使用计算属性写法

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性引入小例子</title>
    </head>
    <body>
    
    <div id="app">
        first name: <input type="text" v-model:value="firstName"/> <br>
        last name: <input type="text" v-model:value="lastName"/>
        <h3 style="color: green">full name: {{ fullName }}</h3>
    </div>
    
    <script src="./vue.js"></script>
    <script>
        Vue.config.productionTip = false
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三',
            },
            computed: {
                // 完整写法
                fullName: {
                    get() {
                        return this.firstName + '-' + this.lastName
                    },
                    set(value) {
                        let arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
                // 如果只是获取数据,那么可以直接使用简写方式
                fullName(){
            		return this.firstName + '-' + this.lastName
        		}
            }
        })
    
    </script>
    
    </body>
    </html>
    

1.10.2 计算属性小结

计算属性一般用于通过已知属性计算获取到的值,例如上面的例子。姓名都是已经的属性。连接起来获取全名

计算属性的原理也是利用Object.defineProperty()方法提供的getter和setter

get函数初次读取时执行一次,数据变化时执行一次

与methods相比,计算属性有缓存机制,数据不变化就不会更新

计算属性会出现在vm实例对象上,可以直接在模板中像data中的数据一样使用

如果需要改变计算属性,并响应式的更新页面,那么需要设计set方法,如果不需要修改计算属性和其相关的属性,那么使用简写格式是最好用的。

1.11 侦听属性watch用法

watch监视属性用于监视属性的变化,当属性发生变化时,回调函数自动被调用。

监视的属性必须存在才能被监视,可以是data里面的属性也可以是计算属性

监视属性同样具有两种写法:

  • 第一种是在配置对象中直接传入
  • 第二种是使用vm实例的$watch函数

1.11.1 watch基本用法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>watch用法</title>
</head>
<body>
<div id="app">
    <h3>好好 {{ msg }}</h3>
    <button @click="changeStatus">切换状态</button>
</div>

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

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            flag: true,
            msg: ''
        },
        methods: {
            changeStatus() {
                this.flag ? this.msg = '学习' : this.msg = '玩耍'
                this.flag = !this.flag
            }
        },
        // 监视属性,key必须是watch,vue中已经写死,value是一个对象
        watch: {
            // 监视的属性,可以是data也可以是计算属性,当被监视的属性发生变化时,handler函数会被调用
            msg: {
                // 内部钩子函数,接收两个值,分别是修改后的值和修改前的值
                handler(newVal, oldVal) {
                    console.log('msg被修改了', newVal, oldVal)
                }
            }
        }
    })
</script>

</body>
</html>
  • 计算属性的简写方式

    		/*前面省略*/
    		watch: {
                msg(newVal, oldVal){
                    console.log('msg被修改了', newVal, oldVal)
                }
            }
            /*后面省略*/
    
  • 如果想加载时就执行一次,那么需要配置immediate属性

       		 /*前面省略*/
    		watch: {
                msg: {
                    immediate: true,  //加载完成就触发一次
                    handler(newVal, oldVal) {
                        console.log('msg被修改了', newVal, oldVal)
                    }
                }
            }
             /*后面省略*/
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFuLA8wM-1663494649203)(D:\markdown\Vue速成手册.assets\image-20220721110751321.png)]

    加载时候就执行了一次监听属性的回调函数,这时候未修改时候的值是undefined。

1.11.2 监视复杂对象

在js中对象和数组属于复杂对象,复杂对象的值是一个指针,指向的是堆中的地址,修改复杂对象内部的值并不会改变对象本身,所以也就不会触发监视属性。

  • 监视复杂对象的错误写法

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>watch深度监视案例</title>
    </head>
    <body>
    
    <div id="app">
        <h1>个人信息</h1>
        <input type="text" :value="person.name">
        <input type="text" :value="person.age"/>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
    </div>
    
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                person: {
                    name: '张三',
                    age: 12
                }
            },
            methods: {
                changeName() {
                    this.person.name = 'lisi'
                },
                changeAge() {
                    this.person.age = 99
                },
            },
            watch: {
                person: {
                    handler(newValue, oldValue) {
                        console.log('person被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
                    }
                }
            }
    
        })
    </script>
    </body>
    </html>
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju1c6FXJ-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721112249335.png)]

    无论怎么点击按钮,handler回调都不会执行。

  • 监视复杂对象的正确写法1

    		watch: {
                'person.name': {
                    handler(newValue, oldValue) {
                        console.log('person.name被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
                    }
                },
                'person.age': {
                    handler(newValue, oldValue) {
                        console.log('person.age被修改了' + '新值是:' + newValue + '老值是:' + oldValue)
                    }
                }
    
            }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o2Pn9EES-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721112442174.png)]

  • 监视复杂对象的正确写法2

    		watch: {
                person: {
                    deep: true,
                    handler(newValue, oldValue) {
                        console.log('person.name被修改了' + '新值是:' + newValue.name + '老值是:' + oldValue.name)
                        console.log('person.age被修改了' + '新值是:' + newValue.age + '老值是:' + oldValue.age)
                    }
                },
    

    deep属性可以监听到复杂对象内部的数据

1.12 计算属性与侦听属性的区别

1.12.1 小实例讲解计算属性与侦听属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffnji6u6-1663494649204)(D:\markdown\Vue速成手册.assets\image-20220721114031722.png)]

  • 使用计算属性实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性与侦听属性的用法比对</title>
    </head>
    <body>
    <div id="app">
        <input type="text" v-model:value="firstName"> <br>
        <input type="text" v-model:value="lastName">
        <h3>{{ fullName }}</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三'
            },
            computed: {
                fullName(){
                    return this.firstName + '-' + this.lastName
                }
            }
    
        })
    </script>
    </body>
    </html>
    

    这里的代码不做过多的解释,很基础的实现过程。

  • 使用侦听属性实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性与侦听属性的用法比对</title>
    </head>
    <body>
    <div id="app">
        <input type="text" v-model:value="firstName"> <br>
        <input type="text" v-model:value="lastName">
        <h3>{{ fullName }}</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三',
                fullName: '张-三'
            },
            // computed: {
            //     fullName(){
            //         return this.firstName + '-' + this.lastName
            //     }
            // }
            watch: {
                firstName(newVal){
                    this.fullName = newVal + this.lastName
                },
                lastName(newVal){
                    this.fullName = this.firstName + newVal
                }
            }
        })
    </script>
    </body>
    </html>
    

1.12.2 更新一下需求

我现在的需求是,如果名字更新那么全名立即更新,如果是姓更新了,等一秒钟再更新全名,更新需求后发现用计算属性很难实现了

  • 用侦听属性实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>计算属性与侦听属性的用法比对</title>
    </head>
    <body>
    <div id="app">
        <input type="text" v-model:value="firstName"> <br>
        <input type="text" v-model:value="lastName">
        <h3>{{ fullName }}</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                firstName: '张',
                lastName: '三',
                fullName: '张-三'
            },
            // computed: {
            //     fullName(){
            //         return this.firstName + '-' + this.lastName
            //     }
            // }
            watch: {
                firstName(newVal) {
                    setTimeout(() => {
                        this.fullName = newVal + this.lastName
                    }, 1000)
                },
                lastName(newVal) {
                    this.fullName = this.firstName + newVal
                }
            }
        })
    </script>
    </body>
    </html>
    

1.12.3 小结

  • 计算属性和侦听属性的对比

    • 计算属性

      计算属性能完成的功能,侦听属性都可以完成

    • 侦听属性

      侦听属性能够完成的功能,计算属性不一定能完成,比如实现一些异步操作,在一个属性修改1s后执行一个动作。

  • 关于this指向的小总结,极其重要

    • 所有Vue管理的函数,最好写成普通函数,这样this指向的是vm或者是组件实例对象
    • 所有不被Vue管理的函数,比如定时器函数,ajax回调函数,promise回调函数等,最好写成箭头函数,这样this的指向还是vm或者组件实例对象。

1.13 使用v-bind绑定样式

通过v-bind绑定任意dom元素的class属性,通过对class属性赋值

绑定样式的写法:

:class = 'xxx'  // xxx可以是字符串、数组、对象
  • 字符串写法用于:类名不确定、要动态获取
  • 数组写法用于:要绑定多个样式,个数也不确定,名字也不确定
  • 对象写法用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用

也可以直接绑定dom元素的style属性

:style="[a,b]" // 其中a,b是样式对象
:style="{fontSize: xxx}" 其中xxx是动态值

示例代码:

<style>
  .basic {width: 300px;height: 50px;border: 1px solid black;}
  .happy {border: 3px solid red;background-color: rgba(255, 255, 0, 0.644);
    background: linear-gradient(30deg, yellow, pink, orange, yellow);}
  .sad {border: 4px dashed rgb(2, 197, 2);background-color: skyblue;}
  .normal {background-color: #bfa;}
  .atguigu1 {background-color: yellowgreen;}
  .atguigu2 {font-size: 20px;text-shadow: 2px 2px 10px red;}
  .atguigu3 {border-radius: 20px;}
</style>

<div id="root">
  <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
  <div class="basic" :class="mood" @click="changeMood">{{name}}</div><br/><br/>

  <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
  <div class="basic" :class="classArr">{{name}}</div><br/><br/>

  <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
  <div class="basic" :class="classObj">{{name}}</div><br/><br/>

  <!-- 绑定style样式--对象写法 -->
  <div class="basic" :style="styleObj">{{name}}</div><br/><br/>

  <!-- 绑定style样式--数组写法 -->
  <div class="basic" :style="styleArr">{{name}}</div>
</div>

<script type="text/javascript">
  Vue.config.productionTip = false

  const vm = new Vue({
    el: '#root',
    data: {
      name: '尚硅谷',
      mood: 'normal',
      classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
      classObj: {
        atguigu1: false,
        atguigu2: false,
      },
      styleObj: {
        fontSize: '40px',
        color: 'red',
      },
      styleObj2: {
        backgroundColor: 'orange'
      },
      styleArr: [
        {
          fontSize: '40px',
          color: 'blue',
        },
        {
          backgroundColor: 'gray'
        }
      ]
    },
    methods: {
      changeMood() {
        const arr = ['happy', 'sad', 'normal']
        const index = Math.floor(Math.random() * 3)
        this.mood = arr[index]
      }
    },
  })
</script>

1.14 条件渲染

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>条件渲染</title>
</head>
<body>
<div id="app">
    <h1>打分系统</h1>
    <label for="number">分数:</label>
    <input type="text" v-model:value="number" id="number">
    <button @click="getRandomNumber">获取分数</button>
    <br>
    <hr>
    <p>等级:</p>
    <p v-if="number>=80">A</p>
    <p v-else-if="number<80 && number>=60">B</p>
    <p v-else-if="number<60 && number>=40">C</p>
    <p v-else>D</p>
</div>
<script src="./vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            number: 0,
        },
        methods: {
            getRandomNumber() {
                this.number = Math.round(Math.random(1, 100) * 100)
            }
        }
    })
</script>
</body>
</html>
  • 小结

    • 写法

      v-if = "表达式"
      v-else-if = "表达式"
      v-else
      
    • v-if 与v-show的区别

      v-if 会直接操作dom元素,直接删除dom元素,v-if用于切换不频繁的场景
      v-show 不会直接操作dom元素,v-show操作的是display属性,不会直接操作dom元素,用于频繁切换的场景
      
    • 如果需要使用template标签对整块结构进行操作,那么只支持v-if,不支持v-show

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>v-if与v-show</title>
      </head>
      <body>
      <div id="app">
         <!--在template标签内智能使用v-if 不能使用v-show -->
        <template v-if="isShow">
            <p>静夜思</p>
            <br>
            <p>窗前明月光</p>
            <p>疑是地上霜</p>
            <p>举头望明月</p>
            <p>低头思故乡</p>
        </template>
          <button @click="show"> 显示/隐藏 </button>
      </div>
      <script src="./vue.js"></script>
      <script>
        const vm = new Vue({
            el: '#app',
            data: {
                isShow: true
            },
            methods: {
                show(){
                    this.isShow = !this.isShow
                }
            }
        })
      </script>
      </body>
      </html>
      

1.15 v-for

v-for用于遍历数组、对象、字符串

  • 语法:

    <li v-for=(item, index) of items :key="index"></li>
    
  • 示例代码

    <title>基本列表</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    
    <div id="root">
      <!-- 遍历数组 -->
      <h3>人员列表(遍历数组)</h3>
      <ul>
        <li v-for="(p,index) of persons" :key="index">{{ p.name }}-{{ p.age }}</li>
      </ul>
    
      <!-- 遍历对象 -->
      <h3>汽车信息(遍历对象)</h3>
      <ul>e
        <li v-for="(value,k) of car" :key="k">{{ k }}-{{ value }}</li>
      </ul>
    
      <!-- 遍历字符串 -->
      <h3>测试遍历字符串(用得少)</h3>
      <ul>
        <li v-for="(char,index) of str" :key="index">{{ char }}-{{ index }}</li>
      </ul>
    
      <!-- 遍历指定次数 -->
      <h3>测试遍历指定次数(用得少)</h3>
      <ul>
        <li v-for="(number,index) of 5" :key="index">{{ index }}-{{ number }}</li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el: '#root',
        data: {
          persons: [
            { id: '001', name: '张三', age: 18 },
            { id: '002', name: '李四', age: 19 },
            { id: '003', name: '王五', age: 20 }
          ],
          car: {
            name: '奥迪A8',
            price: '70万',
            color: '黑色'
          },
          str: 'hello'
        }
      })
    </script>
    

    image.png

1.15.x key原理 diff算法 vm.$set() api的用法

待补充

1.16 收集表单数据

  • 表单类型

    • text :v-model收集value值
    • radio:value值
    • checkbox :
      • 没有配置value属性, 那么收集的是checked属性(勾选与未勾选,是布尔值)
      • 如果配置value属性:
        • v-model的初始值是非数组,那么收集的是checked属性(勾选或者未勾选,布尔型)
        • v-model的初始值是数组,那么收集的就是value组成的数组
  • v-model 修饰符

    • lazy 失去焦点后再收集数据
    • number 输入字符串转换成有效的数字
    • trim 输入首尾空格过滤
  • 收集表单示例

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>使用v-model收集表单数据</title>
    </head>
    <body>
    
    <div id="root">
        <form @submit.prevent="demo">
            账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            密码:<input type="password" v-model="userInfo.password"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            性别:
            男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
            爱好:
            学习<input type="checkbox" v-model="userInfo.hobby" value="study">
            打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
            吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
            <br/><br/>
            所属校区
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">成都</option>
            </select>
            <br/><br/>
            其他信息:
            <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
            <input type="checkbox" v-model="userInfo.agree">阅读并接受
            <a href="https://www.yuque.com/cessstudy">《用户协议》</a>
            <button>提交</button>
        </form>
    </div>
    
    <script src="./vue.js"></script>
    
    <script type="text/javascript">
        Vue.config.productionTip = false
    
        new Vue({
            el: '#root',
            data: {
                userInfo: {
                    account: '',
                    password: '',
                    age: 18,
                    sex: 'female',
                    hobby: [],
                    city: 'beijing',
                    other: '',
                    agree: ''
                }
            },
            methods: {
                demo() {
                    console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
    
    </body>
    </html>
    

1.17 自定义过滤器(vue3将移除)

对需要再模板中显示的数据进行特定格式化后再显示,适合做一些简单的逻辑

  • 注册过滤器

    • 全局过滤器

      Vue.filter(name, callback) 全局过滤器
      
    • 局部过滤器

      new Vue({
      	filters: {
      		name: callback
      	}
      })
      
  • 使用过滤器

    {{ xxx | 过滤器名 }}
    
    v-bind:属性名 = ”xxx | 过滤器名“ 
    

    过滤器可以接收额外参数,多个过滤器可以串联

    并不是改变源数据,而是产生新的数据用于模板的展示

  • 代码示例

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8" />
      <title>过滤器</title>
      <script type="text/javascript" src="./vue.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
    </head>
    <body>
    <div id="root">
      <h2>时间</h2>
      <h3>当前时间戳:{{time}}</h3>
      <h3>转换后时间:{{time | timeFormater()}}</h3>
      <h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
      <h3>截取年月日:{{time | timeFormater() | mySlice}}</h3>
    </div>
    </body>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      // 全局过滤器
      Vue.filter('mySlice',function(value){
        return value.slice(0,11)
      })
      new Vue({
        el:'#root',
        data:{
          time:1626750147901,
        },
        // 局部过滤器
        filters:{
          timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
            return dayjs(value).format(str)
          }
        }
      })
    </script>
    </html>
    

1.18 vue内置指令小结

  • v-bind

  • v-model

  • v-for

  • v-on

  • v-show

  • v-if

  • v-else-if

  • v-else

  • v-text

    • 向其所在的节点中渲染文本内容
    • 与插值语法的区别,v-text会直接替换掉dom元素中的其他内容,插值表达式可以拼接,更加灵活
  • v-html

    • v-html会解析html元素,可能造成xss攻击
    • v-html与插值表达式的区别也是前者可以替换掉dom中的内容
    • v-html与v-text的区别是前者可以解析html元素,后者不可以
  • v-cloak

    • 使用css配和v-cloak可以解决网速慢时页面展示出{{ xxx }} 的问题

    • 示例代码

      <title>v-cloak指令</title>
      
      <style>
        [v-cloak] {
          display:none;
        }
      </style>
      
      <div id="root">
        <h2 v-cloak>{{ name }}</h2>
      </div>
      
      // 够延迟5秒收到vue.js
      <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
      
      <script type="text/javascript">
        console.log(1)
        Vue.config.productionTip = false
        new Vue({
          el:'#root',
          data:{name:'cess'}
        })
      </script>
      
  • v-once

    • v-once所在的dom节点只在第一次动态渲染后,就视为静态内容了
    • 用于优化性能
  • v-pre

    • 跳过v-pre修饰的dom节点,不参与编译过程
    • 加快编译过程

1.19 自定义Vue指令

待补充

1.20 vue生命周期与钩子函数

这是vue基础的最后一个内容,也是vue中最重要的内容。

生命周期.png

1.20.1 小实例引入vue生命周期

需求是这样的,我想打开网页就能看到文字具有动态效果。

不使用vue周期函数的实现代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>练习生命周期</title>
</head>
<body>

<div id="app">
    <h1 :style="{opacity: opacity}">欢迎就入魔鬼训练营</h1>
</div>

<script src="./vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            opacity: 1
        },
    })
    setInterval(function () {
        if (vm.opacity>0) {
            vm.opacity -= 0.01
        } else {
            vm.opacity = 1
        }
    }, 16)
</script>

</body>
</html>
  • 缺点:
    • 每次打开界面都会创建一个定时器,定时器数量会越来越多,造成浏览器卡死。
    • vue外部使用vm也是不推荐的

使用vue钩子的实现代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>练习生命周期</title>
</head>
<body>

<div id="app">
    <h1 :style="{opacity: opacity}">欢迎就入魔鬼训练营</h1>
</div>

<script src="./vue.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            opacity: 1
        },
        mounted(){
            this.interval = setInterval(()=>{
                if (vm.opacity > 0) {
                    vm.opacity -= 0.01
                } else {
                    vm.opacity = 1
                }
            }, 16)
        },
        beforeDestroy(){
            clearInterval(this.interval)
        }
    })
</script>

</body>
</html>

1.20.2 小结

常用的生命周期函数:

  • mounted发送ajax请求,启动定时器,绑定自定义事件,订阅消息等初始化操作
  • beforeDestroy清除定时器、解绑自定义事件,取消订阅消息等收尾工作

关于销毁Vue实例:

  • 销毁后借助Vue开发者工具看不到任何信息
  • 销毁后自定义事件会失效,但原生dom事件依然有效
  • 一般不会在beforedestroy操作数据,因为即便操作数据,也不会再触发更新流程了

2 组件化开发

2.1 理解组件化

2.1.1 概念理解

  • 模块
    • 在前端工程中模块一般指定一个.js文件,可以在js文件引入其他模块暴露的信息,模块是一个名词,用于描述一个单独的js文件
  • 模块化
    • 使用多文件js编写的项目就可以称为模块化的项目,模块化是一个形容词,用来形容项目
  • 组件(用于实现局部功能的代码和资源的集合)
    • 组件用于描述前端项目中的一块内容,组件具有一定的可复用性,类似于函数,可以通过传递参数的不同输出不同的结果
  • 组件化
    • 组件化同样是一个形容词,用于形容项目

2.1.2 模块化编写项目的缺点

在没有类似vue这种前端框架之前,我们都是用模块化编写的前端项目的,模块化开发的缺点是依赖关系混乱,不好维护,代码复用性不高,具有很高的耦合性,代码冗余。

image.png

组件化编程具有低耦合,代码复用率高等特点:

image.png

2.2 非单文件组件

  • 定义

    一个文件中包含有n个组件

2.3 单文件组件

  • 定义

    一个文件中只有1个组件

2.4 组件的基本使用

使用组件的三大步骤

1定义组件

2注册组件

3使用组件

2.4.1 定义组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建组件</title>
</head>
<body>
<div id="app">
    <!--在容器内使用组件-->
    <h1>{{ title }}</h1>
    <person></person>
    <hr>
    <dog></dog>
</div>

<template id="cdog">
    <div>
        <h1>狗详细信息</h1>
        <label for="dog-name"></label>
        <input type="text" v-model:value="name" id="dog-name"> <br>
        <label for="dog-sex"></label>
        <input type="text" v-model:value="sex" id="dog-sex">
        <button @click="showDogInfo">显示狗的信息</button>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    // 第一步: 创建组件
    const person = Vue.extend({
        template: `
          <div>
          <label for="name"></label>
          <input id="name" type="text" v-model:value="name"> <br>
          <label for="age"></label>
          <input id="age" type="text" v-model:value="age">
          <button @click="showInfo">显示人员信息</button>
          </div>
        `,
        data() {
            return {
                name: '张三',
                age: 19
            }
        },
        methods: {
            showInfo() {
                alert(this.name + '-' + this.age)
            }
        }
    })

    const dog = Vue.extend(
        {
            template: '#cdog',
            data() {
                return {
                    name: '大黄',
                    sex: '小公狗'
                }
            },
            methods: {
                showDogInfo() {
                    alert(this.name + '-' + this.sex)
                }
            }
        }
    )

    // 第二步:注册组件,全局注册,对所有的vm都有效
    Vue.component('person', person)


    const vm = new Vue({
        el: '#app',
        data: {
            title: '人员详细信息'
        },
        // 局部注册组件,只能在当前vm的容器内使用
        components: {
            dog,
        }
    })
</script>
</body>
</html>
  • 对上面的案例简单总结:

    • 定义组件

      普通写法

      const component-name = Vue.extend({
      	
          data(){
      		return {
      			xxxx: 'yyyy'
      		}
      	}
      })
      

      简单写法

      const component-name = {
      	data(){},
      	methods: {}
      }
      

      Vue.extend传入的配置项于new Vue() 传入的配置项基本相同,有两点需要注意:

      Vue.extend中不能有el,因为所有的组件最终都会挂载到vm上面

      Vue.extend中的data必须写成函数形式的,不可以写成对象形式的

    • 注册组件

      • 全局注册

        Vue.component('组件名', 组件)
        //例如: 
        Vue.component('person', person) 
        // 组件名是最终在模板中使用的标签名
        
      • 局部注册

        new Vue({
        	'''
        	components: {
        		组件名: 组件
        	}
        })
        // 一般组件名与组件都会定义成一样的名字,所以在局部注册的时候一般会使用简写形式
        new Vue({
            ’‘’
            components: {
            	组件, 
        	}
        })
        
    • 使用组件

      • 用法1

        <组件名></组件名>
        
      • 用法2 (需要脚手架支持,否则会有问题)

        <组件名/>
        
  • 注意事项

    • 关于组件名

      • 如果是一个单词组成

        • 第一种写法(首字母小写):person
        • 第二种写法(首字母大写):Person
      • 如果是多个单词组成

        • 第一种写法(kebab-case命名): person-info
        • 第二种写法(CamelCase命名): PersonInfo
      • 特别注意

        • 组件名要尽可能回避html标签

        • 可以在定义组件时候使用name配置项指定组件在开发者工具中呈现的名字

          const cat = {
                  name: 'xiao-zhu-pei-qi',
                  template: '#ccat',
                  data(){
                      return {
                          msg: '我是一只小花猫'
                      }
                  }
              }
          

          [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyDDRgvZ-1663494649205)(D:\markdown\Vue速成手册.assets\image-20220722133451776.png)]

2.5 组件的嵌套用法

vm可以理解为所有组件的父组件,一个组件内部可以挂多个组件,组件是可以嵌套的,类似与数据结构中的树,vm类似于根节点

img

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>组件嵌套</title>
</head>
<body>

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

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

    const student = {
        name: 'student',
        data() {
            return {
                name: '张三',
                age: 11
            }
        },
        template: `
          <div>
          <h3>学生信息展示</h3>
          <p>学生姓名: {{ name }}</p>
          <p>学生年龄: {{ age }}</p>
          </div>
        `,
    }

    const school = {
        name: 'school',
        data() {
            return {
                name: '家里蹲',
                address: '蹲家里'
            }
        },
        template: `
          <div>
          <h2>学校信息展示</h2>
          <p>学校名称:{{ name }}</p>
          <p>学校地址:{{ address }}</p>
          <hr>
          <student></student>
          </div>
        `,
        components: {
            student,
        }

    }

    const app = {
        name: 'app',
        template: `
          <div>
          <h1>{{ title }}</h1>
          <school></school>
          </div>
        `,
        data() {
            return {
                title: 'app标题'
            }
        },
        components: {
            school
        }
    }

    const vm = new Vue({
        el: '#app',
        components: {
            app
        }
    })
</script>
</body>
</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4aug9T0-1663494649206)(D:\markdown\Vue速成手册.assets\image-20220722142336299.png)]

2.6 关于VueComponent

通过const school = Vue.extend({options}),会定义一个组件,school本质上是一个VueComponent构造函数

当我们在模板中写 或者,Vue解析时会创建school的实例对象,Vue自动调用了new Component(options)

每次调用Vue.extend,都会返回一个全新的VueComponent,不同的组件是不同的对象

关于this指向:

组件配置中data函数、methods中的函数、watch中的函数、computed中的函数他们的this均是VueComponent实例对象

new Vue(options) 配置中:data函数或者对象、 methods中的函数、watch中的函数、computed中的函数他们的this均是Vue实例对象

VueComponent实例对象简称vc, Vue实例对象简称vm

组件实例对象vc可以访问到Vue原型上面的属性和方法

**VueComponent.prototype. _ _ proto _ _ === Vue.prototype **

image.png

2.6.1 简单说一下prototype和 _ _ proto _ _

_ _ proto _ _ 是对象特有的,负责对象的属性方法的查找,如果对象自身没有需要的属性或者方法,_ _ proto _ _会指向对象构造函数的原型对象, _ _ proto _ _的终点是null

prototype 是函数特有的,但是因为js中函数也是一种特殊的对象,所以在函数中也有 _ _ proto _ _ 这是js原型结构中很难理解的地方

2.7 单文件组件

单文件组件就是一个文件只有一个组件,一个文件只完成局部的功能和包装局部的静态资源,vue中单文件组件的文件后缀是.vue

定义vue中单文件组件的常规目录结构结构:

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2022/7/22     14:49            114 app.vue 
-a----         2022/7/22     14:49            132 index.html
-a----         2022/7/22     14:48              0 main.js
-a----         2022/7/22     14:49            117 school.vue
-a----         2022/7/22     14:49            118 student.vue

  • school.vue

    <template>
        <div id='Demo'>
            <h2>学校名称:{{name}}</h2>
            <h2>学校地址:{{address}}</h2>
            <button @click="showName">点我提示学校名</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'School',
            data() {
                return {
                    name:'UESTC',
                    address:'成都'
                }
            },
            methods: {
                showName(){
                    alert(this.name)
                }
            },
        }
    </script>
    
    <style>
        #Demo{
            background: orange;
        }
    </style>
    
  • student.vue

    <template>
        <div>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生年龄:{{age}}</h2>
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',
            data() {
                return {
                    name:'cess',
                    age:20
                }
            },
        }
    </script>
    
  • App.vue

    <template>
        <div>
            <School></School>
            <Student></Student>
        </div>
    </template>
    
    <script>
        import School from './School.vue'
        import Student from './Student.vue'
    
        export default {
            name:'App',
            components:{
                School,
                Student
            }
        }
    </script>
    
  • main.js

    import App from './App.vue'
    
    new Vue({
        template:`<App></App>`,
        el:'#root',
        components:{App}
    })
    
  • index.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>单文件组件练习</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="../../js/vue.js"></script>
        <script src="./main.js"></script>
    </body>
    </html>
    

3 vue脚手架

3.1 初始化脚手架

3.1.1 基本介绍

vue-cli(vue脚手架)是vue官方提供的标准化开发平台

目前最新 的版本是5.x

3.1.2 使用步骤

  • 配置淘宝镜像cnpm (这一步不是必须的,主要为了安装扩展包时候加速的)

    npm config set register http://registry.npm.taobao.org
    
  • 全局安装@vue/cli

    npm install -g @vue/cli
    
  • 切换到需要创建项目的目录,创建一个空的vue项目

    vue create xxx // xxx是项目名称
    
  • 选择使用的vue版本

    选择vue2,等待下载安装依赖包,这里需要联网

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DAbFV7wu-1663494649206)(D:\markdown\Vue速成手册.assets\image-20220722150357812.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ab37CKoo-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220722150416187.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-slE6bzIt-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220722150523086.png)]

​ 表示成功创建了项目。

  • 启动项目

    npm run serve   // 注意这里是serve 不是server
    
    
  • 打包项目

    npm run build  // 用于生产环境的打包
    
  • 停止运行的测试服务器

    Ctrl+C
    

vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行vue inspect > output.js

output.js只能用于查看配置项,在output.js中修改配置并不能改变vue的配置项

3.2 脚手架项目文件结构

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

3.3 修改配置项

使用vue.config.js 可以对脚手架进行个性化定制

module.exports = {
  pages: {
    index: {
      entry: 'src/index/main.js' // 入口
    }
  },
  lintOnSave: false	// 关闭语法检查
}

配置参考链接:

https://cli.vuejs.org/zh/config/#devserver-proxy

3.4 main.js 文件解读

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

3.4.1 不同版本的vue

选择runtime版本的vue的目的是为了减少生产环境的空间,提高效率

默认情况下,vue脚手架使用的vue是runtime版本

  • vue.js与vue.runtime.xxx.js的区别

    vue.js是完整的Vue,包含核心功能+模板解析器

    vue.runtime.xxx.js,只包含核心功能,没有模板解析器

    vue.runtime.xxx.js中没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去执行具体内容

3.4.2 render函数

// render函数的完整形式: 
render(createElement){
    return createElement(App)
}

4 脚手架内组件通讯

4.1 ref属性

ref用来给元素或子组件注册引用信息,类似于id

将ref应用在html标签上获取的是真实的DOM元素;应用在组件标签上获取的是组件实例对象vc

  • 设置ref标识

    
    <h1 ref="h1"> 组件间通讯 </h1>
    
    <School ref="school"></School>
    
  • 获取ref

    this.$refs.xxx 获取ref
    
    this.$refs  返回一个对象
    
  • App.vue

    <template>
      <div>
        <div class="first-menu">
          <h1 ref="h1"> 组件间通讯 </h1>
          <h3>ref属性</h3>
          <button @click="testTag">测试标签</button>
          <button @click="testCpn">测试组件</button>
          <hr>
        </div>
        <div class="content">
          <School ref="school"></School>
        </div>
      </div>
    </template>
    
    <script>
    
    import School from "@/components/School";
    
    export default {
      name: 'App',
      components: {
        School
      },
      methods: {
        testTag() {
          console.log(this.$refs.h1) // 获取到的是真实的DOM,this.$refs.h1.innerHTML 获取到标签内的文本
        },
        testCpn() {
          console.log(this.$refs.school)  // 获取到的是组件实例对象,this.$refs.school.name  获取到的是组件实例对象的name
        }
      }
    }
    </script>
    
    <style>
    .active {
      background-color: cornflowerblue;
    }
    
    .content {
      position: absolute;
      left: 200px;
    }
    </style>
    
    
  • School.vue

    <template>
      <div>
        <h4 ref="info">学校信息</h4>
        <p>学校名称: {{ name }}</p>
        <p>学校地址:{{ addr }} </p>
      </div>
    </template>
    
    <script>
    export default {
      name: "School",
      data(){
        return {
          name: '清华大学',
          addr: '北京'
        }
      },
    }
    </script>
    
    <style scoped>
    
    </style>
    

4.2 props属性

props 让组件接收外部传来的数据

  • 传递数据

    <School name='清华大学' :age="122"></school> 
    
    

    注意:这里的:age 是 v-bind:age的缩写,通过v-bind,122是一个整数类型

  • 接收数据

    • 方式一:

      props: ['name', 'age']
      
    • 方式二(限制接收到变量的类型):

      props: {
      	name: String,
      	age: Number
      }
      
    • 方式三 (限制类型,限制必要性,设定默认值)

      props:{
      	name: {
      		type: String, // 限制类型
      		required: true, // 必须传递
      		default: 'kobe' // 默认值
      	}
      }
      

注意:

props是只读的,vue底层会监视props属性中的值,如果对其进行修改,不会报错,但是会提示,如果需要修改props中的值,请将props复制到data中进行修改。

  • demo

    • 普通接收

      • App.vue
      <template>
        <div>
          <div class="first-menu">
            <h1 ref="h1"> 组件间通讯 </h1>
            <h3>props属性</h3>
            <hr>
          </div>
          <div class="content">
            <School ref="school" name="清华大学" addr="北京"></School>
            <School ref="school" name="武汉大学" addr="武汉"></School>
            <School ref="school" name="厦门大学" addr="厦门"></School>
          </div>
        </div>
      </template>
      
      <script>
      
      import School from "@/components/School";
      
      export default {
        name: 'App',
        components: {
          School
        },
      }
      </script>
      
      <style>
      .active {
        background-color: cornflowerblue;
      }
      
      .content {
        position: absolute;
        left: 200px;
      }
      </style>
      
      
      • School.vue

        <template>
          <div>
            <h4 ref="info">学校信息</h4>
            <p>学校名称: {{ name }}</p>
            <p>学校地址:{{ addr }} </p>
            <hr>
          </div>
        </template>
        
        <script>
        export default {
          name: "School",
          data() {
            return {}
          },
          props: ['name', 'addr'] // 直接接收属性值
        }
        </script>
        
        <style scoped>
        
        </style>
        
    • 限制变量类型接收

      <template>
        <div>
          <h4 ref="info">学校信息</h4>
          <p>学校名称: {{ name }}</p>
          <p>学校地址:{{ addr }} </p>
          <hr>
        </div>
      </template>
      
      <script>
      export default {
        name: "School",
        data() {
          return {}
        },
        props: {
          name: String,
          addr: Number // 如果传递的不是数字型,报下方错误
        }
      }
      </script>
      
      <style scoped>
      
      </style>
      
      // Vue warn]: Invalid prop: type check failed for prop "addr". Expected Number with value NaN, got String with value "北京".
      
    • 限制类型、必要性、默认值

      <template>
        <div>
          <h4 ref="info">学校信息</h4>
          <p>学校名称: {{ name }}</p>
          <p>学校地址:{{ addr }} </p>
          <hr>
        </div>
      </template>
      
      <script>
      export default {
        name: "School",
        data() {
          return {}
        },
        props: {
          name: {
            type: String,
            required: true,
            default: '家里蹲大学'
          },
          addr: {
            type: String,
            required: true,
            default: 'home'
          }
        }
      }
      </script>
      
      <style scoped>
      
      </style>
      

4.3 mixin 属性

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

  • 定义mixin

    const xxx = {
    	// 公用数据
    	data(){....},
    	// 公用方法
    	methods: {},
    	// 钩子函数
    	created() {}
    }
    
  • 使用mixin

    • 全局使用混入

      Vue.mixin(xxx)
      
      
    • 局部使用混入

      mixins: ['xxx', ]
      
      
  • demo

    • src/mixin/index.js

      const mixin = {
          data() {
              return {
                  'login': 'xxxx'
              }
          },
          methods: {
              Login() {
                  alert('登录视图')
              },
              Logout() {
                  alert('登出视图')
              }
          },
          created() {
              alert('mixin 创建组件钩子函数')
          }
      }
      
      export default mixin
      
    • src/App.vue (局部导入)

      <template>
        <div>
          登录用户: {{ login }}
          <button @click="Login">Login</button>
          <button @click="Logout">Logout</button>
        </div>
      </template>
      
      <script>
      
      import mixin from '@/mixins'
      
      export default {
        name: 'App',
        mixins: [mixin, ]  // 局部mixin
      
      }
      </script>
      
      <style>
      .active {
        background-color: cornflowerblue;
      }
      
      .content {
        position: absolute;
        left: 200px;
      }
      </style>
      
      
    • src/main.js(全局导入)

      import Vue from 'vue'
      import App from './App.vue'
      
      // 引入ElementUI
      import ElementUI from 'element-ui'
      // 引入ElementUI 全部样式
      import 'element-ui/lib/theme-chalk/index.css'
      
      Vue.use(ElementUI) // 全局使用ElementUI
      
      Vue.config.productionTip = false
      
      import mixin from '@/mixins'
      
      Vue.mixin(mixin)  // 全局导入
      
      new Vue({
          render: h => h(App),
      
      }).$mount('#app')
      
      

4.4 plugin 属性

用于提升、扩展Vue的功能

  • 本质

    包含install方法的一个对象,install的第一个参数是Vue,第二个的参数是插件使用者传递的数据

  • 使用插件

    Vue.use()

  • demo

    • src/plugin/index.js

      export default {
       install(Vue, x,y,z){
       	 console.log(x,y,z)
           // 全局过滤器
           Vue.filter('myslice', function(value){return value.slice(0,4)})
           // 定义全局指令
           Vue.directive('fbind', {
               // 指令与元素成功绑定时
               bind(element, binding) {element.value = binding.value}
           },
               // 指令所在元素被插入页面时
               inserted(element, binding){element.focus()}
          	 // 指令所在的模板被重新解析时
          	 update(element, binding){element.value = binding.value}
                        )
       		}
           // 定义混入
           Vue.mixin({
               data(){
                   return {
                       x: 100, y:200
                   }
               }
           })
      	// 给Vue原型上添加一个方法(vm和vc可以用)
      	Vue.prototype.hello = ()=>{alert('hello plugins!')}
      }
      
    • src/main.js

      Vue.use(Vue, 1,2,3) // 应用插件
      

4.5 scoped 属性




## 4.6 自定义事件

父组件传递数据到子组件,使用props

==**自定义事件是从子组件传递数据到父组件**==

子组件想要给父组件传递数据,要在父组件中给子组件定义自定义事件,事件回调在父组件中

- 语法

  - 父组件

    ```vue
    <template>
    	<children @customEvent="callback"> </children> // 或者 v-on:customEvent="callback"
    </template>
    
    <script>
    	methods: {
            callback(...params){
                // params 接收来自子组件中的数据
            }
        }
    </script>
    
    ```

  - 子组件

    ```vue
    <script>
    	methods: {
            alertCustomEvent(){
                this.$emit('customEvent', param1, param2, ...)
            	            // 参数1 是父组件为子组件设定的自定义事件名
                            // 参数2~参数n  是子组件为父组件传递的数据,最终被父组件的回调函数接收并处理。 
                           }
        }
    </script>
    
    ```

    ```js
    this.$emit('customEvent') // 解绑一个自定义事件
    this.$emit(['event1', 'event2']) // 解绑多个自定义事件
    this.$destroy() // 销毁子组件,绑定其身上的自定义事件随着全部失效 
    ```

  - 父组件 方式2

    ```vue
    <template>
      <div>
        <div class="top">
          <bookHeader ref="bookHeader"></bookHeader>
        </div>
      </div>
    </template>
    
    <script>
      methods: {
        addBookCallback(obj){
          this.bookList.push(obj)
        }
      },
      mounted() {
        this.$refs.bookHeader.$on('addBook', this.addBookCallback)
        // 如果想让自定义事件只执行一次,将$on转换成$once即可
      }
    </script>
    ```

  - 父组件无论使用方式1还是方式2,子组件的使用方式均相同。

- 组件绑定原生事件

  在原生事件后面加上==**.native**==  否则原生事件也会被当做是自定义事件。

  ```js
  <template>
    <div>
      <div class="top">
        <bookHeader @click.native="sayHello"></bookHeader>  
      </div>
    </div>
  </template>

4.7 全局事件总线

4.7.1 定义

一种可以在任意组件之间通信的方式,不限于父子组件之间,本质上就是一个对象

  • 所有的组件都必须能够看到这个对象
  • 这个对象必须能够使用$on、$emit、$off、方法去绑定、触发和解绑事件

4.7.2 使用步骤

  • 定义全局事件总线

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

    • 接收数据

      接收数据的组件,使用$bus.$on定义自定义数据,回调函数留在接收数据的组件中.

    • 提供数据

      提供数据的组件使用$bus.$emit()触发自定义事件

  • 解绑当前组件中用到的所有事件,beforeDestroy()

  • Demo

    • 接收数据的组件

      <template>
        <div>
          <div class="top">
            <bookHeader></bookHeader>
          </div>
          <div class="middle">
            <bookList :books="bookList"></bookList>
          </div>
          <div class="bottom">
            <bookFooter></bookFooter>
          </div>
        </div>
      </template>
      
      <script>
      
      import bookHeader from "@/components/bookHeader";
      import bookList from "@/components/bookList";
      import bookFooter from "@/components/bookFooter";
      
      
      export default {
        name: 'App',
        data() {
          return {
            bookList: [
              {id: 1, name: '三国演艺'}
            ]
          }
        },
        components: {
          bookHeader,
          bookList,
          bookFooter
        },
        methods: {
          addBookCallback(obj) { // 哪个组件接收数据,回调函数就写在哪个组件内
            this.bookList.push(obj)
          },
          sayHello() {
            console.log('say hello')
          }
        },
        mounted() {
          this.$bus.$on('addBook', this.addBookCallback)  //绑定事件
        },
        beforeDestroy() {
          this.$bus.$off('addBook')  // 解绑事件
        }
      
      
      }
      </script>
      
      <style scoped>
      
      
      </style>
      

      发送数据的组件

      <template>
        <div>
          <el-input v-model="input" placeholder="请输入内容" @change="addBook"></el-input>
        </div>
      </template>
      
      <script>
      export default {
        name: "bookHeader",
        data() {
          return {
            input: ''
          }
        },
        methods: {
          addBook() {
            this.$bus.$emit('addBook', { // 与自定义组件不同的是必须使用this.$bus触发事件
              id: 2,
              name: this.input
            })
          }
        }
      }
      </script>
      
      <style scoped>
      
      </style>
      

4.7.3 小结

全局事件总线相比较自定义事件总线更加灵活。

4.8 发布与订阅

用的较少,先不展开,后面有时间在搞。

5 vuex

5.1 搭建vuex环境

  • 安装

    npm i vuex@3 vuex目前最新版本是4版本,4版本必须使用vue3,如果使用vue2版本,必须安装vuex3,否则安装会报错

  • 配置

    在src文件下创建store目录,在目录内创建index.js

    看图说话:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IludkSgi-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220802231636436.png)]

    // 导入Vue
    import Vue from 'vue'
    // 导入Vuex
    import Vuex from 'vuex'
    // 注册插件
    Vue.use(Vuex)
    
    const actions = { // 
        xxx(context, value) {
            context.commit('ADD', value)
        },
    }
    
    const mutations = {
        ADD(state, value) {
            state.sum += value
        },
    }
    
    const state = {
        sum: 10
    }
    
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
    

5.2 getter

5.3 模块化

6 vue-router

6.1 安装

当前最新的vue-router版本是V4.x,V4.x版本的vue-router适用于vue3,如果使用vue2版本,请使用vue-router3.x版本

npm i vue-router@3 
cnpm i vue-router@3 # 使用淘宝镜像安装,可以加速

6.2 配置

  • src/router/index.js

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import About from '../components/About'
    import Home from '../components/Home'
    
    Vue.use(VueRouter)
    
    export default new VueRouter({
        routes: [
            {
                path: '/about',
                component: About
            },
            {
                path: '/home',
                component: Home
            }
        ]
    })
    
  • src/main.js

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      router
    }).$mount('#app')
    
  • src/components/App.vue

    <template>
      <div>
        <div id="app">
          跳转标签
          <router-link active-class="active" to="/about">About</router-link>
          <router-link active-class="active" to="/home">Home</router-link>
        </div>
        <div>
          组件内容展示区
          <router-view></router-view>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
    }
    </script>
    
    <style>
    .active {
      background-color: cornflowerblue;
    }
    </style>
    
  • src/components/About.vue

  • src/components/Home.vue

6.3 路由传参query

假设有一个图书管理系统,首页显示所有图书的列表,当点击每本图书后,显示图书的详细视图,点击不同的图书显示不同的详细视图,类似这种需求就需要给路由传参。

vue-router中路由传参有两种写法,分别是字符串写法和对象写法。

src/router/index.js

import Vue from "vue"
import VueRouter from 'vue-router'

import Detail from "@/components/Detail";

Vue.use(VueRouter)

export default new VueRouter({
    routes: [
        {
            path: '/books',
            component: Detail
        }
    ]
})

src/App.vue

<template>
  <div>
    <div id="app">
      <h1>图书管理系统</h1>
      <table border="1px">
        <thead>
        <th>ID</th>
        <th>书名</th>
        <th>出版社</th>
        </thead>
        <tbody>
        <tr v-for="book in bookList" :key="book.id">
          <th>{{ book.id }}</th>
          <th>
            <router-link :to="`/books?id=${book.id}&name=${book.name}&publish=${book.publish}`">
              {{ book.name }}
            </router-link>
          </th>
          <th>{{ book.publish }}</th>
        </tr>
        </tbody>
      </table>
    </div>
    <hr>
    <div style="background-color: cornflowerblue">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>

export default {
  name: 'App',
  data() {
    return {
      bookList: [
        {id: '001', name: '天龙八部', publish: '中国出版社'},
        {id: '002', name: '西游记', publish: '中国出版社'},
        {id: '003', name: '水浒传', publish: '中国出版社'},
        {id: '004', name: '金瓶梅', publish: '中国出版社'},
        {id: '005', name: '神雕侠侣', publish: '中国出版社'},
        {id: '006', name: '天下第一', publish: '中国出版社'},
      ]
    }
  }

}
</script>

<style>

.active {
  background-color: cornflowerblue;
}

</style>

src/components/Detail.vue

<template>
<div>
  <h1>书籍详细信息</h1>
  <h2>书籍名称:{{ $route.query.name }}</h2>
  <h2>书籍出版社:{{ $route.query.publish }}</h2>
</div>
</template>

<script>
export default {
  name: "Detail"
}
</script>

<style scoped>

</style>
  • 路由传参方式1:

    <router-link :to="`/books?id=${id}&name=${name}&publish=${publish}`">跳转</router-link>
    

    在组件中使用$route.query.name 等获取路由中传递的参数

  • 路由传参方式2:

    			<router-link :to="{
                  path: '/books',
                  query: {
                    id: book.id,
                    name: book.name,
                    publish: book.publish
                  }
                }">
                  {{ book.name }}
                </router-link>
    

    在组件中使用$route.query.name 等获取路由中传递的参数

6.4 命令路由

为了简化多级路由的写法,可以在定义路由时添加一个name属性。

src/router/index.js

routes: [
        {
            name: 'detail',
            path: '/books',
            component: Detail
        }
    ]

src/App.vue

  • 不需要传参,to属性使用字符串拼接写法和对象写法都可以

    字符串写法

    <router-link :to="{name: 'detail'}">跳转</router-link>
    

    对象写法

    <router-link :to="{
    	name:'detail'}">
    <router-link>
    
  • 需要传参,to属性必须使用对象写法

    <router-link :to="{
    	name:'detail',
    	query: {
    		id: '01',
    		name: '水浒传',
    		publish: '中国出版社'
    	}
    
    }">
    <router-link>
    

6.5 路由传参params

query传参是在跳转时传递参数,类似get请求传参,将参数通过?链接到url后面,通过&符号链接多个key-value.

http://127.0.0.1:8000/books?id=1&name=水浒传

id和name就会传递给/books路由对应组件的$router.query中。

params在定义路由的时候就要确定要传递的参数,在path中使用占位符声明接收params参数,如果使用params传参,to的对象写法不能使用path属性,必须使用name属性。

src/router/index.js

import Vue from "vue"
import VueRouter from 'vue-router'

import Detail from "@/components/Detail";

Vue.use(VueRouter)

export default new VueRouter({
    routes: [
        {
            name: 'detail',
            path: '/books/:id/:name/:publish',  // 使用:占位符对需要传递的参数占位
            component: Detail
        }
    ]
})

src/App.vue

'''
<router-link :to="`/books/${book.id}/${book.name}/${book.publish}`">
                            {{ book.name }}
                          </router-link>
'''

再次重申,如果to使用对象写法,必须使用命名路由,不能使用path

'''
<router-link :to="{
              name: 'detail',
              params: {
                id: book.id,
                name: book.name,
                publish: book.publish
              }
              }">{{ book.name }}</router-link>
'''

src/component/Detail.vue

使用$route.params.xxx接收路由中的params参数

<template>
<div>
  <h1>书籍详细信息</h1>
  <h2>书籍ID:{{ $route.params.id }}</h2>
  <h2>书籍名称:{{ $route.params.name }}</h2>
  <h2>书籍出版社:{{ $route.params.publish }}</h2>
</div>
</template>

<script>
export default {
  name: "Detail"
}
</script>

<style scoped>

</style>

6.6 路由props属性

props可以让路由更方便接收到参数:

{
	name:'detail',
	path:'books/:id/:name/:publish',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props($route){
		return {
			id: $route.query.id,
			title: $route.query.title
		}
	}
}

src/route/index.js

import Vue from "vue"
import VueRouter from 'vue-router'

import Detail from "@/components/Detail";

Vue.use(VueRouter)

export default new VueRouter({
    routes: [
        {
            name: 'detail',
            path: '/books/:id/:name/:publish',
            component: Detail,
            // 写法1:对象写法
            // props: {    
            //     hello: 'world'
            // }
            // 写法2: 布尔值
            //props: true
            // 写法3: 函数写法
            props($route){
                return {
                    xixi:$route.params.id,
                    haha:$route.params.name,
                    xxoo: $route.params.publish
                }
            }
        }
    ]
})

/src/components/Detail.vue

<template>
<div>
  <h1>书籍详细信息</h1>
  <h2>书籍ID:{{ xixi }}</h2>
  <h2>书籍名称:{{ haha }}</h2>
  <h2>书籍出版社:{{ xxoo }}</h2>
<!--  <h2>我来自路由中的props:{{ hello }}</h2>-->
</div>
</template>

<script>
export default {
  name: "Detail",
  // 写法1:对应路由中props的对象写法
  // props: ['hello']
  // 写法2:对应路由中props的布尔值写法,如果为true,params中传递的参数都将到组件的props中
  // props: ['id','name','publish']
  // 写法3:函数写法
  props: ['xixi', 'haha', 'xxoo']
}
</script>

<style scoped>

</style>

6.7 编程式路由导航

6.7.1 浏览器缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXxXgPPA-1663494649207)(D:\markdown\Vue速成手册.assets\image-20220917172112078.png)]

在浏览器左上角一般都会有两个箭头,分别表示“向前"和"向后".

浏览器的路由缓存类似于一个栈,有两种存储地址的方式:

  • replace 方式:

    <router-link :replace=true>Name</router-link>
    // 简写方式
    <router-link replace>Name</router-link>
    
  • push方式(默认):

    
    

6.7.2 编程式路由组件(不使用router-link实现路由跳转)

不借助实现路由跳转,让路由跳转更加灵活

this.$router.push({})
this.$router.replace({})
this.$router.forward()  // 前进
this.$router.back()  // 后退
this.$router.go(n)   // 可前进也可后退,正数为前进,负数为后退

push和replace里面的参数和to相同:

this.$router.push({
	name: 'router name'
})
  • Test.vue
<template>
  <div>
    <h2>Test push&replace</h2>
    <div>

      <router-link v-for="book in bookInfo" :key="book.id" :to="{name:'xiangxi', params:{id:book.id, title:book.title}}">
        {{ book.title }}
      </router-link>

    </div>
  </div>
</template>

<script>
export default {
  name: "Test",
  data() {
    return {
      bookInfo: [
        {id: 1001, title: '三国演义'},
        {id: 1002, title: '水浒传'},
        {id: 1003, title: '西游记'},
        {id: 1004, title: '红楼梦'},
      ]
    }
  },
  methods: {
	showPush(book){
      this.$router.push({
        name: 'xiangxi',
        params: {
          id: book.id,
          title: book.title
        }
      })
    },
    showReplace(book){
      this.$router.replace({
        name: 'xiangxi',
        params: {
          id: book.id,
          title: book.title
        }
      })
    }
  }
}
</script>

<style scoped>

</style>
  • Xiangxi.vue
<template>
  <div>
    <h1>图书详情信息</h1>
    <p>图书编号: {{ id }}</p>
    <p>图书名称: {{ title }}</p>
  </div>
</template>

<script>
export default {
  name: "Xiangxi",
  data() {
    return {
      'id': this.$route.params.id,
      'title': this.$route.params.title
    }
  }


}
</script>

<style scoped>

</style>

6.8 缓存路由组件

默认情况下:路由组件被切换后,会被vue销毁,所以在该组件中的数据都将被清除。

keep-alive 标签可以将组件缓存,不被销毁,所以在路由切换后能够保存组件中的数据。

  • 缓存一个组件

    include 属性中填写组件名

    <keep-alive include="Python">
            <router-view></router-view>
          </keep-alive>
    
  • 缓存多个组件

    include属性中填写组件列表

    注意:缓存一个组件的时候,include前面没有冒号:,缓存多个组件的时候,include前面有冒号:

    <keep-alive :include="['Python', 'Java']">
            <router-view></router-view>
          </keep-alive>
    
  • 缓存所有组件 不写include属性就代表缓存所有

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

6.9 路由相关的钩子函数

  • activated

    路由组件被激活时触发

  • deactivated

    路由组件失活时触发

<template>
  <div>
    <h1>Python学科</h1>
    <p><span :style="{opacity}">人生苦短,我用Python</span>
      <input type="text" v-model="msg">
    </p>
  </div>
</template>

<script>
export default {
  name: "Python",
  data() {
    return {
      msg: 'python:',
      opacity: 0,
    }
  },
  activated() {
    this.timer = setInterval(() => {
      if (this.opacity <= 1) {
        this.opacity += 0.01
      } else {
        this.opacity = 0
      }
    }, 16)
  },
  deactivated() {
    clearInterval(this.timer)
  }
}
</script>

<style scoped>

</style>

6.10 路由守卫

对路由权限进行控制

路由配置中通过:meta配置源信息

6.10.1 全局守卫

对所有组件都起作用

  • 全局前置守卫:初始化时,每次路由切换前执行

    router.beforeEach((to, from, next) => {
        if (to.meta.requireAuth) {
            let c = localStorage.getItem('class')
            if (c === 'v') {
                next()
            } else {
                alert('无权访问')
                next(false)
            }
        } else {
            next()
        }
    })
    
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYvPKB9A-1663494649208)(D:\markdown\Vue速成手册.assets\image-20220918093757914.png)]

  • 全局后置守卫:初始化时,每次路由切换后执行

    router.afterEach((to, from)=>{
        document.title = to.meta.title
    })
    
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cyq5sdyx-1663494649208)(D:\markdown\Vue速成手册.assets\image-20220918093838110.png)]

  • 小结

    to,from,next三个参数都是必要的
    to:即将要进入的目标 路由对象
    from:当前导航正要离开的路由
    next:一定要调用该方法来处理这个钩子,如果不写next()或者next(false),页面路由不会跳转,也就是页面被阻止在当前页面了
    to,from是一个对象,就是 routes[] 数组里面配置的某个具体的路由对象,
    比如:to.path, to,name, to.meta 或 from.path, from.name, from.meta 【path,name,meta】这些字段都是自己在路由里面定义的字段,这样就可以开始写逻辑了。
    next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    
    next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    
    next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
    
    next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
    

6.10.2 独享守卫

对某个组件起作用,你可以在路由配置上直接定义 beforeEnter 守卫:

参数与全局前置守卫相同:

{
        path: '/class',
        name: 'class',
        meta: {
            requireAuth: true,
            title: 'Class'

        },
        component: Class,
        children: [
            {
                path: 'python',
                name: 'python',
                component: Python,
                beforeEnter: (to, from, next)=>{
                    if (localStorage.getItem('class') === 'v1'){
                        next()
                    }else{
                        alert('无权访问')
                        next(false)
                    }
                }
            },
        ]
}

6.10.3 组件内守卫

  • 通过路由匹配后,进入路由组件时守卫

    beforeRouteEnter((to, from, next)=>{})
    
  • 离开路由组件时守卫

    beforeRouteLeave((to, from, next)=>{})
    

6.10.4 路由的两种模式

  • hash

    地址中有#号
    地址中#号后面的值不会出现在http请求中
    兼容性好
    不美观
    app检验严格的情况会被标记为不合法
    
  • history

    地址干净、美观
    兼容性比hash差
    可能会出现404报错,因为路径中的数据会被发送到后端
    
  • 设置模式

    const router = new VueRouter({
        mode: 'history' or 'hash'
    })
    

7 vue ui组件库

1 安装element-ui

cnpm i element-ui -S

2 全局配置

src/main.js

import Vue from 'vue'
import App from './App.vue'
// 引入ElementUI  
import ElementUI from 'element-ui'
// 引入ElementUI 全部样式
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI) // 全局使用ElementUI

new Vue({
  render: h => h(App),
}).$mount('#app')

src/App.vue

<template>
  <div>
    <div id="app">
      <el-row>
        <el-button type="primary" icon="el-icon-edit circle" @click="sayHello"></el-button>
      </el-row>
    </div>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {},
  methods: {
    sayHello(){alert('say hello!')}
  }
}
</script>

<style>
</style>

3 部分配置

1 安装babel-plugin-component

npm i babel-plugin-component -D

2 修改babel-config-js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["@babel/preset-env", { "modules": false }]
  ],
  plugins: [
    [
      "component",
      {        
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

src/main.js

import Vue from 'vue'
import App from './App.vue'
import { Button,Row } from 'element-ui'	// 按需引入

Vue.config.productionTip = false

Vue.component(Button.name, Button);
Vue.component(Row.name, Row);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Row)
 */

new Vue({
    el:"#app",
    render: h => h(App),
})

一般如果对性能要求不是太苛刻的话都会选择全局配置

8 LocalStorage 和 SessionStorage

8.1 LocalStorage(本地存储)

  • setItem

    localStorage.setItem('k1', 'v1')
    
  • getItem

    localStorage.getItem('k1')
    'v1'
    
  • length 属性

    localStorage.length
    1
    
  • getItem

    localStorage.getItem('k2')
    'v2'
    
  • clear 清楚所有存储

    localStorage.clear()
    
  • localStorage.removeItem

    localStorage.removeItem('k1')
    

8.2 SessionStorage(会话存储)

  • API与localStorage相同

    - setItem(key, value)
    - getItem(key)
    - removeItem(key)
    - length 
    - clear() 清空所有的会话存储
    

常见问题总结:

1 Component name “Class” should always be multi-word vue/multi-word-component-names

  • 解决方式1:

    在vue.config.js中添加关闭语言检查功能

    const {defineConfig} = require('@vue/cli-service')
    module.exports = defineConfig({
        transpileDependencies: true,
        lintOnSave: false /*关闭语法检查*/
    })
    
  • 解决方式2:

    按照要求修改组件名

2 路由文件与main文件分模块时报如下错误:Error in beforeCreate hook: “TypeError: this._router.init is not a function”

问题原因: 导出和导入模块的方式不对

正确的处理方式:


格式一 变量形式 --暴露

export default {

router

}

--接收

import {router} from './router'



格式二 直接暴露


 export default router

接收

import router from './router'


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kobe_OKOK_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值