Vue3.0基础知识(一)

Vue3.0基础知识(一)

发表于 2024-08-20
更新于 2024-08-24
阅读时长:40分钟
阅读量:7
AI 总结
|

Vue是一套构建用户界面的渐进式框架。

一、初识Vue3

1.1 概述

在2020年9月19日,万众期待的Vue3终于发布了正式版,命名为“One Piece”

新的特性:更好的性能、更小的包体积、更好的TypeScript集成、更优秀的API设计

vue3带来的新变化:

1、源码——源码通过Monorepo的形式来管理源代码

源码使用TypeScript来进行重写

Mono:单个

Repo : repositiry仓库

主要是将许多项目的代码存储在同一个repository中,这样做的目的是多个包本身相互独立,可以有自己的功能逻辑、单元测似乎等,同时又在同一个仓库下方便管理,而且模块划分的更加信息,可维护性、可拓展性更强

2、性能

在vue2.x的时候,Vue2是使用Object.defineProperty来劫持数据的getter和setter方法的,但是这种方法的缺陷是当给对象添加或删除属性时,是无法劫持和监听的

而在Vue3.x开始,Vue使用Proxy来实现数据的劫持

3、删除了不必要的API

移除了实例上的 $on, $off 和 $once ;

移除了一些特性:如filter、内联模板等

4、新的API

(1)由Option API 到 Composition API:在vue2.x的时候,我们通过Option API来描述组件的对象,Option API包括data、props、methods、computed、生命周期等等的选项。

存在比较大的问题是多个逻辑可能是在不同的地方:比如created中使用某一个method来修改data的数据,代码的内聚性非常差

(2)Composition API可以将相关联的代码放在同一处进行处理

(3)Hooks函数增加代码的复用性,在vue2.x,我们使用mixins在多个组件之间共享逻辑,但是mixins也是由一大堆Options组成的,并且多个mixins会存在命名冲突;

(3)vue3.x中,Hook函数将一部分独立的逻辑抽取出去,并且它们还可以做到是响应式的

1.2 基本使用

<div id="app">
    {{str}}
</div>
<script>
const app = Vue.createApp({
    data(){
        return {
            str:"hello world"
        }
    }

})
app.mount('#app') //挂载

//或者 
Vue.createApp({
    data(){
        return {
            str:"hello world"
        }
    }
}).mount("#app") //挂载

//或者
const App ={
        template:"#my-app",
        data () {
            return {
                info:"hello world"
            }
        }
    }

 Vue.createApp(App).mount("#app")
</script>

Vue.createApp({ 对象 })

该对象有什么属性呢?

1、template

表示Vue需要帮助我们渲染的模板信息 它里边包含很多的HTML标签,这些标签替换掉我们挂载的元素的innerHTML

//方法一
<script type="template" id="tem">
    //模板内容
</script>

<script>
Vue.createApp({
    template:"#tem"
})
</script>

//方法二
<template id="tem">
//模板内容
</template>

<script>
Vue.createApp({
    template:"#tem"
})
</script>

2、data属性

1、data属性传入的是一个函数,并且函数返回一个对象。 2、在Vue3.x的时候,如果传入一个对象,会直接报错 3、data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理

3、methods属性

改属性是一个对象,通常会在这个对象中定义很多方法: 1、这些方法会被绑定到template模板中 2、在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性

注意:不能在methods中使用箭头函数,因为这里箭头函数绑定了父级作用域的上下文

  • 为什么不能使用箭头函数?
methods:{
    fn:()=>{  //不能这样子写
        console.log(this)  //this指向window
    }
}

1.3 拓展补充

CDN

内容分发网络

1、它是指通过相互连接的网络系统,利用最靠近每个用户的服务器

2、更快、更可靠地将音乐、图片、视频、应用程序以及其他文件发送给用户

3、来提供高性能、可拓展性及低成本的网络内容传递给用户

vue3的cdn

<script src="https://unpkg.com/vue@next"></script>

MVVM模式

mvvm(Model - View - ViewModel)是非常流行的架构设计模式

Vue并没有完全遵守MVVM的模型,但是整个设计是受到了它的启发

vscode代码片段

如果经常需要在编辑器中编写重复的代码,我们可以自定义代码片段,从而达到节省时间,高效编程

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>

    <template id="my-app">
        <h2>{{info}}</h2>
    </template>

    <script src="./js/vue.js"></script>
    <script>
        const App ={
            template:"#my-app",
            data () {
                return {
                    info:"hello world"
                }
            }
        }

        Vue.createApp(App).mount("#app")
    </script>
</body>
</html>

那么,如何将上边的代码定义为用户的代码片段呢?

1、找到vscode的User snippets

2、选择其中的html.json

3、将我们需要作为模板的代码转换为json格式(https://snippet-generator.app/)

4、将生成的代码输入html.json的对象中

5、如果我们在html.json设置的"prefix": "vueapp",那么以后就可以在html文件中快速通过vueapp命令快速创建模板

二、基本语法

2.1 Mustache语法

如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值。

注:vue3允许template有多个根元素

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

    <template id="my-app">
        <div>
            <!-- 1.直接使用 -->
            <div>{{info}}</div>
            <!-- 2.表达式 -->
            <div>{{count * 10}}</div>
            <!-- 3.可以调用计算属性 -->
            <div>{{reverseInfo}}</div>
            <!-- 4.三元运算符 -->
            <div>{{isShow? info : ""}}</div>

        </div>
    </template>

    <script src="./js/vue.js"></script>
    <script>
        const App ={
            template:"#my-app",
            data () {
                return {
                    info:"hello world",
                    count:10,
                    isShow:true
                }
            },
            computed: {
                reverseInfo(){
                    return this.info.split(" ").reverse().join(" ")
                }
            }
        }

        Vue.createApp(App).mount("#app")
    </script>

2.2 基本指令

(以下的指令用的并不多,特殊情况下使用)

v-once

当数据发生变化时,元素或者组件以及所有的子元素将视为静态内容并且跳过(该指令可以用于性能优化)

<template id="my-app">
    <div>{{count}}</div>
    <div v-once>{{count}}</div>  //组件渲染后将不会再改变
    <button @click="increment">增加</button>
</template>

v-text

用于更新元素的 textContent

<template id="my-app">
    <div v-text="count"></div>
    //等价于
    <div>{{count}}</div>
</template>

v-html

默认情况下,如果展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。

如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;

<div v-html="info"></div>
//info的HTML标签会被解析
//info:"<h2 >hello world</h2>"

v-pre

v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:

跳过不需要编译的节点,加快编译的速度

<template id="my-app">
<div v-pre>{{info}}</div>
//输出{{info}}
</template>

2.3 v-bind绑定属性

1、v-bind绑定class(对象语法)

<!-- 对象语法:{'类名':Boolean} -->
    <div :class="{'actvie':true}">
        我走过你的世界
    </div>

也可以有多个键值对

<div :class="{'actvie':true,'bgcolor':true}">
    我走过你的世界
</div>

注:'actvie' 与 'bgcolor' 可以不用加单引号

也可以将| :class = 对象 |中的对象放在data中,或者通过computed(methods)中定义函数返回

<div :class="isFunc">
    我走过你的世界
</div>

2、v-bind绑定class(数组语法)

<div :class="['title',msg]"></div>
//             字符串  变量
//title,以及msg指向的内容会被当类名做添加进div

3、绑定style(对象语法)

<div :style="{color:'red'}">我变红了</div> 
//注:这里的red必须加引号,不加则会当变量处理
<div :style="{color:'red','font-size':'20px'}">我变红了</div> 
//使用-连接的css属性需要引号,使用驼峰命名则不用
<div :style="{color:'red',fontSize:'20px'}">我变红了</div>

4、动态绑定属性

根据data中的数据动态决定div的属性名称与值

<div :[name]="value">
    动态绑定属性名与值
</div>

data(){
    return {
        name:'abc',
        value:'info'
    }
}

5、动态绑定一个对象

<div v-bind="info">哈哈哈哈</div>
data(){
    return {
        info:{
            name:'mzlin',
            age:20,
            height:1.80
        }    }}
//效果:最终
<div name:'mzlin' age="20" , height="1.80">哈哈哈哈</div>

2.4 v-on绑定事件

在前端开发中,我们需要经常和用户进行各种各样的交互: 这个时候,我们就必须监听用户发生的事件,比如点击、拖拽、键盘事件等等

在Vue中如何监听事件呢?使用v-on指令。

<!-- 基本使用 -->
    <button v-on:click="btnClick">按钮</button>
<!-- 语法糖 -->
    <button @click="btnClick">按钮</button>
<!-- 绑定一个表达式 -->
    <button @click="count++">按钮</button>
<!-- 绑定一个对象 -->
    <div class="box" v-on="{click:btnClick,mouseover:mouseMove}"></div>

参数传递

<!-- 参数传递 -->
<button @click="btnClick">按钮</button>
methods:{
btnClick(event){
console.log(event)
//输出事件对象
}
}

如果需要传递多个参数,需要给改为$event

<!-- 参数传递 -->
<button @click="btnClick($event,'mzlin')">按钮</button>
methods:{
    btnClick(event,name){
    console.log(event,name)
    //输出事件对象
}
}	

修饰符

2.5 条件渲染

v-if 、v-else 、 v-else-if

根据条件来渲染某一块的内容,只有当条件为true时,才会被渲染出来

<template id="my-app">
    <div v-if="isShow">你好</div>
</template>

    data () {
        return {
            isShow:false
        }
    }
<template id="my-app">
    <div v-if="score > 90">优秀</div>
    <div v-else-if="score > 60 && score <=90">良好</div>
    <div v-else="score <= 60">不及格</div>
</template>

data () {
    return 
    score:88
    }

渲染原理

1、v-if 是惰性的

2、当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉(即标签不存在)

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

template元素

v-if是一个指令,所以必须添加到一个元素上,但是如果我们希望切换多个元素呢?

<template id="my-app">
    <div v-if="isShow">
        <h2>嘿嘿嘿嘿</h2>
        <h2>嘿嘿嘿嘿</h2>
        <h2>嘿嘿嘿嘿</h2>
    </div>

    <div v-else>
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
    </div>
</template>

问题:template最终都会残留一个外层div标签,然后并没有必要

这个时候我们可以使用template来包裹

<template v-if="isShow">
    <h2>嘿嘿嘿嘿</h2>
    <h2>嘿嘿嘿嘿</h2>
    <h2>嘿嘿嘿嘿</h2>
</template>

<template v-else>
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
    <h2>哈哈哈哈</h2>
 </template>

template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来

v-show

v-show与v-if都是能够动态控制元素是否渲染到页面上

<div v-show="isShow">
    哈哈哈哈诶
</div>
//区别
<div v-if="isShow">你好</div>

v-if与v-show的区别

1、v-show不支持template

2、v-show不可以和v-else一起使用

3、v-show元素无论是否需要显示到浏览器上,它的DOM实际上都是有渲染的,只是通过css的display来进行切换

4、v-if当条件为false时,其对应的元素不会被渲染到DOM

如何选择?

1、如果元素需要在显示和隐藏之间平凡切换,则使用v-show(它只是改变display的值)

2、如果不频繁切换,则使用v-if

2.6 v-for

v-for除了遍历数组,也可以遍历对象

<template id="my-app">
    <!-- 数组 -->
    <div v-for="(item,index) in arr">
        {{index}}-{{item}}
     </div>
    <!-- 对象 -->
    <div v-for="(value,key,index) in obj">
     //默认只传一个参数时,默认为value
      {{index}} - {{key}} - {{value}}
    </div>
</template>
    data () {
        return {
            arr:['魔戒','哈利波特','小谢尔顿','楚门的世界'],
            obj:{
                movie:"盗梦空间",
                music:"枫",
                game:'LOL',
                novel:'穆斯林的葬礼'
            }
        }
    }

也可以遍历数字

<!-- 遍历数字 -->
    <div v-for="num in 20">
        {{num}}
    </div>

v-for中key的作用

在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性

官方的解释是:

1、key属性主要用在vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes

2、如果不使用key,vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改、复用相同类型元素的算法

3、而使用key时,它会基于key的变化重新排列元素的顺序,并且会移除、销毁key不存在的元素

要理解以上概念,我们需要先了解VNode

VNode

VNode的全称是Virtual Node,也就是虚拟节点。VNode的本质是一个JavaScript的对象。

事实上,无论是组件还是元素,他们最终在Vue中表示出来的都是一个个VNode

vue会将template里边的内容进行处理,形成VNode

例如:

<template id="my-app">
        <div class="title" style="font-size: 30px; color: red;">嘿嘿嘿</div>    		</template>

 //最终表现出来的VNode是            
 const vnode = {
     type:"div",
     props:{
         class:"title",
         style:{
             "font-size":"30px",
             "color":"red"
         }
     },
     chlidren:"嘿嘿嘿"
 }

虚拟DOM

vNode不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree:

我们通过案例分析key的作用

<template id="my-app">
    <ul>
    <li v-for="item in letters" :key="item">{{item}}</li>
    </ul>
    <button @click="insertF">insert f</button>
</template>

    const App ={
        template:"#my-app",
        data () {
            return{
                letters:['a','b','c','d']
            }
        },
        methods:{
            insertF(){
                this.letters.splice(2,0,'f')
            }
        }

可以确定的是,这次更新对于ul和button是不需要进行更新,需要更新的是li的列表:

Ø 在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表;

Ø 因为对于列表中 a、b、c、d它们都是没有变化的;

Ø 在操作真实DOM的时候,只需要在中间插入一个f的li即可;

那么Vue中对于列表的更新究竟是如何操作的呢?

Ø Vue事实上会对于有key和没有key会调用两个不同的方法;

Ø 有key,那么就使用 patchKeyedChildren方法;

Ø 没有key,那么久使用 patchUnkeyedChildren方法;

2.7 计算属性

对于任何响应式数据的复杂逻辑,都应该使用计算属性

计算属性被混入到组件实例中,所有getter和setter的this上下文自动地绑定为组件实例

计算属性的用法:

  • 选项:computed

  • 类型:{ [key: string]: Function | { get: Function, set: Function } }

    <template id="my-app">
        <!-- 执行三次 -->
        <div>{{getFullName()}}</div>
        <div>{{getFullName()}}</div>
        <div>{{getFullName()}}</div>
        <!-- 执行一次 -->
        <div>{{fullName}}</div>
        <div>{{fullName}}</div>
        <div>{{fullName}}</div>
    </template>

        data () {
            return {
                firstName:'lin',
                lastName:'明'
            }
        },
            methods:{
                getFullName(){
                    console.log('调用多次,执行多次');
                    return this.firstName + " " + this.lastName
                }
            },
            computed: {
                 fullName(){
                    console.log('执行一次,有缓存');
                     return this.firstName + ' ' + this.lastName
                    }
                }
        }

计算属性会基于他们的依赖关系进行缓存,当数据不发生变化时,计算属性不需要重新计算的。

但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算

计算属性的getter和setter

computed: {
    fullName(){ //相当于get的语法糖写法
        return this.firstName + ' ' + this.lastName
    },
    // 相当于get
    fullName:{ //计算属性的另一种写法
        get:function() {
            return this.firstName + ' ' + this.lastName
        }
    }
}
<button @click="changeFullName()">改值</button>

computed: {
fullName:{
    get:function() {
        return this.firstName + ' ' + this.lastName
    },
    set:function (newValue) {
            console.log(newValue)
                }
        }
    },
    methods:{
        changeFullName(){
            this.fullName = '林林同学'
        }
    }

2.8 侦听器watch

类型:{ [key: string]: string | Function | Object | Array }

侦听器也称为监听器,当代码逻辑发生变化时,我们可以使用watch来进行相应的监听操作

<template id="my-app">
    请输入密码:<input type="text" v-model="password">
</template>

<script src="./js/vue.js"></script>
<script>
    Vue.createApp({
    template:'#my-app',
    data () {
        return {
            // 侦听password的变化,去进行一些逻辑的处理
            password:''
        }
    },
    // 监听
    watch: {
        // password侦听data中属性的名称
        // newValue变化后的新值
        // oldValue变化前的旧值
        password(newValue,oldValue) {
            console.log('新值:',newValue, '旧值:',oldValue);
        }
    }
}).mount('#app')
</script>

问题:默认情况下,侦听器只会针对侦听数据本身的改变(内部的改变时不能侦听的)

例如:可以监听到info对象,整个对象的改变。但是当info对象中的某个属性改变时,侦听不了

<template id="my-app">
    {{info.name}}
        <!--点击按钮2时改变,但是侦听不到-->
            <button @click="changeInfo">改变对象</button>
    <button @click="changeInfoName">改变对象中的值</button>
</template>

<script src="./js/vue.js"></script>
<script>
const app = {
    template:'#my-app',
    data () {
        return {
            info:{
                name:'linming',
                age:18
            }
        }
    },
    // 监听
    watch: {
        info(newValue,oldValue){
            console.log('新值',newValue,'旧值',oldValue);
        }	
    },
    methods:{
        changeInfo(){
            // 改变整个info
            this.info = {name:'哈利波特',age:12}
        },
        changeInfoName(){
            //改变info中的值
            this.info.name = '嘿嘿嘿'
        }
    }
}
Vue.createApp(app).mount("#app")
</script>

为了解决这个问题,我们需要设置侦听器的配置选项

侦听器的配置选项

1、深度侦听

开启深度侦听后,我们便可以侦听info的内部的变化了

watchinfo{ deep:true }

 // 监听
watch: {
    info:{
        handler: function(newValue,oldValue) {
            console.log('newValue:',newValue,'oldValue',oldValue);
        },
        deep:true //深度侦听
    }
},

2、立即执行

页面加载时,立即侦听一次info

watch: {
    info:{
        handler: function(newValue,oldValue) {
            console.log('newValue:',newValue,'oldValue',oldValue);
        },
        deep:true, //深度侦听
        immediate:true
    }
},

侦听器的另一种使用方式

created(){
    this.$watch("info", (newValue,oldValue)=>{
        console.log('newValue',newValue,'oldValue',oldValue);
    }, { deep:true })
}

2.9 v-model数据绑定

实现原理:

1、v-bind绑定value属性

2、v-on绑定input事件监听到函数中,函数会获取最新的值,并赋值到绑定的属性中

<template id="my-app">
    <input type="text" :value="info" @input="changeInput">
        <h2>{{info}}</h2>
</template>

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

<script>
    const App ={
        template:"#my-app",
        data () {
            return {
                info:"hello world"
            }
        },
        methods: {
            changeInput (event) {
                this.info = event.target.value
            }
        }
    }
Vue.createApp(App).mount("#app")
</script>

所以,v-model实际上是上边写法的一种语法糖

1、v-model与textarea

<!-- v-model绑定textarea -->
    <textarea cols="30" rows="10" v-model="article"></textarea>

<div>内容:{{article}}</div>

2、v-model绑定checkbox

单个勾选框

<!-- v-model绑定checkbox -->
<!-- 1、单选框 -->
<label for="agreement">
    <input id="agreement" type="checkbox" v-model='isAgree'>同意协议
    <div>isAgree:{{isAgree}}</div>
</label>

注:当label与相关属性的id属性相同时,当鼠标点击在文本上,就会触发此控件

多个勾选框

<!-- 2、多选框 -->
<div>
<label for="football">
    <input type="checkbox" value="football" id="football" v-model="hobbies">足球
</label>
<label for="basketball">
<input type="checkbox" value="basketball" id="basketball" v-model="hobbies">篮球
</label>
<label for="tennis">
<input type="checkbox" value="tennis" id="tennis" v-model="hobbies">网球
</label>
<div>hobbies:{{hobbies}}</div>
</div>

3、v-model绑定radio

<div>
<label for="male">
  <input type="radio"  id="male" value="male" v-model="gender"></label>

<label for="female">
  <input type="radio"  id="female" value="female" v-model="gender"></label>

<div>你的选择:{{gender}}</div>
</div>

4、v-model绑定select

        <!-- v-model 绑定select -->
            <div>
            <select v-model="fruit">
                <option value="banana">香蕉</option>
                <option value="apple">苹果</option>
                <option value="pear">梨子</option>
            </select>
            <div>你的选择:{{fruit}}</div>
            </div>

v-model修饰符

1、v-model.lazy=""

默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定 的属性进行同步;如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车) 才会触发;

2、number

使用v-model双向绑定数据时,vue会将同步的数据转化为string类型。使用number修饰符则可以将其改为number类型

3、trim

使用v-model.trim="",可以自动过滤自动过滤用户输入的守卫空白字符

    <template id="my-app">
        账号:<input type="text" v-model.trim="msg">
            <button @click="getMsg">捕捉</button>
    </template>

    methods: {
        getMsg(){
            console.log(this.msg);
        }
    }

三、 组件化开发

3.1 注册全局组件

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

<template id="my-app">
    {{info}}
    <component-a></component-a>
</template>

<script>
        const App = {
            template:'#my-app',
            data(){
                return{
                    info:'hello world'
                }
            }
        }
        const app = Vue.createApp(App)
        // 注册全局组件
        app.component('component-a',{
            template:
            `<h2>我是标题</h2>
            <div>我是内容</div>`,
            data(){
                return {}
            },
            methods:{}
        })
        app.mount("#app")
</script>

3.2 注册局部组件

<body>
    <div id="app"></div>
<template id="my-app">
    {{info}}
    <component-a></component-a>

</template>

<!-- 局部组件模板 -->
        <template id="ComponentA">
        <h2>我是标题</h2>
        <div>我是内容</div>
        </template>

<script>
const ComponentA ={
     template:'#ComponentA'
     }
const App ={
    template:"#my-app",
    components: { 
        ComponentA
    },
    data () {
        return {
            info:"hello world"
        }
    }
}
Vue.createApp(App).mount("#app")
</script>
</body>

四、webpack

4.1 初识webpack

webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序

1、打包bundler:webpack可以将代码进行打包

2、静态的static:最终打包的代码是静态资源

3、模块化module:webpack默认支持各种模块化开发,如ES Module、CommonJS等

脚手架是依赖于webpack的,目前前端三大框架的脚手架Vue-CLI、create-react-app、Angular-CLI都依赖于webpack。

webpack的运行依赖于Node,所以电脑上必须要有Node环境

webpack各个版本之间不能相互兼容

安装

webpack的安装目前分为两个:webpack、webpack-cli

npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装 

使用局部webpack npx webpack

注意:在真正的开发过程中,用的更多的是局部安装。一个项目有对应该项目版本的webpack。

另外:在开发项目时,只需要安装webpack的开发时依赖,不需要运行时依赖。

webpack与webpack-cli之间的联系:

1、执行webpack命令,会执行node_modules下的.bin目录下的webpack

2、webpack在执行时是依赖webpack-cli的,没有安装就报错

3、而在webpack-cli中代码执行时,也需要用到webpack进行编译和打包

4.2 webpack的基本使用

在js文件夹下创建两个函数,并用不同的规范导出

//sum.js文件// ES6语法导出
export function addNum (a,b){return a + b ;}
//mult.js文件
function mult (a,b) { return a * b}
// CommonJS的导出
module.exports = {mult}

将两个文件导入到入口文件index.js中

// 获取通过es6导出的addNum函数
import {addNum} from './js/Sum'// 获取CommonJS导出的mult函数
const {mult} = require('./js/mult')
console.log(addNum(10,20));
console.log(mult(10,20));

在index.html中引用入口文件index.js(注意需要开启服务器才能运行)

<!DOCTYPE html>
    <html lang="en">
        <head>    
        <meta charset="UTF-8">    
        <meta http-equiv="X-UA-Compatible" content="IE=edge">    
        <meta name="viewport" content="width=device-width, initial-scale=1.0">    				<title>Document</title>
        </head>
        <body>    
            
            //直接导出,浏览器识别不了    
        <!-- <script src="./index.js" type="module"></script> -->	

        //引用打包好的文件    
        <script src="./dist/main.js"></script>
        </body>
    </html>

但是报错了

原因:浏览器不能识别ES6语法,或者不能识别node语法

解决:通过webpack将入口文件打包成浏览器能够识别的js代码

webpack ./index.js -o ./dist

自定义打包命令

初始化:npm init

在package.json中定义

  "scripts": {    "build":"webpack"  },

之后就可以通过npm run build进行打包(注意:源代码必须在src文件夹下)

当执行npm run build打包时,webpack默认会到src文件夹下寻找index.js,所以src文件夹是必要的,index.js入口文件也是必要的

当然,也可以指定以哪个文件作为入口文件(可以不必是index.js),哪个文件夹作为出口文件夹(可以不必是dist)

npx webpack --entry ./src/main.js --output-path ./build			
//入口    任意文件名       出口         任意文件名

最终解决方案:自定义打包

综上所述的打包方式都是不是很方便,即使使用的是npm run build,也需要提前创建好src,并且将index.js文件创建好

所以,可以在项目中创建webpack.config.js文件,用来解决打包的路径文件问题

const path = require("path");
module.exports = {    
    entry:"./src/main.js", //入口文件的位置    
                  
    output:{        
        path:path.resolve(__dirname,'./build'), 
        //利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build        
        filename:"bundle.js" 
        // 打包好的文件名
    }} 

再次使用npm run build时,会执行webpack.config.js的代码,读取里边的信息,按照配置信息进行打包

loader配置方式

rules属性对应的值是一个数组:[Rule]

数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:

1、test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;

2、use属性:对应的值时一个数组:[UseEntry]

​ UseEntry是一个对象,可以通过对象的属性来设置一些其他属性

​ Ø loader:必须有一个 loader属性,对应的值是一个字符串;

​ Ø options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;

​ Ø query:目前已经使用options来替代;

传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);

​ loader属性: Rule.use: [ { loader } ] 的简写。

webpack打包css

webpack默认可以识别CommonJS、ES6,但是不能识别css。如果想要识别css,必须安装css-loader

1、安装

npm install css-loader -d

npm install style-loader -d

2、配置

const path = require("path");module.exports = {
    entry:"./src/main.js", //入口文件的位置
    output:{
        path:path.resolve(__dirname,'./build'),
        //利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
        filename:"bundle.js"
        // 打包好的文件名
    },
    module:{
        rules:[
            {
                test:/\.css$/, //匹配模块中以.css文件结尾的
                //用法一(语法糖)
                // loader:"css-loader",
                // 用法二:(完整写法)
                use:[
                    // 注意顺序
                    "style-loader",
                    "css-loader"
                ]
            }
        ]
    }} 

webpack打包less

局部安装less :npm install less -d

测试

// test.less
@bgColor : #abc;
@fontSize : 30px;
#box2{
    background-color:@bgColor;
    font-size:@fontSize;
}

通过npx找到module包里边lessc转换为css:npx lessc ./test.less demo.css

// demo.css#box2 {
    background-color: #abc;
    font-size: 30px;
}

通过webpack的方式就行打包less文件

1、安装less-loader: npm install less-loader -d

2、配置webpack.config.js文件

    module:{
        rules:[
            {
                test:/\.less$/, //匹配模块中的.less结尾的文件
                use:[
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ]
            }
        ]
    }

注意:webpack是从下往上执行loader的,过程为:先把less转为css,再由css转为浏览器能够识别的打包代码

4.3 webpack打包其他资源

1、打包图片资源

安装loader:npm install file-loader -d

配置

    module:{
        rules:[
            {
                test:/\.(jpg|png|jpeg|gif)/ ,
                use:[
                    "file-loader"
                ]
            }
        ]
    }

测试:

通过element.js向页面添加div元素,并设置类名为bg-img

// 创建div标签
const bgdiv = document.createElement("div")
bgdiv.className = "bg-img"
// 将bgdiv添加进body
document.body.appendChild(bgdiv)

通过css将图片作为该div的背景图

.bg-img{
    background-image: url('../img/b1.jpeg');
    width: 200px;
    height: 100px;
}

将文件都导入入口文件后进行打包,就可以看到图片被打包到了build文件夹下

如果有多种静态资源(如多个图片)都放在build文件夹下,结构会显得混乱。那么我们可以通过配置webpack的相关配置来规定路径

    module:{
        rules:[
            {
                test:/\.(jpg|png|jpeg|gif)/ ,
                use:{
                    loader:"file-loader",
                    options:{
                        // [ext]表示根据原有图片的后缀命名
                        name:'img/[name]_[hash:6].[ext]'
                        //[name]_[hash:6] 表示文件名的命名规则
                    }
                }
            }
        ]
    }

2、打包图片资源2

原理:url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。

在被打包好的文件夹中,不再能看见图片资源,默认情况下,url-loader会将所有图片资源转成base64编码

优点:占用更小的空间,有利于优化高并发

安装npm install url-loader -d

配置

{
    test:/\.(jpg|png|jpeg|gif)/ ,
        use:{
            loader:"url-loader",
                options:{
                    name:'img/[name]_[hash:6].[ext]'
                }
        }
}

问题:但是对于体积较大的图片,一般不进行打包。因为打包出来的base64编码多,造成bundle.js文件体积过大,反而也会影响加载速度。

所以一般只对体积小的图片使用用url-loader

{
    test:/\.(jpg|png|jpeg|gif)/ ,
        use:{
            loader:"url-loader",
                options:{
                    name:'img/[name]_[hash:6].[ext]',
                    limit: 100*1024 
                    //即小于100kb的图片使用url-loader进行打包,大于的不使用
                }
        }
}

3、打包图片资源3

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;

asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;

asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;

asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体

积限制实现;

4.4 Plugin 插件

plugin与loader的区别

loader适用于特定的模块类型进行转换,Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等

1、cleanWebpackPlugin

功能:每次执行打包命令时,将原有的dist文件夹删除

安装:npm install clean-webpack-plugin -d

配置:

    const {CleanWebpackPlugin} = require("clean-webpack-plugin");

    plugins:[
        new CleanWebpackPlugin()
        // CleanWebpackPlugin是一个类
    ]

2、HtmlWebpackPlugin

最终在打包的dist文件夹下根据webpack的模板生成index.html文件。

安装:npm install html-webpack-plugin -d

配置:

module.exports = {
    entry:"./src/main.js", //入口文件的位置
    output:{
        path:path.resolve(__dirname,'./build'),
        //利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build        
        filename:"js/bundle.js" 
        // 打包好的文件名
    },
        module:{
            rules:[]
        },
        plugins:[
            new CleanWebpackPlugin() ,
            // CleanWebpackPlugin是一个类
            new HtmlWebpackPlugin()
        ]} 

注:默认情况下是根据ejs模板来生成的inedx.html的,不是根据根目录下的

3、自定义html模板

如果不想使用ejs模板的index.html模板,想用自己自定义的html模板,如下vue3-cli模板

在根目录下创建public/index.html

<!DOCTYPE html>
<html lang="">
<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">    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
            <title><%= htmlWebpackPlugin.options.title %></title>
</head>

    <body>
<noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
    </strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
    </body>
</html>

所以,当我们在配置HtmlWebPackPlugin时,我们可以添加如下的配置

1、template:指定想要使用的模板所在的路径

plugins:[
    new HtmlWebpackPlugin({
        template:'模板路径'
    })
]

2、title:在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;

但是此时使用npm run build进行打包时会报错,因为模板中还使用到一个BASE_URL的常量

解决:

使用DefinePlugin

DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装)

配置

const {DefinePlugin} = require('webpack');
plugins:[
    new CleanWebpackPlugin() ,
    // CleanWebpackPlugin是一个类
    new HtmlWebpackPlugin({
        template:'./public/index.html', 
        //根据这个html模板进行打包
    }) ,
    new DefinePlugin({
        BASE_URL:"'./'"
    })
]

此时打包就不会报错了

4、CopyWebpackPlugin

在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。

因为public文件夹一般用于存放静态资源,不需要进行转换改变的资源。

这个复制的功能,我们可以使用CopyWebpackPlugin来完成;

安装:npm install copy-webpack-plugin -d

相关的配置:from:设置从哪一个源中开始复制。to:复制到的位置。globOptions:设置一些额外ed选项,如要忽略的文件

plugins:[
    new CleanWebpackPlugin() ,// CleanWebpackPlugin是一个类
    new HtmlWebpackPlugin({
        template:'./public/index.html', //根据这个html模板进行打包
    }) ,
    new DefinePlugin({
        BASE_URL:"'./'"
    }),
    new CopyWebpackPlugin({
        patterns:[
            {
                from:"public",
                to:"./",
                globOptions:{
                    ignore:[
                        "**/index.html" //除了index.html外,都复制过去
                    ]
                }
            }
        ]
    })
]

4.5 mode模式

Mode配置选项,可以告知webpack使用响应模式的内置优化:

1、默认值是production(什么都不设置的情况下---生产阶段的代码,难以阅读调试);

2、可选值有:'none' | 'development' | 'production';

module.exports = {
    //设置模式
    //development 开发阶段,会设置development
    //production 准备打包上线的时候,设置production
    mode:"development",

    // 设置source-map,建立js映射文件,方便调试代码和错误
    devtool:"source-map",
    
    
    entry:"./src/main.js", //入口文件的位置
    output:{
        path:path.resolve(__dirname,'./build'), //利用node的内置对象path的resolve方法拼接当前文件的绝对路径和 ./build
        filename:"js/bundle.js" // 打包好的文件名
    },
    module:{
        
    },
    plugins:[]
} 

4.6 Babel

在实际开发中,我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:

1、ES6+、TypeScript、开发react项目,都离不开Babel

2、学习Babel对于理解代码从编写到线上转变过程至关重要

那么,什么是Babel

Babel是一个工具链,主要用于旧浏览器或环境中将ES6+代码转换为向后兼容版本的JavaScript,包括:语法转换、源代码转换等

babel命令行的使用

安装:

npm install @babel/cli @babel/core -d

1、@babel/core:babel的核心代码,必须安装

2、@babel/cli:可以让我们在命令行使用babel

使用:

npx babel src --out-dir dist

src : 是源文件目录

--out-dir : 指定要输出的文件夹dist

babel可以指定代码中的那些语法进行转换(使用插件),也可以使用预设(preset):

安装:

npm install @babel/preset-env -d

使用:

npx babel src --out-dir dist --presets=@babel/preset-env

webpack的babel-loader

1、安装:

npm install babel-loader -d

npm install @babel/core -d babel核心代码(必要)

npm install @babel/preset-env -d 预设插件

2、配置webpack.config.js

module:{
    rules:[
        {
            test:/\.js$/, //js结尾的文件使用babel进行打包转换
            use:{
                loader:"babel-loader",
                options:{
                    presets:[
                        "@babel/preset-env"
                    ]
                }
            }
        }
    ]
}

当webpack.config.js插件或者loader太多,难以阅读维护,可以单独抽离出去

//babel.config.js文件

module.exports = {
    presets:[
        ["@babel/preset-env"]
    ]
}
//webpack.config.js文件
module:{
        rules:[
            {
                test:/\.js$/, //js结尾的文件使用babel进行打包转换
                use:"babel-loader"
            }
        ]
    }

4.7 vue

在vue的开发过程中,有三种方式来编写DOM元素:

  • 方式一:template模板的方式

  • 方式二:render函数的方式,使用h函数来编写渲染的内容

  • 通过.vue文件的template来编写模板

vue在让我们选择版本的时候分为:运行时+编译器 vs 仅运行时

1、运行时+编译器 包含了对template模板的编译代码,更加完整,但是也更大一些;

2、仅运行时,没有包含对template版本的编译代码,相对更小一些。

安装vue2:npm install vue

安装vue3:npm install vue@next

当我们向main.js中使用vue时,出现了警告

import {createApp} from 'vue' //导入vue的createApp方法
const app = createApp({
    template:`<div>这是vue渲染出来的<div>`,
    data(){
        return{
            info:"hahahah"
        }
    }
})
app.mount("#app")

由于webpack默认使用了 ‘ 仅运行时 ’ 版本的代码,不包含template的编译,所以无法渲染出来

修改import {createApp} from 'vue/dist/vue.esm-bundler'

安装npm i vue-loader@next -d 并配置

// 配置.vue文件打包
{
    test:/.\vue$/,
    loader:"vue-loader"
}

打包时报错,因为vue-loader同时依赖于另外一个包:安装npm i @vue/compiler-sfc -d

同时需要再配置一个插件,该插件存在于vue-loader的包里,可以直接配置使用

const {VueLoaderPlugin} = require("vue-loader/dist/index")

// 使用v-loader插件
new VueLoaderPlugin()

4.8 webpack搭建本地服务

每次修改代码都需要使用npm run build打包编译代码,影响效率。

为了完成自动编译,webpack提供了几种可选的方式:

1、webpack watch mode

2、webpack-dev-server(常用)

3、webpack-dev-middleware

1、webpack watch mode

webpack提供了watch模式,在该模式下,webpack依赖于图中所有的文件,只要一个发生了更新,那么代码将重新编译

开启方式一:

在导出的配置中,添加watch:true

module.exports = {
    mode:"development",
    devtool:"source-map",
    watch:true
    }

开启方式二:在启动webpack的命令中,添加--watch标识

"scripts": {
"build": "webpack --watch"
},

2、webpack-dev-server

通过配置watch的方式可以监听到文件的变化,但是事实上它本身没有自动刷新浏览器的功能,而是依赖了vscode的live-server插件

那么,如何在不适用live-serve的情况下,可以具备live reloading(实时重新加载)的功能

安装:

npm install webpack-dev-server -d

配置:

"scripts": {
"build": "webpack --watch",
"serve":"webpack serve"
},

注意:webpack-dev-server在编译之后不会写入到任何输出文件(即不进行任何打包),而是将bundle文件保留在内存中。事实上,它使用了一个名为menfs的库

另外,如果修改了webpack.confog.js里边的代码,还是要重新执行npm run serve命令

dev-sercer的配置

1、contentBase

module.exports = {
    //dev-server的配置
    devServer:{
        contentBase:"./public" 
    }
}

在不适用CopyWebpackPlugin插件的情况下,该配置意味着public文件夹下的资源也可以在浏览器中呈现。但是跟CopyWebpackPlugin相区别,contentBase只是不会将资源打包进bundle。

该配置,可以有利于开发阶段,不想将大型public下的资源进行打包,浪费时间性能时可以使用

2、模块热替换(HMR)

HMR全称是Hot Module Replacement,是指在应用程序运行的过程中,添加、替换、删除模块,而无须重新刷新整个页面

优点:

  • 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
  • 只更新变化的内容,节省开发时间
  • 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式

如何使用?

默认情况下,webpack-dev-server支持HMR,只需要开启即可

target:"web", //针对web浏览器
devServer:{
    hot:true //开启
},

为需要操作的模块进行配置

import add from './js/add'

if(module.hot) {
    module.hot.accept("./js/add.js",()=>{
        console.log("add文件热加载成功");
    })
}

这样一来,在add.js文件中更改内容,就可以进行热更新了

事实上,如果在vue中进行修改、替换、删减操作,不需要以上的操作,因为vue-loader中默认已经有HMR,开箱即用

3、host配置

host设置主机地址:默认是localhost,如果希望其他地方也可以访问,可以设置为0.0.0.0

devServer:{
    host: "0.0.0.0"
},

localhost与0.0.0.0的区别:

1、localhost本质上是一个域名,通常情况下会被解析成127.0.0.1;

2、127.0.0.1:回环地址,表达的意识是主机自己发出去的包,直接被自己接收;

​ a、正常的数据库包经历:应用层-传输层-网络层-数据链路层-物理层

​ b、回环地址:在网络层直接被获取到了,是不会经历数据链路层和物理层的

​ c、比如监听127.0.0.1时,在同一网段下的主机中,通过ip地址是不能访问的;

3、0.0.0.0 :监听IPV4上所有的地址,再根据端口找到不同的应用程序;

​ a、比如监听0.0.0.0时,在同一网段下的主机中,通过ip地址是可以访问的

4、其他配置

port

设置监听的端口,默认情况下是8080

open

more是false,设置为true会打开浏览器

compress

默认值为false,设置为true会开启静态文件打包为gzip

5、proxy

proxy是开发中非常常用的一个配置选项,它的目的是设置代理来解决跨域访问问题。

**跨域问题 **:

比如发送一个api请求时http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8000,这是候发送网络请求就会出现跨域的问题。

解决方案:将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域问题,就可以解决我们的跨域问题

案例

假设我们现在开启一个本地服务器http://localhost:8888,并向里边content请求内容.已知我们当前的服务器域名为http://localhost:8000,于是出现了跨域访问问题,导致请求数据失败

// 测试跨域问题
import axios from 'axios'

axios.get("http://localhost:8888/content").then(res=>{
    console.log(res);
})

所以我们需要通过live-server的代理来解决这个问题

module.exports = {

    devServer:{
        proxy:{
            "/api": {
                target:"http://localhost:8888",
                pathRewrite:{
                    "^/api" : ""
                }
            }
        }
    }
}

同时,发送网络请求的代码也要相应的进行修改

import axios from 'axios'

axios.get("/api/content").then(res=>{
    console.log(res);
})

这样子就可以正常访问到想要的数据了

4.9 resolve模块解析

(用于起别名,引用文件的时候省略扩展名)

resolve用于设置模块如何被解析

1、在开发中我们会有各种各样的模块依赖,这些模块可能自己编写的代码,也可能是来自第三方库

2、resolve可以帮助webpack从每个require/import语句中,找到需要引入的合适的模块代码

  • 应用一:省略文件扩展名配置
module.exports = {
    resolve:{
        extensions:['.js','.vue','.ts']
    }
}

之后引用文件由import App from './vue/App.vue'可变为 import App from './vue/App'

原理:如果导入的是一个文件,文件具有扩展名,则可以直接打包;否则,将使用resolve.extensions选项作为扩展名解析。

extensions的默认值:【'.wasm' , '.mjs' , '.js' , '.json'】

  • 应用二:起别名alias

1、当我们项目的目录结构比较深的时候,或者一个文件的路径需要.././../这种路径片段;就可以给某些常见的路径起一个别名

const path = require('path')
module.exports = {
        resolve:{
        alias: {
            "js":path.resolve(__dirname,"./src/js"),
            "@":path.resolve(__dirname,"./src/js")
        }
    }
}

之后就可以在导入文件的时候使用别名了

import add from 'js/add'
const {mult} = require("js/mult")

五、组件化开发

vscode插件:

1、volar(识别vue——官方指定)

2、vetur(识别vue)

3、Vue VSCode Snippets(代码片段)

5.1 父传子

props

1、props是在组件上注册一些自定义的attribute;

2、父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值

两种写法:

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

//子组件
<script>
    export default {
        props: ["info1","info2"]
    }
</script>

//父组件
<template>
    <nav-bar info1="传递信息一" info2="传递信息二"></nav-bar>
</template>

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

//指定类型
        props: {
            propsA:String
        }
//指定是否为必传的字符串
        props: {
            propsB:{
                type:String,
                require:true,
            }
        }
//指定类型为数值,默认值为200,父组件没传值时,显示200
        props: {	
            propsC:{
                type:Number,
                default:200
            }
        }
//当父组件传递给子组件的值是一个对象时,且有默认值时,default是一个函数。
        props: {
            obj:{
                type:Object,
                default(){
                    return {} //默认返回一个空对象
                }
            }
        }

5.2 子传父

vue3有新的写法

数组写法

//子组件
<template>
    <div>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
        <input type="text" v-model.number="num">
        <button @click="incrementN">+n</button>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                num:0
            }
        },
        // 数组写法
        emits: ["add", "sub", "addN"],
        methods: {
            increment(){
                console.log("++");
                this.$emit("add");
            },
            decrement(){
                console.log("--");
                this.$emit("sub");
            },
        //传递参数
            incrementN(){
                this.$emit("addN",this.num)
            }
        }
    }
</script>

//父组件
<template>
<div>
  {{counter}}
  <counter-click @add="addOne" @sub="subOne" @addN="incrementN"></counter-click>
</div>
</template>

对象写法

// 对象写法,可以对传递的参数进行验证
emits:{
    "add":null, //不进行验证
    "sub":null, //不进行验证
    "addN": function(payload) {
        if(payload ===10){
            return true
        }
        return false 
        //当输入的值>10时,也能传递过去,但是会报警告
    }
},

5.3 非父子组件通信

两种方式:

1、provied/inject

2、Mitt全局事件总线

方式一:Provide与Inject

有一些深度嵌套的组件,子组件想要获取父组件的部分内容,在这种情况下,如果仍然将props沿着组建链逐级传递下去,就会非常麻烦。

比如A是B的子组件,C是B的子组件,D是C的子组件,现在D想要获取A的部分内容,就可以使用Provide与Inject

//祖先(提供数据)
provide:{
    name:"linming",
    age:18
  },
      
//后代(接收数据)
inject:['name','age']  

如果Provide中提供的数据来自于data,那么应该将Provide作为一个函数来使用,才能使用正确的this

//祖先(提供数据)
  provide(){
    return{
      data:this.info, //包含data中的数据
      name:"linming",
      age:18
    }
  },
  data () {
    return {
      info:"120220"
    }
  }

//后代(接收数据)
inject:['name','age','data']

方式二:Mitt全局事件总线

vue3从实例中移除了$on,$off和$once方法,所以如果想继续使用全局事件总线,需要安装第三方库。

vue官方推荐了mitt

安装:npm install mitt

具体步骤:

1、引用mitt

// (eventBus.js)
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

2、在相互存在依赖关系的两个组件比如Home.vue、detail.vue中,实现通信

//Home.vue  触发事件
import emitter from './components/eventbus'

  <button @click="btnClick">触发</button>

  methods:{
    btnClick(){
      emitter.emit("ming",{name:"linming",age:18})
    }
  }
//detail.vue 监听事件
import emitter from './eventbus'
    export default {
        created () {
            emitter.on("ming",(info)=>{
                console.log("接收到了",info);
            })
        }
    }

父子组件的访问方式

1、父组件访问子组件:$refs

//案例
  <async-profile ref="profile"></async-profile>
  <button @click="getInfo">获取</button>

  methods:{
    getInfo(){
      console.log(this.$refs.profile.info);
    }
  }

2、子组件访问父组件:$parent $root

//子组件
        methods: {
            getParent(){
                console.log(this.$parent.currentTab);
            }
}

5.4 插槽的使用

1、基本使用

//NavBar.vue
    <div class="nav-bar">
        <div>我是左边</div>
        <slot></slot>
        <div>我是右边</div>
    </div>

//App.vue
  <nav-bar></nav-bar>

2、默认值

//NavBar.vue
    <div class="nav-bar">
        <div>我是左边</div>
        <slot>我是默认值(中间)</slot>
        <div>我是右边</div>
    </div>

//App.vue
  <nav-bar></nav-bar> //如果中间有插入新值,新值将替换默认值

假如使用nav-bar时,中间插入多个值,那么这多个值都会被替换到插槽中

//App.vue
  <nav-bar>
    <div>ahha</div>
    <span>hieehie</span>
  </nav-bar>

3、具名插槽

不指定具名插槽的话,当存在多个插槽时,会造成混乱

例如

//定义插槽
    <div class="nav-bar">
        <slot>我是左边</slot>
        <slot>我是中间</slot>
        <slot>我是右边</slot>
    </div>

//使用
  <nav-bar>
    <div>哈哈哈</div>
    <span>嘿嘿嘿</span>
  </nav-bar>
//问题:div和span两者会同时存在于3个插槽中
//定义插槽
    <div class="nav-bar">
        <slot name="left">我是左边</slot>
        <slot name="center">我是中间</slot>
        <slot name="right">我是右边</slot>
    </div>

//使用
  <nav-bar>
    <template v-slot:letf>
      <div>哈哈哈</div>
    </template>

    <template v-slot:center>
      <div>嘿嘿嘿</div>
    </template>

    <template v-slot:right>
      <span>呵呵呵</span>
    </template>
  </nav-bar>

4、v-slot的缩写

v-slot :替换为字符#

  <nav-bar>
    <template #letf>
      <div>哈哈哈</div>
    </template>

    <template #center>
      <div>嘿嘿嘿</div>
    </template>

    <template #right>
      <span>呵呵呵</span>
    </template>
    
  </nav-bar>

5、渲染作用域

1、父级模板里的所有内容都是在父级作用域中编译的

2、子模板里所有的内容都是在子作用域中编译的

//NavBar.vue
    <div class="nav-bar">
        <slot>我是左边</slot>
    </div>

//App.vue
  <nav-bar>
    <div>{{info}}</div> 
  </nav-bar>
//注意:这里的info只会在App.vue中查找data,虽然div最终是替换进slot里,但不会访问NavBar里边的内容

但是,有时候我们也想要插槽能够访问到子组件中的内容,所以有了作用域插槽

6、作用域插槽

//App.vue
  data () {
    return {
        //在父组件中定义数据,并传给子组件
      arr:['孙为空','唐僧','沙和尚','猪八戒']
    }
  }

  <slot-area :arr="arr">
    <template v-slot="slotProps">
      <span>{{slotProps.item}}-{{slotProps.index}}</span>
    </template>
  </slot-area>
//SlotArea.vue
    <template v-for="(item,index) in arr" >
         <slot :item="item" :index="index"></slot>
    </template>

        props:{
            arr:{
                type:Array,
                default(){
                    return []
                }
            }
        }

5.5 动态组件的实现

假设我们需要实现一个功能:点击一个tab-bar,切换不同的组件显示

两种思路实现:

1、通过v-if来判断,显示不同的组件

<div>
  <button v-for="item in btn" :key="item" :class="{active:currentTab == item}" @click="btnclick(item)">
    {{item}}
  </button>
  <!-- 显示 -->
  <category v-if="currentTab==='分类'"></category>
  <share v-else-if="currentTab==='分享'"></share>
  <about v-else></about>
</div>

<script>
import Category from './components/Category.vue'
import Share from './components/Share.vue';
import About from './components/About.vue'

export default {
  name: 'App',
  components: {
    Category,
    Share,
    About
  },
  data () {
    return {
      btn:['关于','分类','分享'],
      currentTab:'关于'
    }
  },
  methods:{
    btnclick(item){
      this.currentTab = item
    }
  }
}
</script>

2、动态组件的方式

动态键使用的是component 组件,通过一个特殊的attribute——is来实现

<component :is="组件"></comonent>

is后绑定的‘组件’:应是通过component函数注册过的组件,或者在组件对象的components对象中注册的组件

<component :is="currentTab"></component>

export default {
  name: 'App',
  components: {
    Category,
    Share,
    About
  },
  data () {
    return {
      btn:['About','Category','Share'],
      currentTab:'About'
    }
  },
  methods:{
    btnclick(item){
      this.currentTab = item
    }
  }
}
</script>

同时,也可以通过component标签向对应的组件传值。

keep-alive

在开发中,我们希望切换组件时,可以继续保存组件的状态,而不是销毁掉,这个时候就可以使用内置组件keep-alive

keep-alive的一些属性:

1、include——string | RegExp | Array

只有名称匹配的组件会被缓存

2、exclude——string | RegExp | Array

任何名称匹配的组件都不会被缓存

3、max——number | string

最多可以缓存多少个组件实例

  <keep-alive include="share">
      //这里的share是根据组件的name属性来确定的
    <component :is="currentTab"></component>
  </keep-alive>

对于缓存的组件来说,再次进入时,是不会执行created或者mounted等生命周期函数的:

所以为了监听到何时重新进入到组件,何时离开了组件,我们可以使用activated、deactivated

1、activated:组件处于活跃状态时触发

2、deactivated:组件不活跃时触发

5.6 vue中实现异步组件

webpack的代码分包

默认情况下,在构建整个组件树的过程中,因为组件和组件之间通过模块化直接依赖的,那么webpack在打包时就会将组件模块打包到一起(比如打包到一个app.j文件中)

但是随着项目的不断庞大,app.js文件内容过大,会造成首屏的渲染速度变慢

这个时候,对于一些不需要立即用到的组件,可以单独对其进行拆分,拆分成一些小的代码块chunk.js。这些chunk.js会在需要时,从服务器加载下来,并且运行代码。

例子

//add.js(将该文件单独打包)
export  function add(a,b) {
    return a + b;
}
//main.js(将add.js加入组件树)

import("./add.js").then(res=>{
    console.log(res.add(20,30));
})

实现异步组件

如果项目过大,某些组件我们希望通过异步的方式进行加载(目的是可以对其进行分包处理)

vue提供了一个函数:defineAsyncComponent

defineAsyncComponent接收两种类型的参数

//类型一:工厂函数
<template>
<div>
  <async-profile></async-profile>
</div>
</template>

<script>
import {defineAsyncComponent} from 'vue';
const AsyncProfile = defineAsyncComponent(()=> import('./components/Profile.vue'));

export default {
  components: {
    AsyncProfile //注册
  }
}
</script>
//类型二:接收一个对象类型,对异步函数进行配置
import {defineAsyncComponent} from 'vue';

const AsyncProfile = defineAsyncComponent({
  loader: () => import('./components/Profile.vue'),
  //其他参数
})

5.7 组件的生命周期

每个组件都可能会经历:创建、挂载、更新、写在等一系列的过程。在这个过程中的某个阶段,用于可能会想要添加一些属于自己的代码逻辑。

生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调。

评论
  • 支持 Markdown 格式
  • 评论需要登录
  • 邮箱会回复提醒(也许会在垃圾箱内)
0 /400 字