VUE-第二季-02

3.Vue组件化

3.1 什么是组件

(1) 传统方式开发的应用

复制代码
一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)

传统应用存在的问题:

① 关系纵横交织,复杂,牵一发动全身,不利于维护。

② 代码虽然复用,但复用率不高。

(2) 组件化方式开发的应用

使用组件化方式开发解决了以上的两个问题:

① 每一个组件都有独立的js,独立的css,这些独立的js和css只供当前组件使用,不存在纵横交错。更加便于维护。

② 代码复用性增强。组件不仅让js css复用了,HTML代码片段也复用了(因为要使用组件直接引入组件即可)。

(3) 什么是组件?

① 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用。

② 模块:一个大的js文件按照模块化拆分规则进行拆分,生成多个js文件,每一个js文件叫做模块。凡是采用模块方式开发的应用都可以称为模块化应用。

③ 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件是可以包括模块的。

(4) 组件的划分粒度很重要,粒度太粗会影响复用性。为了让复用性更强,Vue的组件也支持父子组件嵌套使用。

子组件由父组件来管理,父组件由父组件的父组件管理。在Vue中根组件就是vm。因此每一个组件也是一个Vue实例。

3.2 组件的创建、注册和使用

(1) 创建组件

复制代码
①	const userComponent = Vue.extend({这个配置项和创建Vue实例的配置项几乎是一样的,只是略有差异})
②	需要注意的是:
1)	el不能用。组件具有通用性,不特定为某个容器服务,它为所有容器服务。
2)	data必须使用函数形式:return {}
3)	使用template配置项配置页面结构:HTML。

(2) 注册组件

复制代码
①	局部注册
  1. 使用components配置项:components : {user : userComponent},user就是组件名。
    ② 全局注册
  1. Vue.component('user', userComponent)

(3) 使用组件

复制代码
①	直接在页面需要使用组件的位置:<user></user>

②	也可以这样使用:<user/> (不在脚手架环境中使用这种方式会出现后续元素不渲染的问题。)

(4) 创建组件对象也有简写形式:Vue.extend() 可以省略。直接写:{}

(5) 组件的命名细节:

① 全部小写

② 首字母大写,后面全部小写

③ kebab-case串式命名法

④ CamelCase驼峰式命名法(这种方式需要在脚手架环境中使用)

⑤ 不要使用HTML内置的标签作为组件名称。

⑥ 可以使用name配置项来指定Vue开发者工具中显示的组件名。

编写的第一个组件代码

复制代码
<!DOCTYPE html>

<html lang="en">

  

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Document</title>

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

</head>

  

<body>

  

    <div id="app">

        <h1>{{msg}}</h1>

        <!--3.使用组件 -->
        <userlist></userlist>

        <userlist></userlist>

    </div>

  

    <script>
        //1.创建组件
        const userCompont = Vue.extend({

  

            template : `

            <ul>

                <li v-for="(user,index) in users" :key='user.id'>

                    {{user.id}} : {{user.username}}

                </li>

            </ul>

            `

            ,
            //data必须是函数的形式
            data(){

  

               return {

                    users : [

                        { id: '001', username: 'hax1' },

                        { id: '002', username: 'hax2' },

                        { id: '003', username: 'hax3' },

                    ]

               }

            }

        })

  
  

        const vm = new Vue({

            el: '#app',

            data: {

  

                msg: '第一个组件练习',

                // users: [

                //     { id: '001', username: 'hax1' },

                //     { id: '002', username: 'hax2' },

                //     { id: '003', username: 'hax3' },

                // ]

  
  

            },
            //2.注册组件
            components : {

                userlist : userCompont

            }

        })

    </script>

  

</body>

  

</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>

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

</head>

<body>

    <!--

        组件的使用分为三步:

            第一步:创建组件

                Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})

                区别有哪些?

                    1. 创建Vue组件的时候,配置项中不能使用el配置项。(但是需要使用template配置项来配置模板语句。)

                    2. 配置项中的data不能使用直接对象的形式,必须使用function。

            第二步:注册组件

                局部注册:

                    在配置项当中使用components,语法格式:

                        components : {

                            组件的名字 : 组件对象

                        }

                全局注册:

                    Vue.component('组件的名字', 组件对象)

            第三步:使用组件

        小细节:

            1. 在Vue当中是可以使用自闭合标签的,但是前提必须在脚手架环境中使用。

            2. 在创建组件的时候Vue.extend()可以省略,但是底层实际上还是会调用的,在注册组件的时候会调用。

            3. 组件的名字

                第一种:全部小写

                第二种:首字母大写,后面都是小写

                第三种:kebab-case命名法(串式命名法。例如:user-login)

                第四种:CamelCase命名法(驼峰式命名法。例如:UserLogin),但是这种方式只允许在脚手架环境中使用。

                不要使用HTML内置的标签名作为组件的名字。

                在创建组件的时候,通过配置项配置一个name,这个name不是组件的名字,是设置Vue开发者工具中显示的组件的名字。

     -->

    <div id="app">

        <h1>{{msg}}</h1>

        <!-- 3. 使用组件 -->

        <userlogin></userlogin>

        <userlist></userlist>

        <userlist></userlist>

        <userlist></userlist>

        <userlogin></userlogin>

  

        <!-- <userlogin/> -->

    </div>

  

    <div id="app2">

        <userlogin></userlogin>

        <hello-world></hello-world>

        <!-- <form></form> -->

    </div>

  

    <script>

  

        /* // 创建组件

        const abc = {

            template : `<h1>测试组件的名字????</h1>`

        }

  

        // 全局注册组件

        Vue.component('HelloWorld', abc) */

  
  

        Vue.component('hello-world', {

            name : 'Xxxxx',

            template : `<h1>测试组件的名字%%%%%</h1>`

        })

        /* Vue.component('form', {

            template : `<h1>测试组件的名字%%%%%</h1>`

        }) */

  

        // 1.创建组件(结构HTML 交互JS 样式CSS)

        /* const myComponent = Vue.extend({

            template : `

            <ul>

                <li v-for="(user,index) of users" :key="user.id">

                    {{index}},{{user.name}}

                </li>

            </ul>

            `,

            data(){

                return {

                    users : [

                        {id:'001',name:'jack'},

                        {id:'002',name:'lucy'},

                        {id:'003',name:'james'}

                    ]

                }

            }

        }) */

  

        const myComponent = {

            template : `

            <ul>

                <li v-for="(user,index) of users" :key="user.id">

                    {{index}},{{user.name}}

                </li>

            </ul>

            `,

            data(){

                return {

                    users : [

                        {id:'001',name:'jack'},

                        {id:'002',name:'lucy'},

                        {id:'003',name:'james'}

                    ]

                }

            }

        }

  

        // 1. 创建组件

        /* const userLoginComponent = Vue.extend({

            template : `

            <div>

                <h3>用户登录</h3>

                <form @submit.prevent="login">

                    账号:<input type="text" v-model="username"> <br><br>

                    密码:<input type="password" v-model="password"> <br><br>

                    <button>登录</button>

                </form>

            </div>

            `,

            data(){

                return {

                    username : '',

                    password : ''

                }

            },

            methods: {

                login(){

                    alert(this.username + "," + this.password)

                }

            },

        }) */

  

        const userLoginComponent = {

            template : `

            <div>

                <h3>用户登录</h3>

                <form @submit.prevent="login">

                    账号:<input type="text" v-model="username"> <br><br>

                    密码:<input type="password" v-model="password"> <br><br>

                    <button>登录</button>

                </form>

            </div>

            `,

            data(){

                return {

                    username : '',

                    password : ''

                }

            },

            methods: {

                login(){

                    alert(this.username + "," + this.password)

                }

            },

        }

  

        // 全局注册

        Vue.component('userlogin', userLoginComponent)

  

        const vm2 = new Vue({

            el : '#app2'

        })

  

        // Vue实例

        const vm = new Vue({

            el : '#app',

            data : {

                msg : '第一个组件'

            },

            // 2. 注册组件(局部注册)

            components : {

                // userlist是组件的名字。myComponent只是一个变量名。

                userlist : myComponent,

                //userlogin : userLoginComponent

            }

        })

  

        /* let data = {

            counter : 1

        } */

  

        function data(){

            return {

                counter : 1

            }

        }

  

        let x = data();

        let y = data();

  

    </script>

</body>

</html>

3.3 组件嵌套

代码学习:

复制代码
<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>组件嵌套</title>

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

</head>

<body>

    <div id="root"></div>

    <script>

        // 创建Y1组件

        const y1 = {

            template : `

                <div>

                    <h3>Y1组件</h3>

                </div>

            `

        }

        // 创建X1组件

        const x1 = {

            template : `

                <div>

                    <h3>X1组件</h3>

                </div>

            `

        }

        // 创建Y组件

        const y = {

            template : `

                <div>

                    <h2>Y组件</h2>

                    <y1></y1>

                </div>

            `,

            components : {y1}

        }

        // 创建X组件

        const x = {

            template : `

                <div>

                    <h2>X组件</h2>

                    <x1></x1>

                </div>

            `,

            components : {x1}

        }

        // 创建app组件

        const app = {

            template : `

                <div>

                    <h1>App组件</h1>

                    <x></x>

                    <y></y>

                </div>

            `,

            // 注册X组件

            components : {x,y}

        }

        // vm

        const vm = new Vue({

            el : '#root',

            template : `

                <app></app>

            `,

            // 注册app组件

            components : {app}

        })

    </script>

</body>

</html>

嵌套结构:这种结构更加贴切实际项目的开发

3.4 VueComponent & Vue

3.4.1 this

new Vue({})配置项中的this和Vue.extend({})配置项中的this他们分别是谁?

复制代码
1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>vm与vc</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app">  
10.	        <mc></mc>  
11.	    </div>  
12.	    <script>  
13.	        const myComponent = Vue.extend({  
14.	            template : `<h1></h1>`,  
15.	            mounted(){  
16.	                console.log('vc', this)  
17.	            }  
18.	        })  
19.	  
20.	        const vm = new Vue({  
21.	            el : '#app',  
22.	            components : {  
23.	                mc : myComponent  
24.	            },  
25.	            mounted() {  
26.	                console.log('vm', this)  
27.	            },  
28.	        })  
29.	    </script>  
30.	</body>  
31.	</html> 

测试结果:

new Vue({})配置项中的this就是:Vue实例(vm)。

Vue.extend({})配置项中的this就是:VueComponent实例(vc)。

打开vm和vc你会发现,它们拥有大量相同的属性。例如:生命周期钩子、methods、watch等。

3.4.2 vm === vc ???

只能说差不多一样,不是完全相等。

例如:

vm上有el,vc上没有。

另外data也是不一样的。vc的data必须是一个函数。

只能这么说:vm上有的vc上不一定有,vc上有的vm上一定有。

3.4.3 Vue.extend()方法做了什么?

每一次的extend调用返回的都是一个全新的VueComponent函数。

以下是Vue.extend()的源码:

注意:是每一次都会返回一个全新的VueComponent构造函数。是全新的!!!

构造函数有了,什么时候会调用构造函数来实例化VueComponent对象呢?

Vue在解析<mc></mc>时会创建一个VueComponent实例,也就是:new VueComponent()

3.4.4 通过vc可以访问Vue原型对象上的属性

通过vc可以访问Vue原型对象上的属性:

  1. Vue.prototype.counter = 100

  2. console.log(vc.counter) // 100

为什么要这么设计?代码复用。Vue原型对象上有很多方法,例如:mount(),对于组件VueComponent来说就不需要再额外提供了,直接使用vc调用mount(),代码得到了复用。

Vue框架是如何实现以上机制的呢?

  1. VueComponent.prototype.proto = Vue.prototype

测试:

复制代码
1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>测试</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app"></div>  
10.	    <script>  
11.	        const userlist = Vue.extend({  
12.	            template : `<div><h1>用户列表</h1></div>`,  
13.	        })  
14.	        const vm = new Vue({  
15.	            el : '#app',  
16.	            template : `<userlist></userlist>`,  
17.	            components : {userlist}  
18.	        })  
19.	        console.log(userlist.prototype.__proto__ === Vue.prototype) // true  
20.	    </script>  
21.	</body>  
22.	</html>  
3.4.4.1 回顾原型对象

prototype称为:显示的原型属性,用法:函数.prototype,例如:Vue.prototype
proto__称为:隐式的原型属性,用户:实例. proto ,例如:vm. proto
无论是通过prototype还是__proto ,获取的对象都是同一个,它是一个共享的对象,称为:XX的原型对象。

如果通过Vue.prototype获取的对象就叫做:Vue的原型对象。

如果通过User.prototype获取的对象就叫做:User的原型对象。

请看下图:

3.4.4.2 原理剖析

VueComponent.prototype.proto = Vue.prototype

这样做的话,最终的结果就是:Vue、vm、VueComponent、vc都共享了Vue的原型对象(并且这个Vue的原型对象只有一个)。

代码学习:

复制代码
<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>vm与vc</title>

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

</head>

<body>

    <div id="app">

        <h1>{{msg}}</h1>

        <user></user>

        <user></user>

        <user></user>

    </div>

    <script>

  

        // 这个不是给Vue扩展counter属性。

        // 这个是给"Vue的原型对象"扩展一个counter属性。

        Vue.prototype.counter = 1000

  

        // 创建组件

        const user = Vue.extend({

            template : `

            <div>

                <h1>user组件</h1>

            </div>

            `,

            mounted(){

                // this是VueComponent实例

                // user是什么呢????是一个全新的构造函数 VueComponent构造函数。

                //console.log('vc', this === user)

                // 为什么要这样设计?为了代码复用。

                // 底层实现原理:

                // VueComponent.prototype.__proto__ = Vue.prototype

                console.log('vc.counter', this.counter)

                // 这个访问不了,因为msg是vm实例上的属性。

                //console.log('vc.msg', this.msg)

            }

        })

  

        console.log('user.prototype.__proto__ === Vue.prototype' , user.prototype.__proto__ === Vue.prototype)

  

        console.log(user)

  

        // vm

        const vm = new Vue({

            el : '#app',

            data : {

                msg : 'vm与vc'

            },

            components : {

                user

            },

            mounted() {

                // this是Vue实例

                console.log('vm', this)

            },

        })

  

        console.log('vm.counter', vm.counter)

        // 本质上是这样的:

        console.log('vm.counter', vm.__proto__.counter)

  

        /* function test(){

            var Sub = function User(){

                this.name = 'admin'

            }

            return Sub

        }

  

        let a = test()

        // 通过构造函数创建对象

        console.log(new a()) */

  

        /* console.log(a)

        let b = test()

        console.log(b)

        console.log(a === b) */

  

        // prototype __proto__

        // 构造函数(函数本身又是一种类型,代表Vip类型)

        function Vip(){}

  

        // Vip类型/Vip构造函数,有一个 prototype 属性。

        // 这个prototype属性可以称为:显式的原型属性。

        // 通过这个显式的原型属性可以获取:原型对象

        // 获取Vip的原型对象

        let x = Vip.prototype

  

        // 通过Vip可以创建实例

        let a = new Vip()

        /* let b = new Vip()

        let c = new Vip() */

        // 对于实例来说,都有一个隐式的原型属性: __proto__

        // 注意:显式的(建议程序员使用的)。隐式的(不建议程序员使用的。)

        // 这种方式也可以获取到Vip的原型对象

        let y = a.__proto__

        /* b.__proto__

        c.__proto__ */

  

        // 原型对象只有一个,其实原型对象都是共享的。

        console.log(x === y) // true

  

        // 这个不是给Vip扩展属性

        // 是在给"Vip的原型对象"扩展属性

        Vip.prototype.counter = 1000

  

        // 通过a实例可以访问这个扩展的counter属性吗?可以访问。为什么?原理是啥?

        // 访问原理:首先去a实例上找counter属性,如果a实例上没有counter属性的话,会沿着__proto__这个原型对象去找。

        // 下面代码看起来表面上是a上有一个counter属性,实际上不是a实例上的属性,是a实例对应的原型对象上的属性counter。

        console.log(a.counter)

        //console.log(a.__proto__.counter)

  

    </script>

</body>

</html>

3.5 单文件组件

  1. 什么是单文件组件?
    (1) 一个文件对应一个组件(之前我们所学的是非单文件组件,一个html文件中定义了多个组件)
    (2) 单文件组件的名字通常是:x.vue,这是Vue框架规定的,只有Vue框架能够认识,浏览器无法直接打开运行。需要Vue框架进行编译,将x.vue最终编译为浏览器能识别的html js css。
    (3) 单文件组件的文件名命名规范和组件名的命名规范相同:
    ① 全部小写:userlist
    ② 首字母大写,后面全部小写:Userlist
    ③ kebab-case命名法:user-list
    ④ CamelCase命名法:UserList(我们使用这种方式,和Vue开发者工具呼应。)
  1. x.vue文件的内容包括三块:
    (1) 结构:<template>HTML代码</template>
    (2) 交互:<script>JS代码</script>
    (3) 样式:<style>CSS代码</style>
  1. export和import,ES6的模块化语法。
    (1) 使用export导出(暴露)组件,在需要使用组件的x.vue文件中使用import导入组件
    ① 默认导入和导出
    1. export default {}
  1. import 任意名称 from '模块标识符'

② 按需导入和导出

  1. export {a, b}

  2. import {a, b} from '模块标识符'

③ 分别导出

export var name = 'zhangsan'

export function sayHi(){}

  1. VSCode工具可以安装一些插件,这样在编写x.vue的时候有提示。例如:vetur插件。

(1) 使用该插件之后,有高亮显示,并且也可以通过输入 <v 生成代码。

  1. 把之前"组件嵌套"的例子修改为单文件组件

!file-20250803120222466.png(https://gitee.com/javaxubo/work_note-images/raw/master//image/202508031202121.png

auto rename tag插件

这个 name: 'App' 表示该组件的名称是 "App",在其他地方可以通过这个名称来引用或识别该组件。如果没有显式设置 name,Vue 会使用文件名作为默认名称(在单文件组件中)。

记住一个要领:不管是单文件组件还是非单文件组件,永远都包括三步:创建组件、注册组件、使用组件。

创建vm的代码就不是一个组件了,这个js代码写到一个js文件中即可,一般这个起名:main.js。寓意:入口

还剩最后的一点HTML代码,一般这个文件叫做index.html,代码如下:

如上图,注意引入顺序。

代码执行原理:

复制代码
① 第一步:浏览器打开index.html页面,加载容器
② 第二步:加载vue.js文件,有了Vue
③ 第三步:加载main.js
    1)	import App from './App.vue'
    2)	import X from './X.vue'
    3)	import X1 from './X1.vue'
    4)	import Y from './Y.vue'
    5)	import Y1 from './Y1.vue'
这样就完成了所有组件以及子组件的创建和注册。
④ 第四步:创建Vue实例vm,编译模板语句,渲染。
写完之后不能直接运行,浏览器不认识.vue文件,不认识ES6的模块化语法。需要安装Vue脚手架。

3.6 Vue脚手架

3.6.1 确保npm能用(安装Node.js)

Node.js的下载地址:
Node.js --- Download Node.js®

安装步骤参考地址:Node安装 - 哩个啷个波 - 博客园

打开dos命令窗口,输入npm命令。

3.6.2 Vue CLI(脚手架安装)

  1. Vue的脚手架(Vue CLI: Command Line Interface)是Vue官方提供的标准化开发平台。它可以将我们.vue的代码进行编译生成html css js代码,并且可以将这些代码自动发布到它自带的服务器上,为我们Vue的开发提供了一条龙服务。脚手架官网地址:Vue CLI

注意:Vue CLI 4.x需要Node.js v8.9及以上版本,推荐v10以上。

  1. 脚手架安装步骤:

① 建议先配置一下npm镜像:

  1. npm config set registry https://registry.npm.taobao.org

  2. npm config get registry 返回成功,表示设置成功

② 第一步:安装脚手架(全局方式:表示只需要做一次即可)

  1. npm install -g @vue/cli

如果这一步安装出现问题 参考这里的文章解决 npm ERR! code CERT_HAS_EXPIRED:解决证书过期问题 - 哩个啷个波 - 博客园

  1. 安装完成后,重新打开DOS命令窗口,输入vue命令可用表示成功了

③ 第二步:创建项目(项目中自带脚手架环境,自带一个HelloWorld案例)

  1. 切换到要创建项目的目录,然后使用 vue create vue_pro

这里选择Vue2,

babel:负责ES6语法转换成ES5。

eslint:负责语法检查的。

回车之后,就开始创建项目,创建脚手架环境(内置了webpack loader),自动生成HelloWorld案例。

④ 第三步:编译Vue程序,自动将生成html css js放入内置服务器,自动启动服务。

  1. dos命令窗口中切换到项目根:cd vue_pro

  2. 执行:npm run serve,这一步会编译HelloWorld案例

ctrl + c停止服务。

  1. 打开浏览器,访问:http://localhost:8080

3.6.3 认识脚手架结构

使用VSCode将vue_pro项目打开:

package.json:包的说明书(包的名字,包的版本,依赖哪些库)。该文件里有webpack的短命令:

serve(启动内置服务器)

build命令是最后一次的编译,生成html css js,给后端人员

lint做语法检查的。

3.6.4 分析HelloWorld程序

可以看到在index.html中只有一个容器。没有引入vue.js,也没有引入main.js

Vue脚手架可以自动找到main.js文件。(所以main.js文件名不要修改,位置也不要随便移动)

接下来就是将之前写的程序拷贝到脚手架中,进行测试。

需要拷贝过来的是:App.vue、X.vue、Y.vue、X1.vue、Y1.vue。

main.js和index.html都不需要了,因为脚手架中有。

只需要将App.vue中的路径修改一下即可:

打开VSCode终端:ctrl + `, 注意这里命令没有,只是因为md文件中才特意转的

在终端中执行:npm run serve

报错了:

导致这个错误的原因是:组件的名字应该由多单词组成。这是eslint进行的es语法检测。

解决这个问题有两种方案:

第一种:把所有组件的名字修改一下。

第二种:在vue.config.js文件中进行脚手架的默认配置。配置如下:

在终端中ctrl + c 两次,终止之前的服务,再次运行命令:npm run serve

3.6.5 脚手架默认配置

脚手架默认配置在vue.config.js文件中进行。

main.js、index.html等都是可以配置的。

配置项可以参考Vue CLI官网手册,如下:

例如配置这两项:

第一个:保存时不检查语法 lintOnSave : false

第二个:配置入口

3.6.6 解释main.js中的render函数

将render函数更换为:template配置项,你会发现它是报错的。说明引入的Vue无法进行模板编译。

原因:Vue脚手架默认引入的是精简版的Vue,这个精简版的Vue缺失模板编译器。

实际引入的vue.js文件是:dist/vue.runtime.esm.js(esm版本是ES6模块化版本)

为什么缺失模板编译器?

Vue包含两部分:一部分是Vue的核心,一部分是模板编译器(模板编译器可能占整个vue.js文件的一大部分体积)。程序员最终使用webpack进行打包的时候,显然Vue中的模板编译器就没有存在的必要了。为了缩小体积,所以在Vue脚手架中直接引入的就是一个缺失模板编译器的vue.js。

这样就会导致template无法编译(注意:<template>标签可以正常编译package.json文件中进行了配置,说的是template配置项无法编译),解决这个问题包括两种方式:

第一种方式:引入一个完整的vue.js

第二种方式:使用render函数

关于render函数,完整写法:

这个函数被vue自动调用,并且传递过来一个参数createElement。

简写形式可以使用箭头函数:

3.7 props配置

使用props配置可以接收其他组件传过来的数据,让组件的数据变为动态数据,三种接收方式:

(1) 简单接收

props : 'name','age','sex'

(2) 接收时添加类型限制

props : {

name : String

age : Number

sex : String

}

(3) 接收时添加类型限制,必要性限制,默认值

props : {

name : {

type : Number,

required : true

},

age : {

type : Number,

default : 10

},

sex : {

type : String,

default : '男'

}

}

其他组件怎么把数据传过来?
<User name="jack" age="20" sex="男"></User>

注意事项:

① 不要乱接收,接收的一定是其它组件提供的。

② props接收到的数据不能修改。(修改之后会报错,但页面会刷新。)可以找个中间变量来解决。

代码学习:

Car.vue

复制代码
<template>

    <div>

        <h3>品牌:{{brand}}</h3>

        <h3>价格:{{cprice}}</h3>

        <h3>颜色:{{color}}</h3>

        <button @click="add">价格加1</button>

    </div>

</template>

  

<script>

export default {

    name : 'Car',

    data() {

        return {

            cprice : this.price

        }

    },

    methods : {

        add(){

            this.cprice++

        }

    },

    /* data() {

        return {

            brand : '宝马520',

            price : 10,

            color : '黑色'

        }

    }, */

  

    // 在Car这个子组件当中使用props配置项进行数据的接收。

    // 第一种:简单的接收方式,直接采用数组接收。

    //props : ['brand','color','price']

    // 第二种:添加类型限制

    /* props : {

        brand : String,

        color : String,

        price : Number

    } */

    // 第三种:添加类型限制,并且还可以添加默认值,还可以添加必要性

    // 避免直接更改prop,因为每当父组件重新渲染时,该值都会被覆盖

    // 注意:不要修改prop中的数据。

    props : {

        brand : {

            type : String,

            required : true

        },

        color : {

            type : String,

            default : '红色'

        },

        price : {

            type : Number,

            required : true

        }

    }

  

}

</script>

App.vue

复制代码
<template>

    <div>

        <h1>{{msg}}</h1>

        <!-- 在App这个父组件当中,找到子组件Car,然后给Car这个子组件传数据:通过属性的形式传数据 -->

        <Car brand="宝马520" color="黑色" :price="10"></Car>

        <hr>

        <Car brand="比亚迪汉" color="红色" :price="20"></Car>

    </div>

</template>

  

<script>

    import Car from './components/Car.vue'

    export default {

        name : 'App',

        data() {

            return {

                msg : '汽车信息'

            }

        },

        components : {Car}

    }

</script>

浏览器效果:

3.8 从父组件中获取子组件

在组件上使用ref属性进行标识:

复制代码
`<User ref="userJack"></User>`

在程序中使用$refs来获取子组件:

复制代码
this.$refs.userJack

访问子组件的属性:

复制代码
this.$refs.userJack.name

访问子组件的子组件属性:

复制代码
this.$refs.userJack.$refs.name

ref也可以使用在普通的HTML标签上,这样获取的就是这个DOM元素:

复制代码
`<input type="text" ref="username">`
this.$refs.username

代码演示

复制代码
<template>

    <div>

        <h1 ref="hh">{{msg}}</h1>

        <!-- 在App这个父组件当中,找到子组件Car,然后给Car这个子组件传数据:通过属性的形式传数据 -->

        <Car brand="宝马520" color="黑色" :price="10" ref="car1"></Car>

        <hr>

        <Car brand="比亚迪汉" color="红色" :price="20" ref="car2"></Car>

        <hr>

        <button @click="printCarInfo">打印汽车信息</button>

    </div>

</template>

  

<script>

    import Car from './components/Car.vue'

    export default {

        name : 'App',

        data() {

            return {

                msg : '汽车信息'

            }

        },

        methods : {

            printCarInfo(){

                // 获取子组件

                console.log(this.$refs.car1)

                console.log(this.$refs.car2)

  

                console.log(this.$refs.car1.brand)

                console.log(this.$refs.car1.color)

                console.log(this.$refs.car1.price)

  

                // 这个就不是组件了。

                console.log(this.$refs.hh.innerText)

            }

        },

        components : {Car}

    }

</script>

3.9 mixins配置(混入)

运行效果:

可以看到以上Vip.vue和User.vue代码中都有相同的methods,这个代码可以复用吗?可以使用mixins配置进行混入。实现步骤:

第一步:提取

单独定义一个mixin.js(一般和main.js在同级目录),代码如下:

第二步:引入并使用

以上演示的是方法methods的混入,实际上混入时没有限制,之前所学的配置项都可以混入。

混入时会产生冲突吗?已经有一个方法a了,如果再混入一个a方法会怎样?

通过测试,如果冲突了,会执行组件自身的,不会执行混入的。(这是原则:混入的意思就是不破坏)

但对于生命周期周期钩子函数来说,混入时,会采用叠加方式:

执行结果:

通过测试得知:对于生命周期钩子函数来说,都有的话,采用叠加,先执行混入的,再执行自己的。

以上的混入属于局部混入,只混入到指定的组件当中。

全局混入:

执行结果:

一共四个组件,所以输入四次:mixin mounted

main.js

复制代码
// 等同于引入vue.js文件

import Vue from 'vue'

// 导入App组件(根组件)

import App from './App.vue'

  

import {mix1} from './mixin.js'

import {mix2} from './mixin.js'

import {mix3} from './mixin.js'

  

// 全局混入

Vue.mixin(mix1)

Vue.mixin(mix2)

Vue.mixin(mix3)

  

// 关闭生产提示信息

Vue.config.productionTip = false

  

// 创建Vue实例

new Vue({

  el : '#app',

  // 您正在使用Vue的仅运行时版本,其中模板编译器不可用。

  // 目前使用的vue.js是一个缺失了模板编译器的vue.js文件。

  // 怎么解决,两种方案:

  // 第一种:使用完整版的vue.js: import Vue from 'vue/dist/vue.js'

  // 第二种:使用render函数。

  // 为什么采用缺失模板编译器的vue.js?

  // 目的:减小体积。Vue.js包括两块:Vue的核心 + 模板编译器(模板编译器可能占用vue.js文件体积的三分之一。)

  // 将来程序员使用webpack进行打包处理之后,模板编译器就没有存在的必要了。

  //template : '<h1>render函数</h1>'

  // 看不懂,一会再说。

  //render: h => h(App),

  // render函数被自动调用

  // 这个函数被调用的时候会自动传过来一个参数:createElement,createElement是一个函数。

  // createElement函数可以用来创建元素

  /* render(createElement){

    // 创建了一个div元素

    //return createElement('div', 'render函数')

    return createElement(App)

  } */

  

  render : h => h(App)

})

mixins

复制代码
export const mix1 = {

    methods: {

        printInfo(){

           console.log(this.name, ',' , this.age)

        }

    }

}

  

export const mix2 = {

    methods: {

        a(){

            console.log('mixin.js a.....')

        }

    },

}

  

export const mix3 = {

    mounted() {

        console.log('mixin.js mounted...')

    }

}

User

复制代码
<template>

    <div>

        <h1>{{msg}}</h1>

        <h3>姓名:{{name}}</h3>

        <h3>年龄:{{age}}</h3>

        <button @click="printInfo">打印用户信息</button>

        <button @click="a">用户a</button>

    </div>

</template>

  

<script>

    // import {mix1} from '../mixin.js'

    // import {mix2} from '../mixin.js'

    // import {mix3} from '../mixin.js'

    export default {

        name : 'User',

        mounted() {

            console.log('User mounted...')

        },

        data() {

            return {

                msg : '用户信息',

                name : '张三2',

                age : 20

            }

        },

        // mixins : [mix1,mix2, mix3],

        /* methods: {

            a(){

                console.log('user a....')

            }

        }, */

    }

</script>

Vip

复制代码
<template>

    <div>

        <h1>{{msg}}</h1>

        <h3>姓名:{{name}}</h3>

        <h3>年龄:{{age}}</h3>

        <button @click="printInfo">打印会员信息</button>

        <button @click="a">会员a</button>

    </div>

</template>

  

<script>

    // import {mix1} from '../mixin.js'

    // import {mix2} from '../mixin.js'

    export default {

        name : 'Vip',

        data() {

            return {

                msg : '会员信息',

                name : '李四2',

                age : 21

            }

        },

        // mixins : [mix1,mix2],

        methods: {

            a(){

                console.log('vip a....')

            }

        },

    }

</script>

App

复制代码
<template>

    <div>

        <User></User>

        <Vip></Vip>

    </div>

</template>

  

<script>

    import User from './components/User.vue'

    import Vip from './components/Vip.vue'

    export default {

        name : 'App',

        components : {User, Vip}

    }

</script>

3.10 plugins配置(插件)

给Vue做功能增强的。

怎么定义插件?以下是定义插件并暴露插件。插件是一个对象,对象中必须有install方法,这个方法会被自动调用。

插件一般都放到一个plugins.js文件中。

导入插件并使用插件:

插件对象的install方法有两个参数:

第一个参数:Vue构造函数

第二个参数:插件使用者传递的数据

先学会用插件,后面我们做项目的时候会使用很多插件。到时再体会插件存在的意义。

3.11 局部样式scoped

默认情况下,在vue组件中定义的样式最终会汇总到一块,如果样式名一致,会导致冲突,冲突发生后,以后来加载的组件样式为准。怎么解决这个问题?

另外vue组件的style样式支持多种样式语言,例如:css、less、sass等。如何选择使用呢?

使用less注意安装less-loader:npm i less-loader

App根组件中的样式style不建议添加scoped。

App

复制代码
<template>

    <div>

        <User></User>

        <Vip></Vip>

    </div>

</template>

  

<script>

    //如果不加scoped 并且两个组件当中css样式属性名一样,就会出现覆盖的情况,并且是按照谁后引入就会覆盖先引入的文件样式

    import Vip from './components/Vip.vue'

    import User from './components/User.vue'

    export default {

        name : 'App',

        components : {User, Vip}

    }

</script>

  

<style>

    /* 一般在App根组件当中样式不会添加scoped,因为根组件的样式还是希望采用全局的方式处理。 */

</style>

User

复制代码
<template>

    <div class="s">

        <h1>{{msg}}</h1>

        <h3>姓名:{{name}}</h3>

        <h3>年龄:{{age}}</h3>

    </div>

</template>

  

<script>

    export default {

        name : 'User',

        data() {

            return {

                msg : '用户信息',

                name : '张三2',

                age : 20

            }

        }

    }

</script>

  

<style scoped>

   .s {

    background-color: aquamarine;

   }

</style>

Vip

复制代码
<template>

    <div class="s">

        <h1>{{msg}}</h1>

        <h3>姓名:{{name}}</h3>

        <h3>年龄:{{age}}</h3>

    </div>

</template>

  

<script>

    export default {

        name : 'Vip',

        data() {

            return {

                msg : '会员信息',

                name : '李四2',

                age : 21

            }

        }

    }

</script>

  

<style scoped>

   .s {

    background-color:bisque

   }

</style>

样式效果:

3.12 BugList案例

  1. 先使用静态组件的方式把页面效果实现出来。
    (1) App.vue
    (2) BugHeader.vue
    (3) BugList.vue
    (4) BugItem.vue
    (5) BugFooter.vue

动态的判断单选框的值是否默认勾选写法:

删除按钮颜色的写法:

实现效果:

表格的写法:

实现效果:

第一版代码:

App.vue

复制代码
<template>

    <div>

        <BugHeader></BugHeader>

        <br>

        <BugList></BugList>

        <BugFooter></BugFooter>

    </div>

</template>

  

<script>

    //如果不加scoped 并且两个组件当中css样式属性名一样,就会出现覆盖的情况,并且是按照谁后引入就会覆盖先引入的文件样式

    import BugHeader from './components/BugHeader.vue'

    import BugList from './components/BugList.vue'

    import BugFooter from './components/BugFooter.vue'

    // import User from './components/User.vue'

    export default {

        name : 'App',

        components : {BugHeader,BugList,BugFooter}

    }

</script>

  

<style>

    /* 一般在App根组件当中样式不会添加scoped,因为根组件的样式还是希望采用全局的方式处理。 */

   /* 共享 */

.button{

    display: inline-block;

    *display: inline;

    zoom: 1;

    padding: 6px 20px;

    margin: 0;

    cursor: pointer;

    border: 1px solid #bbb;

    overflow: visible;

    font: bold 13px arial, helvetica, sans-serif;

    text-decoration: none;

    white-space: nowrap;

    color: #555;

    background-color: #ddd;

    background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,1)), to(rgba(255,255,255,0)));

    background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));

    background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));

    background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));

    background-image: -o-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));

    background-image: linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));

    -webkit-transition: background-color .2s ease-out;

    -moz-transition: background-color .2s ease-out;

    -ms-transition: background-color .2s ease-out;

    -o-transition: background-color .2s ease-out;

    transition: background-color .2s ease-out;

    background-clip: padding-box; /* Fix bleeding */

    -moz-border-radius: 3px;

    -webkit-border-radius: 3px;

    border-radius: 3px;

    -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;

    -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;

    box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;

    text-shadow: 0 1px 0 rgba(255,255,255, .9);

    -webkit-touch-callout: none;

    -webkit-user-select: none;

    -khtml-user-select: none;

    -moz-user-select: none;

    -ms-user-select: none;

    user-select: none;

}

.button:active{

    background: #e9e9e9;

    position: relative;

    top: 1px;

    text-shadow: none;

    -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;

    -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;

    box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;

}

.button.red{

    color: #fff;

    text-shadow: 0 1px 0 rgba(0,0,0,.2);

    background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,.3)), to(rgba(255,255,255,0)));

    background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));

    background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));

    background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));

    background-image: -o-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));

    background-image: linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));

}

.button.red{

    background-color: #ca3535;

    border-color: #c43c35;

}

.button.red:hover{

    background-color: #ee5f5b;

}

.button.red:active{

    background: #c43c35;

}

.button.green{

    background-color: #57a957;

    border-color: #57a957;

}

.button.green:hover{

    background-color: #62c462;

}

.button.green:active{

    background: #57a957;

}

</style>

BugHeader.vue

复制代码
<template>

  <div class="header" >

        <textarea rows="3" cols="60" v-model="bugInfo" place="请输入bug的信息"></textarea>

        <br></br>

        <button class="small red button" @click="save">保存</button>

  </div>

</template>

  

<script>

export default {

    data(){

        return {

            bugInfo : '',

        }

    },

    methods:{

        save(){

            console.log("保存用户输入的bug信息",this.bugInfo);

        }

    }

}

</script>

  

<style scoped>

/* header */

.header {

    margin-bottom: 20px;

    margin-top: 20px;

}

</style>

BugList.vue

复制代码
<template>

  <div>

    <table>

      <thead>

        <tr>

          <th class="c1">全选<input type="checkbox"> </th>

          <th>bug描述</th>

          <th class="c2">操作</th>

        </tr>

      </thead>

      <tbody>

        <BugItem v-for="bug in bugList" :key="bug.id" :bug="bug"></BugItem>

      </tbody>

    </table>

  </div>

</template>

  

<script>

  

import BugItem from './BugItem.vue'

export default {

  components : {BugItem},

  data() {

    return {

      bugList: [

        { id: '0001', bugInfo: '问题1', isResolve: true },

        { id: '0002', bugInfo: '问题2', isResolve: true },

        { id: '0003', bugInfo: '问题3', isResolve: false },

      ]

    }

  }

}

</script>

  

<style scope>

/* list */

table {

  width: 760px;

  border-collapse: collapse;

}

  

table caption {

  font-size: 1em;

  font-weight: bold;

  margin: 1em 0;

}

  

.c1,

.c2 {

  width: 100px;

}

  

th {

  border: 1px solid #999;

  text-align: center;

  padding: 5px 0;

}

  

table thead tr {

  background-color: #008c8c;

  color: #fff;

}

</style>

BugItem.vue

复制代码
<template>

    <!-- <div></div>  -->

    <tr>

        <td><input type="checkbox" :checked="bug.isResolve"></td>

        <td><span  class="desc">{{ bug.bugInfo }}</span></td>

        <td><button class="small red button">删除</button></td>

    </tr>

</template>

  

<script>

export default {

    props : ['bug'],

}

</script>

  

<style >

/* item */

table tbody tr:nth-child(odd){

    background-color: #eee;

}

table tbody tr:hover{

    background-color: #ccc;

}

table tbody tr td:first-child{

    color: #f40;

}

td{

    border: 1px solid #999;

    text-align: center;

    padding: 5px 0;

}

/* 鼠标指针变成手型(👆) */

.desc {

    cursor: pointer;

}

</style>

BugFooter.vue

复制代码
<template>

  <div class="footer">

    <button class="small red button">清除已解决</button>

    <span>当前BUG总量是{{ 2 }}个,已解决1个。</span>

  </div>

</template>

  

<script>

export default {

  

}

</script>

  

<style >

/* footer */

.footer{

    margin-top: 10px;

}

.footer span{

    font-size: 12px;

}

</style>

初步页面效果:

  1. 在BugList.vue中提供bugList数据,实现动态数据展示。
  1. 保存bug:
    (1) 获取用户输入的信息采用双向数据绑定。
    ① 通过Date.now()获取时间戳的方式来搞定id。
    (2) 将BugList.vue中的bugList数据提升到父组件App.vue中。
    (3) 父组件向子组件传递,采用 :bugList="bugList",在子组件当中使用props接收。
    (4) 子组件向父组件传递,父组件可以提前定义一个函数,将函数传递给子组件,在子组件中调用这个函数即可。
    (5) 该功能的小问题:
    ① 保存完成后自动清空。
    ② 输入为空时不能保存(可以加trim去除空白),并且提示必须输入。
  1. 修改bug的状态
    (1) 勾选和取消勾选,会触发click事件或者change事件。
    (2) 事件发生后,获取bug的id,将id传递给App组件中的回调函数,遍历数组,拿到要修改的bug对象,更改bug对象的resolved属性值。
  1. 删除bug
    (1) 删除时可以调用数组的filter方法进行过滤,将过滤之后的新数组赋值给this.bugList
  1. 统计bug
    (1) 第一种:普通计数器统计。
    (2) 第二种:数组的reduce方法完成条件统计。
  1. 全选和取消全选
    (1) 全选复选框的状态维护:
    ① 已解决的数量 === 总数量 时,勾选。
    ② 全部删除后,应该取消勾选。
    (2) 全部删除了可以将footer隐藏。v-show
    (3) 全选和取消全选
  1. 清除已解决
    (1) 调用数组的filter方法进行过滤,生成新数组,将其赋值给this.bugList
  1. 实现编辑功能
    (1) 功能描述
    ① 鼠标移动到描述信息上之后,光标变成小手。
    ② 点击描述信息之后,将描述信息放入文本框。并且同时让文本框获得焦点。
    ③ 用户开始修改描述信息(要注意避免将信息修改为空)
    ④ 输入修改信息之后,文本框失去焦点,显示修改后的描述信息。
    (2) 实现功能的核心技术:
    ① 给bug对象扩展一个具有响应式的editState属性,如果是true表示处于编辑状态,false表示处于未编辑状态:this.$set(bug, 'editState', true)
    ② 获得焦点的动作如何完成:
  1. 在文本框上添加ref="inputDesc",然后通过this.$refs.inputDesc获取到dom元素,调用focus()让其获取焦点。
  1. 以上操作需要在下一次渲染DOM完成后执行:nextTick
    a. this.nextTick(function(){this.refs.inputDesc.focus()})

3.13 localStorage和sessionStorage

window.localStorage 浏览器关闭,数据还在。

getItem removeItem setItem clear

JSON.stringify

JSON.parse

存储大小5mb

Window.sessionStorage 浏览器关闭清空存储。

getItem的key不存在的话返回null。JSON.parse(null),结果还是null。

改造项目。用本地存储来改造。使用监视属性watch,并且要开启深度监视。

3.14 使用本地存储改造BugList案例

3.15 组件自定义事件

click、keydown、keyup,这些事件都是内置事件。

Vue也支持给组件添加自定义事件。

包括两种方式:

第一种方式:直接在组件标签上绑定事件

第二种方式:通过代码来给组件绑定事件

3.15.1 直接在组件标签上绑定事件

<Car @event1="doSome">

表示给Car这个组件vc实例绑定event1事件,当event1事件发生时,doSome方法执行。

事件绑定在谁的身上,谁就负责触发这个事件,怎么触发?在Car组件中定义methods:

methods : {

triggerEvent1(){

// 触发事件并且给事件传数据

this.$emit('event1', this.name, this.age, this.gender)

}

}

然后,在Car的父组件中编写doSome方法:

methods : {

doSome(name, age, gender){}

// 或者可以这样

doSome(name, ...parameters){} // ...parameters表示采用一个数组接收参数

}

通过这种方式可以轻松完成子组件向父组件传递数据。

<Car @event1.once="doSome"> 表示只触发一次。 <Car @click.native="doSome"> 使原生事件生效。

3.15.2 通过代码给组件绑定事件

在父组件当中:

mounted(){ // 表示挂载完毕后给组件绑定事件。

// 这种方式更加灵活。例如:希望AJAX请求响应回来数据之后再给组件绑定事件。

this.refs.car.on('event1', this.doSome)

}

this.refs.car.once('event1', this.doSome) 表示只触发一次。

绑定时要注意:

this.refs.car.on('event1', function(){

//这里的this是子组件实例(Car组件实例)

})

this.refs.car.on('event1', ()=>{

// 这里的this是父组件实例(App组件实例)

})

this.doSome这个回调函数写成普通函数时:函数体中this是子组件实例。(Car组件实例)

this.doSome这个回调函数写成箭头函数时:函数体中this是父组件实例。(App组件实例)

3.15.3 解绑事件

哪个组件绑定的就找哪个组件解绑:

methods : {

unbinding(){

this.off('event1') // 这种方式只能解绑一个事件。

this.off('event1', 'event2') // 这种方式解绑多个事件。

this.$off() // 解绑所有事件。

}

}

注意:vm和vc销毁的时候,所有组件以及子组件当中的事件会全部解绑。

3.16 全局事件总线

原理:给项目中所有的组件找一个共享的vc对象。把这个共享的对象vc叫做全局事件总线。所有的事件都可以绑定到这个共享对象上。所有组件都通过这个全局事件总线对象来传递数据。这种方式可以完美的完成兄弟组件之间传递数据。这样的共享对象必须具备两个特征:

(1) 能够让所有的vc共享。

(2) 共享对象上有on、off、emit等方法。

第一种解决方案:

在main.js文件中:

// 获取VueComponent构造函数

const VueComponentConstructor = Vue.extend({})

// 创建vc

const vc = new VueComponentConstructor()

// 让所有的vc都能够使用这个vc

Vue.prototype.bus = vc

第二种解决方案:建议的。

在main.js文件中:

new Vue({

el : '#app',

render : h => h(App),

beforeCreate(){

Vue.prototype.$bus = this

}

})

永远需要记住的:A组件向B组件传数据,应该在B组件中绑定事件(接)。应该在A组件中触发事件(传)。

数据发送方:触发事件

methods : {

triggerEvent(){

this.bus.emit('eventx', 传数据)

}

}

数据接收发:绑定事件

mounted(){

this.bus.on('eventx', this.doSome)

}

养成好习惯:组件实例被销毁前,将绑定在bus上的事件解绑。

beforeDestroy(){

this.bus.off('eventx')

}

3.17 BugList案例改造

3.17.1 使用组件自定义事件改造BugList案例

所有从父向子传递函数的位置,都可以修改为自定义事件方式。

父组件向子组件传递数据的写法

主要改造子向父传数据的功能。

3.17.2 使用全局事件总线改造BugList案例

主要改造爷孙之间数据的传递。

自定义事件在Vue开发者工具当中是可以在看到的。

组件销毁的时候,记得把全局事件总线对象上绑定的事件解绑。

3.18 消息订阅与发布

使用pubsub-js库完成消息订阅与发布。该库可以在任意前端框架中实现消息的订阅与发布。

安装pubsub-js:npm i pubsub-js

程序中引入pubsub:import pubsub from 'pubsub-js'

引入了一个pubsub对象,通过调用该对象的subscribe进行消息订阅,调用publish进行消息发布。

订阅:subscribe

mounted(){

this.pubsubId = pubsub.subscribe('message', (messageName, data) => {

// 两个参数:第一个是消息的名字。第二个参数是消息发布时传过来的数据。

// 要使用箭头函数。这样才能保证this的使用。

})

}

beforeDestroy(){

pubsub.unsubscribe(this.pubsubId )

}

发布:publish

pubsub.publish('message', 'zhangsan', 20)

组件间的通信方式总结:

① props:可以完成父向子传数据

② 父向子传一个函数:可以完成子向父传数据

③ 组件自定义事件:可以完成子向父传数据。

④ 全局事件总线

⑤ 消息订阅与发布

3.19 使用消息订阅与发布改造BugList案例

组件销毁时,记得取消订阅。

4.Vue与AJAX

4.1 回顾发送AJAX异步请求的方式

发送AJAX异步请求的常见方式包括:

  1. 原生方式,使用浏览器内置的JS对象XMLHttpRequest
    (1) const xhr = new XMLHttpRequest()
    (2) xhr.onreadystatechange = function(){}
    (3) xhr.open()
    (4) xhr.send()
  1. 原生方式,使用浏览器内置的JS函数fetch
    (1) fetch('url', {method : 'GET'}).then().then()
  1. 第三方库方式,JS库jQuery(对XMLHttpRequest进行的封装)
    (1) .get()
    (2) .post()
  1. 第三方库方式,基于Promise的HTTP库:axios (对XMLHttpRequest进行的封装)
    (1) axios.get().then()
    axios是Vue官方推荐使用的。
    4.2 回顾AJAX跨域
  1. 什么是跨域访问?
    (1) 在a页面中想获取b页面中的资源,如果a页面和b页面所处的协议、域名、端口不同(只要有一个不同),所进行的访问行动都是跨域的。
    (2) 哪些跨域行为是允许的?
    ① 直接在浏览器地址栏上输入地址进行访问
    ② 超链接

    <link href="其它网站的css文件是允许的">
    <script src="其它网站的js文件是允许的">
    ⑥ ......
    (3) 哪些跨域行为是不允许的?
    ① AJAX请求是不允许的
    ② Cookie、localStorage、IndexedDB等存储性内容是不允许的
    ③ DOM节点是不允许的

  1. AJAX请求无法跨域访问的原因:同源策略
    (1) 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
    (2) AJAX请求不允许跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
  1. 解决AJAX跨域访问的方案包括哪些
    (1) CORS方案(工作中常用的)
    ① 这种方案主要是后端的一种解决方案,被访问的资源设置响应头,告诉浏览器我这个资源是允许跨域访问的:response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
    (2) jsonp方案(面试常问的)
    ① 采用的是<script src="">不受同源策略的限制来实现的,但只能解决GET请求。
    (3) 代理服务器方案(工作中常用的)
    ① Nginx反向代理
    ② Node中间件代理
    ③ vue-cli(Vue脚手架自带的8080服务器也可以作为代理服务器,需要通过配置vue.config.js来启用这个代理)
    (4) postMesssage
    (5) websocket
    (6) window.name + iframe
    (7) location.hash + iframe
    (8) document.domain + iframe
    (9) ......
  1. 代理服务器方案的实现原理
    同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略的。

4.3 演示跨域问题

Vue脚手架内置服务器的地址:http://localhost:8080

我们可以额外再开启一个其它的服务器,这个服务器随意,例如:node server、Apache服务器、JBOSS服务器、WebLogic服务器、WebSphere服务器、jetty服务器、tomcat服务器......我这里选择的是基于Java语言的一个服务器Tomcat,这个web服务器开启了一个8000端口,提供了以下的一个服务,可以帮助我们获取到一个Bug列表:
http://localhost:8000/bugs/

打开BugList案例的代码,在mounted钩子函数中发送ajax请求,获取bug列表。

vue-cli安装axios库:npm i axios。使用时:import导入axios

以上的访问表示:在8080服务器中发送AJAX请求访问8000服务器,必然会出现AJAX跨域问题:

4.4 启用Vue脚手架内置服务器8080的代理功能

  1. 简单开启
    vue.config.js文件中添加如下配置:
    devServer: {
    proxy: 'http://localhost:8000' // 含义:Vue脚手架内置的8080服务器负责代理访问8000服务器
    }
    发送AJAX请求时,地址需要修改为如下:

原理:访问地址是http://localhost:8080/bugs,会优先去8080服务器上找/bugs资源,如果没有找到才会走代理。

另外需要注意的是:这种简单配置不支持配置多个代理。

  1. 高级开启

支持配置多个代理。

devServer: {

proxy: {

'/api': {

target: 'http://localhost:8000',

pathRewrite:{'^/api', ''},

ws: true, // 支持websocket

changeOrigin: true // true表示改变起源(让目标服务器不知道真正的起源)

},

'/abc': {

target: 'http://localhost:9000',

pathRewrite:{'^/abc', ''},

ws: true, // 默认值true

changeOrigin: true // 默认值true

}

}

}

4.5 使用AJAX改造BugList案例

mounted钩子中发送ajax请求即可。

4.6 Vue插件库vue-resource发送AJAX请求

  1. 安装:npm i vue-resource
  1. import vueResource from 'vue-resource'
  1. 使用插件:Vue.use(vueResource)
  1. 使用该插件之后,项目中所有的vm和vc实例上都添加了:$http属性。
  1. 使用办法:
    (1) this.http.get('').then() 用法和axios相同,只不过把axios替换成this.http
    4.7 天气预报
  1. 实现效果
  1. 接口来自:https://openweathermap.org/
  1. 开发者进行注册,获取api key

  2. 获取当前天气的接口

  3. 根据城市名字获取经度和纬度的接口

  1. 功能实现要点:
    (1) 首先实现静态组件

    ② App根组件下有两个子组件:Search、Weather
    (2) 根据城市名获取经度纬度的接口:
    http://api.openweathermap.org/geo/1.0/direct?q=${this.cityName}&appid=${apiKey}
    ② 以上红色字体采用了ES6的模板字符串。
    ③ 经度:const lon = response.data0.lon
    ④ 纬度:const lat = response.data0.lat
    (3) 获取天气信息的接口:
    https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}
    ② 天气图标:response.data.weather0.icon
    ③ 温度:response.data.main.temp
    ④ 湿度:response.data.main.humidity
    ⑤ 风力:response.data.wind.speed
    (4) 以上免费接口已经在服务端解决了跨域的问题,可以直接用。
    (5) 兄弟组件之间通信采用全局事件总线。
    (6) 以上接口返回的图标的id,根据图标id动态拼接图片地址


    (7) 初次打开页面的时候,不应该显示天气的任何信息,Weather组件应该是隐藏的。
    (8) 查询天气过程中,显示"正在拉取天气信息,请稍后..."
    (9) 如果访问的城市不存在时,应该提示"对不起,请检查城市名"
    (10) 如果出现其它错误一律显示"网络错误,请稍后再试"
    (11) 在Search组件中触发事件的时候,将多个属性封装为对象的形式(语义化更明确)传给Weather组件。
    ① 在Weather组件中可以采用对象的形式一次性接收。
    ② 注意结构中的代码:

{{weather.windSpeed}} m/s

(12) ES6语法,合并对象

① this.weather = {...this.weather, ...weather}

5. Vuex

5.1 vuex概述

  1. vuex是实现数据集中式状态管理的插件。数据由vuex统一管理。其它组件都去使用vuex中的数据。只要有其中一个组件去修改了这个共享的数据,其它组件会同步更新。一定要注意:全局事件总线和vuex插件的区别:
    (1) 全局事件总线关注点:组件和组件之间数据如何传递,一个绑定on,一个触发emit。数据实际上还是在局部的组件当中,并没有真正的让数据共享。只是数据传来传去。

(2) vuex插件的关注点:共享数据本身就在vuex上。其中任何一个组件去操作这个数据,其它组件都会同步更新。是真正意义的数据共享。

  1. 使用vuex的场景是:
    (1) 多个组件之间依赖于同一状态。来自不同组件的行为需要变更同一状态。
    5.2 vuex环境搭建
  1. 安装vuex
    (1) vue2安装vuex3版本
    ① npm i vuex@3
    (2) vue3安装vuex4版本
    ① npm i vuex@4
  1. 创建目录和js文件(目录和文件名不是必须叫这个)
    (1) 目录:vuex
    (2) js文件:store.js
  1. 在store.js文件中创建核心store对象,并暴露
  1. 在main.js文件中关联store,这一步很重要,完成这一步之后,所有的vm和vc对象上会多一个$store属性

5.3 vuex实现一个最简单的案例

  1. 使用vuex实现一个点我加1的简单功能。
  1. 为什么这么折腾呢?
    (1) 通过以上案例,可以看出数据num可以被多个组件共享。(vuex可以管理多个组件共享的数据)
    (2) 通过on和emit这种全局事件总线不好吗?可以。但如果组件多的话,并且涉及到读和写的操作会导致混乱。
  1. actions中的回调函数,参数context
    (1) 如果业务逻辑非常负责,需要多个actions中的方法联合起来才能完成,可以在回调函数中使用context继续调用dispatch方法触发下一个action方法的执行。
    5.4 vuex工作原理

如果业务逻辑非常简单,也不需要发送AJAX请求的话,可以不调用dispatch方法,直接调用commit方法也是可以的。

5.5 多组件数据共享

实现以下案例:

5.6 getters配置项

  1. 如果想将state中的数据进行加工计算,并且这个计算逻辑复杂,而且要在多个位置使用,建议使用getters配置项。
  1. 怎么用?
    (1)
    (2)
    (3) 类似于Vue当中的:data和computed的关系。
    5.7 mapState和mapGetters的使用(优化计算属性)
  1. 组件中在使用state上的数据和getters上的数据时,都有固定的前缀:
    {{this.store.state.name}}
    {{this.store.getters.reverseName}}
    使用mapState和mapGetters进行名称映射,可以简化以上的写法。
  1. 使用mapState和mapGetters的前提是先引入
    (1) import {mapState, mapGetters} from 'vuex'
  1. mapState如何使用,在computed当中使用ES6的语法
    (1) 第一种方式:对象形式
    ① ...mapState({name:'name'})
    (2) 第二种方式:数组形式
    ① ...mapState('name')
    (3) 插值语法就可以修改为:{{name}}
  1. mapGetters如何使用,在computed当中使用ES6的语法
    (1) 第一种方式:对象形式
    ① ...mapGetters({reverseName:'reverseName'})
    (2) 第二种方式:数组形式
    ① ...mapGetters('reverseName')
    (3) 插值语法就可以修改为:{{reverseName}}
    5.8 mapMutations和mapActions的使用(优化methods)
    import {mapMutations, mapActions} from 'vuex'
    methods : {
    // 对象写法
    ...mapActions({add:'plusOne',reverseName:'reverseName'})
    // 数组写法(前提是:保证methods中的方法名和actions中的方法名一致)
    ...mapActions('plusOne', 'reverseName')
    }
    5.9 vuex的模块化开发
    5.9.1 未使用mapXxxx的模块化开发
    a模块

b模块

c模块

在store.js文件中引入各个模块

A组件

b组件

将A组件和B组件在App组件中注册

5.9.2 使用mapXxxx的模块化开发

a模块

b模块

在store.js中引入a和b模块

A组件

B组件

在APP组件中注册A和B组件

当然,在action中也可以发送AJAX请求:

6. 路由route

6.1 传统web应用vs单页面web应用

传统web应用

传统web应用,又叫做多页面web应用:核心是一个web站点由多个HTML页面组成,点击时完成页面的切换,因为是切换到新的HTML页面上,所以当前页面会全部刷新。

单页面web应用(SPA:Single Page web Application)

整个网站只有一个HTM页面,点击时只是完成当前页面中组件的切换。属于页面局部刷新。

单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。单页面的跳转仅刷新局部资源。因此,对单页应用来说模块化的开发和设计显得相当重要。

单页面应用的优点:

1、提供了更加吸引人的用户体验:具有桌面应用的即时性、网站的可移植性和可访问性。

2、单页应用的内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷。

3、单页应用没有页面之间的切换,就不会出现"白屏现象",也不会出现假死并有"闪烁"现象

4、单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。

5、良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端

单页面应用的缺点:

1、首次加载耗时比较多。

2、SEO问题,不利于百度,360等搜索引擎收录。

3、容易造成CSS命名冲突。

4、前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本高。

单页面和多页面的对比

目前较为流行的是单页面应用的开发。

如果想使用Vue去完成单页面应用的开发,需要借助Vue当中的路由机制。

6.2 路由route与路由器router

路由:route

路由器:router

每一个路由都由key和value组成。

key1+value1===>路由route1

key2+value2===>路由route2

key3+value3===>路由route3

......

路由的本质:一个路由表达了一组对应关系。

路由器的本质:管理多组对应关系。

Vue中路由的工作原理:

6.3 使用路由

  1. 实现功能描述
  1. 根据静态页面提取两个组件:Tea.vue和Fruit.vue

  2. vue-router也是一个插件,安装vue-router
    (1) vue2要安装vue-router3
    ① npm i vue-router@3
    (2) vu3要安装vue-router4
    ① npm i vue-router@4

  1. main.js中引入并使用vue-router
    (1) 导入:import VueRouter from 'vue-router'
    (2) 使用:Vue.use(VueRouter)
    (3) new Vue时添加新的配置项:一旦使用了vue-router插件,在new Vue的时候可以添加一个全新的配置项:router
  1. router路由器的创建一般放在一个独立的js文件中,例如:router/index.js
    (1) 创建router目录
    (2) 创建index.js,在index.js中创建路由器对象,并且将其暴露。然后在main.js文件中引入该路由器即可。
  1. 使用router-link标签代替a标签(App.vue中)

router-link标签最终编译之后的还是a标签。vue-router库帮助我们完成的。

  1. 添加激活样式

使用active-class属性,在激活时添加样式:selected

  1. 指定组件的最终显示位置。
  1. 测试

注意事项:

① 路由组件一般会和普通组件分开存放,路由组件放到pages目录,普通组件放到components目录下。

② 路由组件在进行切换的时候,切掉的组件会被销毁。

③ 路由组件实例比普通组件实例多两个属性:route和router

  1. route:属于自己的路由对象。

  2. router:多组件共享的路由器对象。

6.4 多级路由

  1. 要实现的效果
  1. 主要实现

6.5 路由query传参

为了提高组件的复用性,可以给路由组件传参。

怎么传?

怎么接?

6.6 路由起名字

可以给路由起一个名字,这样可以简化to的编写。

怎么起名?

怎么使用?必须使用 :to="{}" 的方式

6.7 路由params传参

怎么接?

怎么传?

需要注意的是,如果采用params传参,使用:to的时候,只能用name,不能用path。

6.8 路由的props

props配置主要是为了简化query和params参数的接收。让插值语法更加简洁。

第一种实现方式:

第二种实现方式:函数式

第三种实现方式:直接将params方式收到的数据转化为props

6.9 router-link的replace属性

  1. 栈数据结构:先进后出,后进先出原则。
  1. 浏览器的历史记录是存储在栈这种数据结构当中的。包括两种模式:
    (1) push模式(默认的)
    (2) replace模式
  1. 如何开启replace模式:
    (1)
    (2)
    6.10 编程式路由导航
    需求中可能不是通过点击超链接的方式切换路由,也就是说不使用如何实现路由切换。可以通过相关API来完成:
    (1) push模式:
    ① this.router.push({
    name : '',
    query : {}
    })
    (2) replace模式:
    ① this.router.replace({
    name : '',
    query : {}
    })
    (3) 前进:
    ① this.router.forward()
    (4) 后退:
    ① this.router.back()
    (5) 前进或后退几步:
    ① this.router.go(2) 前进两步
    ② this.router.go(-2) 后退两步
    (6) 使用编程式路由导航的时候,需要注意:重复执行push或者replace的API时,会出现以下错误:

这个问题是因为push方法返回一个Promise对象,期望你在调用push方法的时候传递两个回调函数,一个是成功的回调,一个是失败的回调,如果不传就会出以上的错误。所以解决以上问题只需要给push和replace方法在参数上添加两个回调即可。

6.11 缓存路由组件

默认情况下路由切换时,路由组件会被销毁。有时需要在切换路由组件时保留组件(缓存起来)。

这里的组件名称指的是:

不写include时:包含的所有路由组件全部缓存。

如何指定多个缓存路由,可以使用数组形式:

6.12 activated和deactivated

这是两个生命周期钩子函数。

只有"路由组件"才有的两个生命周期钩子函数。

路由组件被切换到的时候,activated被调用。

路由组件被切走的时候,deactivated被调用。

这两个钩子函数的作用是捕获路由组件的激活状态。

6.13 路由守卫

6.13.1 全局前置守卫

router/index.js文件中拿到router对象。

router.beforeEach((to, from, next)=>{ // 翻译为:每次前(寓意:每一次切换路由之前执行。)

// to 去哪里(to.path、to.name)

// from 从哪来

// next 继续:调用next( )

})

这种路由守卫称为全局前置路由守卫。

初始化时执行一次,以后每一次切换路由之前调用一次。

如果路由组件较多。to.path会比较繁琐,可以考虑给需要鉴权的路由扩展一个布尔值属性,可以通过路由元来定义属性:meta:{isAuth : true}

6.13.2 全局后置守卫

router/index.js文件中拿到router对象。

router.afterEach((to, from)=>{ // 翻译为:每次后(寓意:每一次切换路由后执行。)

// 没有 next

document.title = to.meta.title // 通常使用后置守卫完成路由切换时title的切换。

})

这种路由守卫称为全局后置路由守卫。

初始化时执行一次,以后每一次切换路由之后调用一次。

该功能也可以使用前置守卫实现:

该功能使用后置守卫实现更好:

解决闪烁问题:

6.13.3 局部路由守卫之path守卫

注意:没有afterEnter

6.13.4 局部路由守卫之component守卫

注意:只有路由组件才有这两个钩子。

6.13.5 前端项目上线

  1. 路径中#后面的路径称为hash。这个hash不会作为路径的一部分发送给服务器:
    (1) http://localhost:8080/vue/bugs/#/a/b/c/d/e (真实请求的路径是:http://localhost:8080/vue/bugs)
  1. 路由的两种路径模式:
    (1) hash模式
    (2) history模式
  1. 默认是hash模式,如何开启history模式
    (1) router/index.js文件中,在创建路由器对象router时添加一个mode配置项:
  1. 项目打包
    (1) 将Xxx.vue全部编译打包为HTML CSS JS文件。
    (2) npm run build
  1. 这里服务器端选择使用Java服务器:Tomcat,接下来教大家配置Tomcat服务器:
    (1) 下载JDK





    (2) 安装JDK



    (3) 配置环境变量:JAVA_HOME





    ⑥ 注意:如果你安装的路径和我安装的不一样,只要能够找到JDK bin目录的上一级目录即可。
  1. (4) 下载Tomcat



    (5) 安装Tomcat
    ① 解压就是安装。我这里直接解压到C盘的根目录下
  1. (6) 配置环境变量:CATALINA_HOME


    (7) 配置环境变量:PATH



    (8) 启动Tomcat
    ① 打开dos命令窗口,输入startup.bat,看到以下窗口表示tomcat启动成功(注意Tomcat服务器的端口号是8080,启动Tomcat服务时最好先关闭vue脚手架,因为vue cli使用的端口也是8080,如果启动了Tomcat服务器,再启动vue脚手架的话,脚手架会另外开启一个其他的端口。)
  1. 如果你启动tomcat控制台有乱码也无所谓,如果实在看不下去,可以修改以下配置文件内容:
    a.
    b.
    (9) 关闭Tomcat
    ① ctrl + c 或者:dos命令窗口中输入:shutdown.bat
    (10) 将前端项目部署到Tomcat。
    ① 找到tomcat服务器的webapps目录,并找到webapps目录下的ROOT目录,清空ROOT目录
    ② 将前端项目直接拷贝放到ROOT目录下即可。
    (11) 访问项目。
    ① 重启tomcat,并访问。
    http://localhost:8080
  1. hash模式和history模式的区别与选择

(1) hash模式

① 路径中带有#,不美观。

② 兼容性好,低版本浏览器也能用。

③ 项目上线刷新地址不会出现404。

④ 第三方app校验严格,可能会导致地址失效。

(2) history模式

① 路径中没有#,美观。

② 兼容性稍微差一些。

③ 项目上线后,刷新地址的话会出现404问题。需要后端人员配合可以解决该问题。

  1. 对于tomcat服务器来说,如何解决history带来的404问题,这需要后端人员写一段代码:

(1) 在ROOT目录下新建WEB-INF目录。

(2) 在WEB-INF目录下新建web.xml文件。

(3) 在web.xml文件中做如下配置:

404/index.html 7. Vue37.1 了解Vue31. vue3官网地址 (1) https://cn.vuejs.org/ 2. vue3发布时间 (1) 2020年9月18日。 翻译: 今天,我们很自豪地宣布Vue.js 3.0"海贼王"正式发布。这个新的主要版本的框架提供了改进的性能、更小的捆绑包大小、更好的TypeScript集成、用于处理大规模用例的新API,以及为框架未来的长期迭代奠定了坚实的基础。 3.0版本代表了两年多的开发工作,包括30多个RFC、2600多个提交、来自99个贡献者的628个拉取请求,以及核心回购之外的大量开发和文档工作。我们要向我们的团队成员表示最深切的感谢,感谢他们接受了这一挑战,感谢我们提出的撤回请求,感谢我们的赞助商和支持者提供的财政支持,感谢广大社区参与我们的设计讨论并为预发布版本提供反馈。Vue是一个为社区创建并由社区支持的独立项目,如果没有您的持续支持,Vue 3.0是不可能实现的。 3. 版本迭代历史 (1) https://github.com/vuejs/core/releases 4. vue3做了哪些改动 (1) 最核心的虚拟DOM算法进行了重写。 (2) 支持tree shaking:在前端的性能优化中,es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉 (3) 最核心的响应式由Object.defineProperty修改为Proxy实现。 (4) 更好的支持TS(Type Script:TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。) (5) ...... 5. vue3比vue2好在哪 (1) (2) 翻译: ① 与Vue 2相比,Vue 3在捆绑包大小(通过树抖动可轻41%)、初始渲染(快55%)、更新(快133%)和内存使用(少54%)方面都有了显著的性能改进。 6. Vue3的新特性 (1) 新的生命周期钩子 ① (2) 键盘事件不再支持keyCode。例如:v-on:keyup.enter支持,v-on:keyup.13不支持。 (3) 组合式API(Composition API) ① (4) 新增了一些内置组件 ① (5) data必须是一个函数。 (6) ...... 7.2 Vue3工程的创建 7.2.1 vue-cli创建Vue3工程 1. 创建Vue3版本的工程,要求vue-cli最低版本4.5.0 (1) (2) 可以使用以下命令升级你的脚手架版本 ① npm install -g @vue-cli 2. 创建Vue3工程 (1) vue create vue3_pro 3. 启动工程 (1) 切换到工程根目录。 (2) npm run serve 7.2.2 vue-cli创建的vue3项目说明 1. 目录结构以及文件和vue2相同。 2. main.js文件说明 3. 查看App.vue组件 vue3中template标签下可以有多个根标签了。 7.2.3 create-vue创建Vue3工程1. create-vue是什么? (1) 和vue-cli一样,也是一个脚手架。 (2) vue-cli创建的是webpack+vue项目的脚手架工具。 (3) create-vue创建的是vite+vue项目的脚手架工具。 (4) webpack和vite都是前端的构建工具。 2. vite官网 (1) https://vitejs.cn/ 3. vite是什么?(vite被翻译为:快) (1) vite是一个构建工具,作者尤雨溪。 (2) 前端构建工具有哪些? 4. vite和传统构建工具的区别? (1) https://cn.vitejs.dev/guide/why.html 官方的说辞。 (2) 使用vite后,至少两方面是提升了: ① 服务器启动速度快。 ② 更新速度快了。 5. 使用create vue创建Vue3工程 (1) 官方指导:快速上手 | Vue.js (2) 安装 create-vue脚手架并创建vue3项目:npm init vue@latest 执行时,如果检测到没有安装create-vue脚手架时会安装脚手架。如果检测到已经安装过脚手架,则直接创建项目。 一路选择No: (3) cd vue3_pro (4) npm install (5) npm run dev (6) 访问5173端口 7.2.4 create-vue创建的vue3工程说明1. 目录结构 2. 和vue-cli脚手架创建的区别 (1) index.html文件不再放到public目录下了。 ① vite官方的解释是:让index.html成为入口。(vue-cli脚手架生成的项目入口是:main.js) (2) vue-cli的配置文件vue.config.js。create-vue脚手架的配置文件:vite.config.js ① vite.config.js 能配置什么?可以参考vite官网:https://cn.vitejs.dev/config/ 例如配置代理服务器和以前就不太一样了。 7.3 Proxy实现原理 Vue2的响应式核心:Object.defineProperty Vue3的响应式核心:Proxy 7.3.1 Object.defineProperty get做数据代理。set做数据劫持。在set方法中修改数据,并且渲染页面,完成响应式。 Object.defineProperty(data, 'name', { get(){ // 数据代理 }, set(val){ // 数据劫持 } }) 存在的问题:1) 通过数组下标修改数据,这个操作是没有响应式的。 2) 后期给对象新增属性、删除属性,这些操作都是没有响应式的。 导致以上问题最根本原因是:Object.defineProperty只能捕获到读和修改。 Vue2中怎么解决以上问题? 1) 对于数组来说调用数组的相关API。例如:push、shift、unshift.... 2) 新增属性、删除属性等操作通过:Vue.set或者this.$set 7.3.2 Proxy Proxy是ES6新增特性。window.Proxy Proxy是一个构造函数,参数传递一个目标对象,可以为目标对象生成代理对象。 通过访问代理对象上的属性来间接访问目标对象上的属性。 访问代理对象上的属性时,读属性、修改属性、删除属性、新增属性。Proxy都可以捕获到。 Vue3框架底层实际上使用了ES6的Reflect对象来完成对象属性的访问: 7.4 setup1. setup是一个函数,vue3中新增的配置项。 2. 组件中所用到的data、methods、computed、watch、生命周期钩子....等,都要配置到setup中。 3. setup函数的返回值: (1) 返回一个对象,该对象的属性、方法等均可以在模板语法中使用,例如插值语法。 (2) 返回一个渲染函数,从而执行渲染函数,渲染页面。 ① import {h} from 'vue' (引入渲染函数) ② return ()=>{h('h2', '欢迎大家学习Vue3')} 4. vue3中可以编写vue2语法,向下兼容的。但是不建议。更不建议混用。 5. setup中的this是undefined。所以setup中this是不可用的。 7.5 ref函数(实现响应式) 7.5.1 简单数据的响应式 1. ref是一个函数。完成响应式的。 2. ref使用时需要import (1) import {ref} from 'vue' 3. 凡是要实现响应式的data,需要使用ref函数进行包裹 (1) let name = ref('李四') (2) 输出name,你会发现,它是RefImpl对象(引用实现的实例对象,简称引用对象),在这个引用对象当中有一个value属性,value属性的实现原理是:Object.defineProperty,通过它实现了响应式处理。 4. 修改数据的话必须这样做:name.value = '王五' 7.5.2 对象数据的响应式 重点:如果ref函数中是一个对象,例如:ref({}),底层会为这个对象{}生成一个Proxy代理对象。通过这个代理对象完成了响应式。 如果代码是这样写,如下,实际上没有用到Proxy,还是使用了Object.defineProperty完成的响应式: 如果代码是这样写,如下,就是使用了Proxy完成的响应式: 7.6 Vue3中专门为对象做响应式的核心函数:reactive 以上代码中的Proxy是怎么创建的?底层是通过调用reactive函数来完成的。 当然这个函数我们也可以自己使用。 注意:这个函数不能为简单类型服务,只能为对象类型服务。 注意:reactive为响应式做了递归处理。对象中的对象中的对象的属性,也是响应式的。 重点1: 并且数组如果使用reactive函数包裹的话,数组中的数据,在通过下标去修改的时候,也是支持响应式的。(这个在Vue2中是不支持的。) 重点2: 在开发中一般很少使用ref,一般会使用reactive。即使是简单类型的数据,也会将其存放到一个对象中,使用reactive函数进行包括。 7.7 Vue3中的props 给组件User传数据 User组件使用props接收数据 在setup函数中如何使用props? setup的第一个参数就是props。可以直接拿来用。 7.8 Vue3的生命周期 vue2的生命周期图: vue3的生命周期图: 1. vue2中 (1) beforeDestroy (2) destroyed 2. vue3中 (1) beforeUnmount (2) unmounted 3. vue3中仍然可以使用配置项方式提供生命周期钩子函数。但也可以使用组合式API方式。如果采用组合式API方式,API名称规律如下: (1) beforeCreate => setup (2) created => setup (3) beforeMount => onBeforeMount (4) mounted => onMounted (5) beforeUpdate => onBeforeUpdate (6) updated => onUpdated (7) beforeUnmount => onBeforeUnmount (8) unmounted => onUnmounted 4. 当然如果需要使用beforeCreate和created钩子,可以采用vue2的语法:配置项形式。vue2和vue3语法可以共存,但不建议。 7.9 Vue3中的自定义事件 绑定事件 触发事件 7.10 Vue3的全局事件总线1. 安装mitt (1) npm i mitt 2. 封装event-bus.js文件 (1) (2) 3. 绑定事件 4. 触发事件 5. 移除所有事件和指定事件 7.11 vue3的计算属性 import {computed} from 'vue' //computed是一个组合式的API。 setup(){ // 简写 let reversedName = computed(()=>{ }) // 完整写法 let reversedName = computed({ get(){}, set(value){} }) } 计算属性最重要的特征是:只要计算属性关联的数据发生变化,计算属性的回调函数就会执行。所以计算属性关联的数据必须是具有响应式的。 7.12 自定义hook函数 将组合式API拿出来,封装成一个函数,在需要复用的位置,使用这个hook函数。 一般创建一个hooks目录,在hooks目录当中放hook函数。 代码复用。 在需要使用的位置导入: 7.13 vue3的监视属性 import {watch} from 'vue' //watch就是组合式API。 1. 监视ref定义的一个响应式数据 (1) watch(数据, (newValue, oldValue)=>{}) 2. 监视ref定义的多个响应式数据 (1) watch(数据1, (newValue, oldValue)=>{}) (2) watch(数据2, (newValue, oldValue)=>{}) 或者 (3) watch(数据1,数据2, (newValue, oldValue)=>{}) 3. immediate在哪里写? (1) 在watch的第三个参数位置上使用一个对象:{immediate : true, deep:true} 4. 监视reactive定义的响应式数据。注意:oldValue拿不到 (1) watch(数据, (newValue, oldValue)=>{}) 5. 如果监视的reactive定义的响应式数据。强制开启了深度监视,配置deep无效。默认就是监视对象的全部属性。 6. 如果要监视对象中的某个属性怎么办?被监视的数据必须是一个函数,将要监视的数据返回 (1) watch(()=>user.age, (newValue,oldValue)=>{}) 7. 如果要监视对象中的某些属性怎么办? (1) watch(()=\>user.age, ()=\>user.name, (newValue,oldValue)=>{}) 8. 如果要监视reactive定义的响应式数据中的某个属性,这个属性是一个对象形式,那么开启deep:true是可以的。 (1) watch(()=>user.address, (newVal, oldVal)=>{}) 9. 关于ref包裹对象时的value (1) watch(name.value, (newVal, oldVal){}) //监视无效 (2) watch(user.value, (newVal, oldVal)=>{})//监视有效 (3) watch(user,(newVal, oldVal)=>{}, {deep:true}) // 深度监视生效 7.14 watchEffect函数1. watchEffect函数里面直接写一个回调 (1) 回调中用到哪个属性,当这个属性发生变化的时候,这个回调就会重新执行。 (2) watchEffect(()=>{const a = data.counter1; const b = data.counter2 逻辑代码......}) 当counter1和counter2被修改后,这个回调就会执行。 2. computed和watchEffect的区别? (1) computed注重返回结果。 (2) watchEffect注重逻辑处理。 3. 示例代码 7.15 shallowReactive和shallowRef 浅层次的响应式。 shallowReactive:对象的第一层支持响应式,第二层就不再支持了。 shallowRef:只给基本数据类型添加响应式。如果是对象,则不会支持响应式。 以下是演示shallowRef 以下是演示shallowReactive 7.16 组合式API和选项式API对比 组合式API:Composition API 选项式API:Options API 选项式API:特点是 分散。 组合式API:特点是 集中 (一个hook是一个独立的功能,一个hook中有自己的data、methods、computed、watch) 7.17 深只读与浅只读 组合式API:readonly与shallowReadonly 应用场景:其它组件传递过来的数据,如果不希望你修改,你最好加上只读,以防以后不小心改了人家的数据。 深只读: 浅只读: 7.18 响应式数据的判断 isRef:检查某个值是否为 ref。 isReactive:检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。 isProxy:检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。 isReadonly:检查传入的值是否为只读对象。 7.19 toRef和toRefs 通过以上写法,可以看到模板语法中都有"data.",这个是可以改善的。 大家可以看一下,下面的这个改善是否可以? 我们发现这样修改是不行的。丢失了响应式。什么原因?主要原因是因为以上的这种写法等同于: 显然这种写法和响应式对象data无关了。 再修改为以下这样行不行? 我们发现功能实现了。但是存在的问题是:data不会变。例如: 怎么解决这个问题:toRef,它可以让数据具有响应式,并且修改toRef生成的对象时,还能关联更新data: 测试结果: 还有一个更简单的:toRefs 运行结果: 7.20 转换为原始&标记为原始 toRaw与markRaw toRaw:将响应式对象转换为普通对象。只适用于reactive生成的响应式对象。 markRaw:标记某个对象,让这个对象永远都不具备响应式。比如在集成一些第三方库的时候,比如有一个巨大的只读列表,不让其具备响应式是一种性能优化。 toRaw: markRaw: 7.21 Fragment组件 fragment翻译为:碎片。片段。 在Vue2中每个组件必须有一个根标签。这样性能方面稍微有点问题,如果每一个组件必须有根标签,组件嵌套组件的时候,有很多无用的根标签。 在Vue3中每个组件不需要有根标签。实际上内部实现的时候,最终将所有组件嵌套好之后,最外层会添加一个Fragment,用这个fragment当做根标签。这是一种性能优化策略。 7.22 Teleport组件 teleport翻译为:远距离传送。用于设置组件的显示位置。

相关推荐
weixin_382395236 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__6 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅11 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni11 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端