Vue 基础入门与进阶

Vue 基础入门与进阶


简介

1、IDE简介

  • IDE(Integrated Development Environment,集成开发环境)是含代码编辑、关键字高亮、智能提示、智能纠错、格式美化等功能于一身的“高级代码编辑器”
  • Visual Studio Code 简称 VS Code,来自微软公司
  • 优点:内置功能非常丰富、插件全且安装简单、轻量、有MAC版本
  • VS Code 下载:https://code.visualstudio.com/
  • VS Code 中文配置:在插件中心搜索 Chinese,安装插件即可
  • VS Code 颜色主题:在Code->首选项->颜色主题,将主题改为:浅色 + (默认浅色)
  • VS Code 集成 Sublime 的快捷键:在插件中心搜索 Sublime,安装插件即可
  • 多行编辑:按住鼠标滚轮,然后下拉,即可进行多行编辑
  • VS Code 安装 Live Server 插件,这个插件可以让“实时热更新”网页,自动刷新网页

2、在线引用 Vue

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

3、使用 vue-cli 创建项目

  • 创建项目
// front 是项目名
vue create front
  • 选择 “Manually select features” 手动选择功能(使用上下键控制上下,回车进入下一步)
    在这里插入图片描述
  • 使用空格键确定要选择的功能,一般项目选择如下图
    Choose Vue version:选择 Vue 版本
    Babel:使用 Babel
    Router:使用 Router
    Vuex:使用 Vuex
    CSS Pre-processors:使用 CSS 预编译
    Linter / Formatter:格式化代码
    在这里插入图片描述
  • 选择需要的版本
    在这里插入图片描述
  • 是否使用 history 模式的路由:N
    在这里插入图片描述
  • 选择使用 Sass/SCSS
    在这里插入图片描述
  • 使用 ESLint 作为标准配置
    在这里插入图片描述
  • 选择 Lint on save
    在这里插入图片描述
  • 选择 In dedicated config files
    在这里插入图片描述
  • 选择 N,不保存这个预设
    在这里插入图片描述
  • 出现下图则创建项目成功
  • 使用“ cd 项目名”进入项目目录下
  • 使用“npm run serve”启动项目
    在这里插入图片描述

一、Vue 基础语法

1、Vue 的基本结构

  • 使用 Vue.createApp({}) 去创建一个应用,存储到 app 变量中
  • 传入的参数表示,这个应用最外层的组件应该如何展示
  • 将 app对应的应用挂载(只作用)在 id = "root"的元素上
  • vm 代表的就是 vue 应用的根组件,其充当了 vm 层的角色
<!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>
    <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
    <div id="root"></div>
</body>
<script>
    // 使用 Vue.createApp({}) 去创建一个应用
	// 传入的参数表示,这个应用最外层的组件应该如何展示
	const app = Vue.createApp({
		data() {
			return {
				msg:"hello world!"
			}
		},
		template:`<div>{{msg}}</div>`
	});
	// 将 app对应的应用挂载(只作用)在 id = "root"的元素上
	// vm 代表的就是 vue 应用的根组件,其充当了 vm层的角色
	const vm = app.mount('#root');
</script>
</html>

2、mvvm 设计模式:

  • m ->model 数据
  • v -> view 视图
  • vm -> viewModel 视图数据连接层

3、Vue生命周期函数

  • 生命周期函数:在 某一时刻 会 自动 执行的函数
  • 在实例生成之前会自动执行的函数:beforeCreate() {},
  • 在实例生成之后会自动执行的函数:created() {},
  • 在组件内容被渲染到页面之前会自动执行的函数:beforeMount() {},
  • 在组件内容被渲染到页面之后会自动执行的函数:mounted() {},
  • 当 data 中的数据发生改变时会立即自动执行的函数:beforeUpdate (){},
  • 当 data 中的数据发生变化,同时页面重新完成更新后,会自动执行的函数:updated (){},
  • 当 Vue 实例(应用)失效时,自动执行的函数:beforeUnmount() {},
  • 当 Vue 实例(应用)失效时,且 dom 完全销毁之后,自动执行的函数:unmounted() {}
    // 使用 Vue.createApp({}) 去创建一个应用
	// 传入的参数表示,这个应用最外层的组件应该如何展示
	const app = Vue.createApp({
		data() {
			return {
				msg:"hello world!"
			}
		},
		
		// 生命周期函数
		// 在实例生成之前会自动执行的函数
		beforeCreate() {
			
		},
		// 在实例生成之后会自动执行的函数
		created() {
			
		},
		// 在组件内容被渲染到页面之前会自动执行的函数
		beforeMount() {
			
		},
		// 在组件内容被渲染到页面之后会自动执行的函数
		mounted() {
			
		},
		// 当 data 中的数据发生改变时会立即自动执行的函数
		beforeUpdate(){
			
		},
		// 当 data 中的数据发生变化,同时页面重新完成更新后,会自动执行的函数
		updated(){
			
		},
		// 当 Vue 实例(应用)失效时,自动执行的函数
		beforeUnmount() {
			
		},
		// 当 Vue 实例(应用)失效时,且 dom 完全销毁之后,自动执行的函数
		unmounted() {
			
		}
	});
	// 将 app对应的应用挂载(只作用)在 id = "root"的元素上
	// vm 代表的就是 vue 应用的根组件,其充当了 vm层的角色
	const vm = app.mount('#root');

4、模版语法讲解

  • v-html = “”:表示html标签按原html执行,不进行转义为字符串展示
<script>
    // 使用 Vue.createApp({}) 去创建一个应用
	// 传入的参数表示,这个应用最外层的组件应该如何展示
	const app = Vue.createApp({
		data() {
			return {
				msg:"<strong>hello world!</strong>"
			}
		},
		template:`<div v-html="msg"></div>`
	});
	// 将 app对应的应用挂载(只作用)在 id = "root"的元素上
	// vm 代表的就是 vue 应用的根组件,其充当了 vm层的角色
	const vm = app.mount('#root');
</script>
  • v-bind:(可简写为:“:”):表示对属性进行绑定
<script>
    
	const app = Vue.createApp({
		data() {
			return {
				disabled: true
			}
		},
		template:`<input v-bind:disabled= "disabled" />`
	});
	
	const vm = app.mount('#root');
</script>
  • v-on:(可简写为:“@”):表示对事件进行绑定
<script>
	const app = Vue.createApp({
		data() {
			return {
				message: "弹出",
			}
		},
        methods: {
            handleClick() {
                alert('click');
            }
        },
		template:`<div v-on:click="handleClick">{{message}}</div>`
	});
	
	const vm = app.mount('#root');
</script>
  • 动态参数:
    动态属性:v-bind:[自定义属性] 自定义属性:"要绑定的属性名"
    动态事件:v-on:[自定义事件] 自定义事件:"要绑定的事件名"
<script>
	const app = Vue.createApp({
		data() {
			return {
				message: "hello world!",
                show: false,
                name: 'title',
                event: 'click'
			}
		},
        methods: {
            handleClick() {
                alert('click');
            }
        },
		template:`<div :[name]="message" @[event]="handleClick">{{message}}</div>`
	});
	
	const vm = app.mount('#root');
</script>
  • v-once:表示在第一次完成渲染之后,后面再改变数据,依旧保持第一次渲染的结果,可以降低无用渲染
<script>
	const app = Vue.createApp({
		data() {
			return {
				message: "hello world!"
			}
		},
		template:`<div v-once>{{message}}</div>`
	});
	
	const vm = app.mount('#root');
</script>
  • v-if = “condition”:表示该标签的内容展不展示由v-if="值"的值决定
  • v-else:表示当 if 的条件不成立时执行(v-else 可以不加 condition)
  • v-else-if = “condition”:表示当 if 的条件不成立且 else if 的条件成立时执行
<script>
	const app = Vue.createApp({
		data() {
			return {
				message: "hello world!",
				elseMessage: "elseMessage",
                show: false
			}
		},
		template:`
			<div v-if="show">{{message}}</div>
			<div v-else>{{elseMessage}}</div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • v-show = “condition”:表示控制该标签内容的展示与否
  • v-if 和 v-show 的区别:
    v-if 是通过控制 dom 元素的存在与否来控制展示与不展示
    v-show 是通过控制 style 样式来控制展示与不展示(style=“display: none;”)
    如果频繁的改变 DOM 元素的展示与否,建议使用 v-show,性能会好一些;如果不涉及频繁销毁或创建 DOM,v-if 和 v-show 都差不多
  • v-for ="":表示对列表循环渲染
    1)普通列表的渲染:v-for="(value,index) in listArray"
    2)对象列表的渲染:v-for="(value,key,index) in listObject"
    3)在做v-for 时增加 :key="",key值尽量用唯一的,这样可以优化v-for指令,表示在第二次渲染的时候,如果Vue的底层发现某个元素的key值两次是一样的,那么Vue看看之前key值对应的dom元素能不能复用,如果能复用,就不再创建该元素

5、表单中双向绑定指令v-model 与修饰符

  • input:在使用 input 进行双向绑定时,就可以不必写 value=""
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: 'hello'
            }
        },
    
		template:`
		<div>
            {{message}}
            <input v-model="message"/>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • textarea:绑定文本域
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: 'hello'
            }
        },
    
		template:`
		<div>
            {{message}}
            <textarea v-model="message"/>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • checkbox:使用数组去存
    默认选中是 true,不选中是 false;可以使用 true-value="" false-value="" 形式去自定义选中和不选中时展示的内容
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: []
            }
        },
    
		template:`
		<div>
            {{message}}
            A <input type="checkbox" v-model="message" value="A"/>
            B <input type="checkbox" v-model="message" value="B"/>
            C <input type="checkbox" v-model="message" value="C"/>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • radio:使用字符串去存
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: ''
            }
        },
    
		template:`
		<div>
            {{message}}
            A <input type="radio" v-model="message" value="A"/>
            B <input type="radio" v-model="message" value="B"/>
            C <input type="radio" v-model="message" value="C"/>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • select:下拉选择
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: '',
                options: [
                    {value: 'A'},
                    {value: 'B'},
                    {value: 'C'},
                    {value: 'D'},
                    {value: 'E'}
                ]
            }
        },
    
		template:`
		<div>
            {{message}}
            <select v-model="message">
                <option v-for="item in options">{{item.value}}</option>    
            </select>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

v-model 修饰符:

  • lazy 修饰符:数据改变的时候不实时跟着改变,当改变完成失去焦点时才改变
  • number 修饰符:将数据转为 number 类型再存到绑定的变量
  • trim 修饰符:去除内容前后的空格
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: 'hello'
            }
        },
    
		template:`
		<div>
            {{message}}
            <input v-model.lazy="message" />
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

6、data & methods & computed & watch

  • data:可以REPL环境中通过 vm.$data.“属性” = “值” 的方式改变data 里某一个属性的值(其中 vm = app.mount(’#root’);)
    如果属性是一级属性,还可以简写为:vm.“属性” = “值” 的方式
  • methods:只要页面重新发生渲染,就会执行重新计算
  • computed:只有当计算属性依赖的内容发生变更时,才会重新执行计算(只能同步)
  • watch(侦听器):可以侦听data里面数据的变化,当数据变化后才会执行(当异步执行时可以使用)
  • methods、computed、watch 三者之间的区别与使用:
    computed 和 methods 都能实现的功能,建议使用 computed,因为有类似缓存的功能
    computed 和 watch 都能实现的功能,建议使用 computed,因为更加简洁

7、样式绑定

  • 在 Vue 里面使用 class 定义样式,可以使用字符串、对象、数组形式(记得class 前加 v-bind)
  • 字符串形式:在<style>标签中定义好样式之后,可以直接以字符串的形式引入
  • 对象形式:以对象形式时,true 表示使用,false 表示不使用
  • 数组形式:可以将需要展示的样式列在数组中
<style>
	.red {
        color: red;
    }
    .green {
        color: green;
    }
</style>
<body>
    <div id="root"></div>
</body>
<script>
	const app = Vue.createApp({
		data() {
			return {
				message: "hello world!",
                classString: 'red',
                classObject: {red: true, green: true},
                classArray: ['red', 'green']
			}
		},
        
		template:`<div :class="classObject" >{{message}}</div>`
	});
	
	const vm = app.mount('#root');
</script>
  • 在 Vue 里面使用 style 定义行内样式,可以直接在标签内使用 style=“color: yellow” 的形式使用,也可以使用字符串、对象形式(记得class 前加 v-bind)行内样式
<body>
    <div id="root"></div>
</body>
<script>
	const app = Vue.createApp({
		data() {
			return {
                styleString: 'color: red;',
                styleObject: {
                	color: 'green',
                	background: 'yellow'
                }
			}
		},
        
		template:`
		<div style="color: yellow" >hello world!</div>
		<div :style="styleString" >hello world!</div>
		<div :style="styleObject" >hello world!</div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • 父子组件间样式传递:当子组件最外层只有一个标签,样式可以写在子组件上或者父组件中调用的子组件标签上
<script>
	const app = Vue.createApp({
		data() {
			return {
                styleObject: {
                	color: 'green',
                }
			}
		},
        
		template:`
		<div :style="styleObject" >
            hello world
            // <demo />
            <demo class="red"/>
            </div>
		`
	});

    app.component('demo',{
        template:`
            // <div class="red">one</div>
            <div >one</div>
        `
    })
	
	const vm = app.mount('#root');
</script>
  • 父子组件间样式传递:
    当子组件最外层有多个标签,样式可以写在子组件上;
    或者父组件中调用的子组件标签上,由于样式写在父组件中调用的子组件标签上,导致子组件上不明确哪个标签会使用该样式,所以子组件都会使用相同的样式;如果想单独指定子组件标签的样式,可以使用 :class="$attrs.class" ,表示子组件标签的样式是父组件属性的 class 的值
<script>
	const app = Vue.createApp({
		data() {
			return {
                styleObject: {
                	color: 'green',
                }
			}
		},
        
		template:`
		<div :style="styleObject" >
            hello world
            <demo class="red"/>
            </div>
		`
	});

    app.component('demo',{
        template:`
            <div :class="$attrs.class">one</div>
            <div >two</div>
        `
    })
	
	const vm = app.mount('#root');
</script>

8、事件绑定与修饰符

事件绑定:

  • 在 Vue 里面如果想获取原生的事件的话,在函数的第一个参数默认就是原生的事件对象 event
<script>
	const app = Vue.createApp({
		data() {
			return {
                counter: 0
			}
		},
        methods: {
            handleBtnClick() {
                console.log(event);
                this.counter += 2;
            }
        },
		template:`
		<div>
            {{counter}} 
            <button @click="handleBtnClick">按钮</button>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • 当事件传有参数并且想同时获取原生的事件对象的话,那么需要的模版里使用 $event 作为参数传给事件函数
<script>
	const app = Vue.createApp({
		data() {
			return {
                counter: 0
			}
		},
        methods: {
            handleBtnClick(num, event) {
                console.log(event);
                this.counter += num;
            }
        },
		template:`
		<div>
            {{counter}} 
            <button @click="handleBtnClick(2, $event)">按钮</button>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>
  • 如果绑定事件想执行多个函数,把多个函数用逗号间隔,调用这些函数的时候不能直接写函数的引用了,需要使用圆括号
<script>
	const app = Vue.createApp({
		data() {
			return {
                counter: 0
			}
		},
        methods: {
            handleBtnClick(num) {
                console.log(this.counter += num);
            },
            handleBtnClick1(num) {
                console.log(this.counter += num);
            }
        },
		template:`
		<div>
            {{counter}} 
            <button @click="handleBtnClick(1),handleBtnClick1(2)">按钮</button>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

事件修饰符:

  • stop 修饰符:阻止事件向外冒泡
  • self 修饰符:事件的触发必须是自己的DOM标签触发的才会执行,若是子标签触发的事件不会执行
  • prevent 修饰符:阻止默认事件
  • capture 修饰符:会将事件由默认的冒泡模式转为捕获模式
  • once 修饰符:绑定的事件只执行一次
  • passive 修饰符
<script>
	const app = Vue.createApp({
		data() {
			return {
                counter: 0
			}
		},
        methods: {
            handleDivClick() {
                alert('div clicked');
            },
            handleBtnClick() {
                this.counter += 1;
            }
        },
		template:`
		<div>
            
            <div @click="handleDivClick">
                {{counter}} 
                <button @click.stop="handleBtnClick()">按钮</button>
            </div>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

按键修饰符:

  • enter 修饰符:当按下键盘的 enter 键时才会触发事件
  • tab 修饰符:当按下键盘的 tab 键时才会触发事件
  • delete 修饰符:当按下键盘的 delete 键时才会触发事件
  • esc 修饰符:当按下键盘的 esc 键时才会触发事件
  • up 修饰符:当按下键盘的 up 键时才会触发事件
  • down 修饰符:当按下键盘的 down 键时才会触发事件
  • left 修饰符:当按下键盘的 left 键时才会触发事件
  • right 修饰符:当按下键盘的 right 键时才会触发事件
  • ......
<script>
	const app = Vue.createApp({
		
        methods: {
            handleKeyDown() {
                console.log('keydown');
            }
        },
		template:`
		<div>
            <input @keydown.enter="handleKeyDown" />
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

鼠标修饰符:

  • left 修饰符:当按下鼠标的左键时才会触发事件
  • right 修饰符:当按下鼠标的右键时才会触发事件
  • middle 修饰符:当按下鼠标的滚轮键时才会触发事件
<script>
	const app = Vue.createApp({
		
        methods: {
            handleClick() {
                console.log('鼠标按键');
            }
        },
		template:`
		<div>
            <div @click.left="handleClick">鼠标按键</div>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

精确修饰符:

  • exact 修饰符:当没有这个修饰符时,只要按有对应的键就能触发事件(即按住对应的键和其它键也能触发事件,只要按有对应的键);当加上 exact 修饰符,就必须只能按住对应的键,多按其他键也不会触发事件
<script>
	const app = Vue.createApp({
		
        methods: {
            handleClick() {
                console.log('按键');
            }
        },
		template:`
		<div>
            <div @click.ctrl.exact="handleClick">按键</div>
        </div>
		`
	});
	
	const vm = app.mount('#root');
</script>

二、Vue 组件

1、组件的定义及使用、局部组件和全局组件

  • 组件的概念:将一个复杂的应用拆分成很多子组件去完成,再由许多小组件组成一个完整的应用
  • 组件的特点:具备复用性,组件内的数据是被当前组件所独享的,不会被其它同名组件共享
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: 'hello'
            }
        },
    
		template:`
		<div>
            <counter/>
            <counter/>
        </div>
		`
	});

    app.component('counter',{
        data() {
            return {
                count: 1
            }
        },
        template: `<div @click="count += 1">{{count}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 全局组件:只要定义了,随处可以使用,由于无论用不用到都会挂载在Vue上,所以性能不高,但使用起来简单,名字建议是小写字母单词,多个单词间用“-”分隔app.component (‘lgk-test’,{})
<script>
	const app = Vue.createApp({
		data() {
            return {
                message: 'hello'
            }
        },
    
		template:`
		<div>
            <counter-parent/>
            <counter-parent/>
        </div>
		`
	});

    app.component('counter-parent',{
        template: `<counter />`
    })

    app.component('counter',{
        data() {
            return {
                count: 1
            }
        },
        template: `<div @click="count += 1">{{count}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 局部组件:定义后需要用 components 注册后才能使用,性能比较高,但使用起来有些麻烦,组件名建议大写字母开头,中间使用驼峰命名,局部组件使用时,要做一个名字和组件间的映射对象,如果不写,Vue 底层也会自动尝试帮做映射
<script>
    const Counter = {
        data() {
            return {
                count: 1
            }
        },
        template: `<div @click="count += 1">{{count}}</div>`
    }
    
    const HelloWorld = {
        template: `<div>hello world</div>`
    }

	const app = Vue.createApp({
        components: {
            Counter: Counter,
            HelloWorld: HelloWorld
        },
		template:`
		<div>
            <counter-parent/>
            <counter-parent/>
            <Counter />
            <Counter />
            <HelloWorld />
            <HelloWorld />
        </div>
		`
	});

    app.component('counter-parent',{
        components: {
            Counter: Counter
        },
        template: `<Counter />`
    })
	
	const vm = app.mount('#root');
</script>

2、组件间传值及传值校验

  • 组件间传值:父组件调用子组件的标签,然后通过标签上的属性向子组件传递值,子组件通过 props 属性接收父组件传过来的内容,然后子组件就能使用
  • 静态传值:一般直接通过属性进行传值,传入的是写死的值
<script>
    
	const app = Vue.createApp({
        
		template:`
		<div>
            <test content="hello world!" />
        </div>
		`
	});

    app.component('test',{
        props: ['content'],
        template: `<div>{{content}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 动态传值:根据 data 中的值的变化使用 v-bind 动态绑定属性,然后传入到子组件中
<script>
	const app = Vue.createApp({
        data() {
            return {
                message: 'hello world!'
            }
        },
		template:`
		<div>
            <test :content="message" />
        </div>
		`
	});

    app.component('test',{
        props: ['content'],
        template: `<div>{{content}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 传值校验:子组件可以对父组件传过来的值进行校验,可以校验的类型有 String、Number、Boolean、Array、Object、Function、Symbol …
  • required 属性:值为 true 或 false,表示是否要求必须传值
  • default 属性:值为默认值,表示不传值时默认值为多少(默认值也可以为函数)
  • validator 校验:如果值为 true 则通过校验,否则不通过校验
<script>
	const app = Vue.createApp({
        data() {
            return {
                message: 123
            }
        },
		template:`
		<div>
            <test :content="message" />
        </div>
		`
	});

    app.component('test',{
        props: {
            content: {
            	type: Number,
            	// required: true,
                validator: function(value) {
            		return value < 1000;
            	},
            	default: 0
            }
        },
        
        template: `<div >{{content}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 当传有多个参数时,可以用对象的形式传,其中 v-bind=“params” 等价于 :a=“params.a” :b=“params.b” :c="params.c"
<script>
	const app = Vue.createApp({
        data() {
            return {
                params: {
                    a: 11,
                    b: 22,
                    c: 33
                }
            }
        },
		template:`
		<div>
            <!-- <test :="params" /> 等同于下面 -->
            <test v-bind="params" />
            <!-- 等价于 -->
            <test :a="params.a" :b="params.b" :c="params.c" />
        </div>
		`
	});

    app.component('test',{
        props: ['a','b','c'],
        
        template: `<div >{{a}}-{{b}}-{{c}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 在 HTML 中以 data-param 这种属性命名方式传值的时候,要使用驼峰式 dataParam 形式接
<script>
	const app = Vue.createApp({
        data() {
            return {
                param: 1232
            }
        },
		template:`
		<div>
            <test :data-param="param" />
        </div>
		`
	});

    app.component('test',{
        props: ['dataParam'],
        template: `<div >{{dataParam}}</div>`
    })
	
	const vm = app.mount('#root');
</script>

3、单项数据流的理解

  • 子组件可以使用父组件传递过来的数据,但绝对不能直接修改传递过来的数据
  • 如果子组件想要修改数据,可以将父组件传过来的值赋给子组件的某个属性,然后改变子组件的属性值从而达到修改数据的目的
<script>
	const app = Vue.createApp({
        data() {
            return {
                count: 1
            }
        },
		template:`
		<div>
            <test :count="count" />
        </div>
		`
	});

    app.component('test',{
        props: ['count'],
        data() {
            return {
                myparam: this.count
            }
        },
        methods: {
            handleClick() {
                this.myparam += 1;
            }
        },
        template: `<div @click="handleClick">{{myparam}}</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • 为什么有单项数据流:假设父组件多次使用同一个子组件时,当某个子组件改变父组件传过去的值,就会影响父组件的值,同时也会影响其它子组件的值,这就导致子组件间相互耦合

4、Non-props 属性

  • 父组件向子组件传递数据,子组件不通过 props 来接收,此时底层会把父组件传递过来的内容放在子组件最外层的 dom 标签下,变成子组件最外层 dom标签的属性
  • 若是不想子组件最外层dom标签展示传过来的数据,可以加 inheritAttrs: false 特性,表示不继承父组件传递过来的这些 Non-props 属性
<script>
	const app = Vue.createApp({
        
		template:`
		<div>
            <test msg="hello" />
        </div>
		`
	});

    app.component('test',{
        inheritAttrs: false,
        template: `<div >hello world</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • Non-props 属性一般用于对子组件样式修饰
<script>
	const app = Vue.createApp({
        
		template:`
		<div>
            <test style="color: red" />
        </div>
		`
	});

    app.component('test',{
        // inheritAttrs: false,
        template: `<div >hello world</div>`
    })
	
	const vm = app.mount('#root');
</script>
  • v-bind="$attrs" 表示把父组件传递过来的所有 props 属性都放到具有v-bind="$attrs"的这个元素上
  • 如果想得到某个Non-props特性,可以通过 :属性名="$attrs.某个属性名"获取到
<script>
	const app = Vue.createApp({
        
		template:`
		<div>
            <test style="color: red" msg1="hello" msg2="world" />
        </div>
		`
	});

    app.component('test',{
        template: `
        <div v-bind:style="$attrs.style" :msg1="$attrs.msg1">one</div>
        <div v-bind:msg1="$attrs.msg1">two</div>
        <div v-bind:msg2="$attrs.msg2">three</div>
        <div v-bind="$attrs">four</div>
        `
    })
	
	const vm = app.mount('#root');
</script>

5、父子组件间如何通过事件进行通信

  • 父组件向子组件传递参数,子组件通过触发事件改变参数:
  • 子组件方法中通过 this.$emit(‘方法名’[,params]) 向父组件触发事件(该方法名采用驼峰命名,也可以加一或多个参数)
  • 或者也可以在子组件中加:emits:[‘要触发的方法’]表示子组件向外触发事件(emits 也可以是一个对象)
  • 父组件通过 @‘方法名’=‘父组件自定义方法名’ 接受事件(此处的‘方法名’如果由多个单词组成,单词间用“-”分隔)
<script>
    const app = Vue.createApp({
        data(){
            return {
                count:1
            }
        },
        methods: {
            // handleAddOne(){
            handleAddOne(param){
                // this.count += 1
                this.count += param
            }
        },
        template:`
            <div>
                <counter :count = "count" @add-one = "handleAddOne"/>
            </div>
        `
    });
    
    app.component('counter',{
        props:['count'],
        // emits:['addOne'],
        methods: {
            handleClick(){
                // this.$emit('addOne');
                this.$emit('addOne',3);
            }
        },
        template:`
            <div @click = "handleClick">{{count}}</div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

如果父子组件存在双向绑定的时候:

  • 接收的参数必须为:modelValue 这是固定写法;
  • 触发事件的名字一定是:update:modelValue 也是固定写法;
  • 如果想改变接收参数的名字,把modelValue 改为‘自定义名称’,则把传入参数改为:v-model:自定义名称;
  • v-model=“属性” 绑定属性的形式只能使用一次,原因是只有一个 modelValue;若想使用多次,必须改为 v-model:自定义名称1=“属性” v-model:自定义名称2=“属性” 的形式,然后子组件就可以使用 props: [‘自定义名称1’, ‘自定义名称2’] 的方式接受参数
<script>
    const app = Vue.createApp({
        data(){
            return {
                count:1
            }
        },
        
        template:`
            <div>
                <!-- <counter v-model='count'/> -->
                <counter v-model:app='count'/>
            </div>
        `
    });
    
    app.component('counter',{
        // props:['modelValue'],
        props:['app'],
        methods: {
            handleClick(){
                // this.$emit('addOne');
                // this.$emit('update:modelValue',this.modelValue + 3);
                this.$emit('update:app',this.app + 3);
            }
        },
        template:`
            <!-- <div @click = "handleClick">{{modelValue}}</div> -->
            <div @click = "handleClick">{{app}}</div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

6、插槽、具名插槽及作用域插槽解决组件内容传递问题

  • 插槽slot:如果父组件想向子组件传一些 dom 节点或元素标签,可以直接把这些元素标签写在组件标签里就可以了,子组件通过<slot></slot>标签调用传过来的元素标签即可。
  • slot 是不能直接绑定事件的,但可以在外层包一个<span></span>标签,然后在标签里绑定事件
  • 插槽里不仅可以传标签,还可以传字符串、子组件
<script>
    const app = Vue.createApp({
       
        template:`
            <div>
                <myform>
                    <div>提交</div>
                </myform>
                <myform>
                    <button>提交</button>
                </myform>
            </div>
        `
    });
    
    app.component('myform',{
        methods: {
            handleClick(){
                alert(123)
            }
        },
        template:`
            <input />
            <span @click="handleClick">
                <slot></slot>
            </span>
        `
    });
    
    const vm = app.mount("#root");
</script>

slot中使用的数据作用域的问题:

  • 父模版里调用的数据属性,使用的都是父模版里的数据
  • 子模版里调用的数据属性,使用的都是子模版里的数据

具名插槽:

  • 将插槽拆成几个部分,每个部分在子组件分开调用
  • 在父组件中用<template v-slot:自定义名称>内容</template>的方式定义小块(其中 “v-slot:” 可以用“#”替换)
  • 在子组件用<slot name=“自定义名称”></slot>的方式接收
<script>
    const app = Vue.createApp({
        template:`
        <layout>
            <template v-slot:header>
                <div>header</div>
            </template>
            <template v-slot:footer>
                <div>footer</div>
            </template>
        </layout>
        `
    });
    
    app.component('layout',{
        template:`
        <div>
            <slot name="header"></slot>
            <div>content</div>
            <slot name="footer"></slot>
        </div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

作用域插槽:

  • 作用域插槽解决了:当父组件需要使用子组件的数据时,可以通过作用域插槽将子组件的数据传给父组件使用
  • 子组件在 <slot> 标签内绑定属性进行参数传递,父组件在使用子组件时使用 v-slot="" 接收参数
<script>
    const app = Vue.createApp({
        template:`
            <test v-slot="slotProps">
                <div>{{slotProps.value}}-{{slotProps.index}}</div>
            </test>
            <!-- 也可以用解构的形式 -->
            <!-- 
            <test v-slot="{value}">
                <div>{{value}}</div>
            </test>
            -->
        `
    });
    
    app.component('test',{
        data() {
            return {
                lists: [1,2,3]
            }
        },
        template:`
        <div>
            <slot v-for="(item,index) in lists" :value = "item" :index="index" />
        </div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

7、动态组件和异步组件

  • 动态组件:根据数据变化,结合 <component :is=“切换条件值” /> 这个标签来实现组件动态切换
  • 若是要保存切换前的数据到切换后还存在,可以利用<keep-alive> <component :is=“切换条件值” />包裹
<script>
    const app = Vue.createApp({
        data() {
            return {
                currentItem: 'input-item'
            }
        },
        methods: {
            handleClick() {
                if(this.currentItem === 'input-item') {
                    this.currentItem = 'common-item';
                } else {
                    this.currentItem = 'input-item';
                }
            }
        },
        template:`
            <keep-alive>
                <component :is="currentItem" />
            </keep-alive>
            <!-- <component :is="currentItem" /> 等价于
            <input-item v-show="currentItem === 'input-item'" />
            <common-item v-show="currentItem === 'common-item'" /> 
            -->
            <button @click="handleClick">切换</button>
        `
    });
    
    app.component('input-item',{
        
        template:`
            <div>
                <input />
            </div>
        `
    });

    app.component('common-item',{
        
        template:`
        <div>hello world</div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>
  • 异步组件:异步执行某些组件的逻辑
<script>

    const app = Vue.createApp({
        
        template:`
            <common-item />
            <async-common-item />
        `
    });
    
    app.component('async-common-item',Vue.defineAsyncComponent(() => {
        return new Promise((resolve,reject) => {
            setTimeout(() => {
                resolve({
                    template: `<div>this is a async component</div>`
                });
            },5000)
        })
    }));

    app.component('common-item',{
        
        template:`
        <div>hello world</div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

8、基础知识查缺补漏

  • v-once: 让某个元素标签值渲染一次
  • ref: 获取 dom 节点或者组件引用的一个语法。可以在想要获取的 dom标签上加 ref=“自定义名称”,然后等待挂载结束在mounted(){this.$refs.自定义名称}获取到这个dom节点
  • provide/inject:多级组件传值
    假设父组件要传值到孙组件,且子组件不需要这个值,这时可以在父组件定义一个provide:{变量:值}存放要传的值,然后在孙组件用inject:[‘变量’]接收
    如果想要传的是data里面的参数值,可以将provide定义为函数的形式:
provide(){
		return {
			变量: this.‘data里的变量名’
		}
	}

但是这种方式传值只能执行一次,并不是双向绑定的(也就是会说,如果父组件的值改变了,孙组件的不会变)

三、Vue 实现动画

过渡: 缓慢变化的过程称为过渡效果
动画: 由一个效果迅速转换为另一个效果

四、Vue 的高级语法

1、mixin 混入的基础语法

  • 组件 data、methods 优先级高于 mixin 里 data、methods 的优先级
  • 生命周期函数,先执行 mixin 里面的,再执行组件里面的
  • 全局的 mixin 不用 mixins: [mixin] 形式引入就能直接使用,但不建议使用全局的 mixin
<script>
    const myMixin = {
        data() {
            return {
                number: 2
            }
        },
        created() {
            console.log('mixin created');
        },
        methods: {
            handleClick() {
                this.number += 1;
                console.log('mixin: ' + this.number);
            }
        },
    };

    const app = Vue.createApp({
        data() {
            return {
                number: 1
            }
        },
        created() {
            console.log('created');
        },
        methods: {
            handleClick() {
                this.number += 1;
                console.log('component: ' + this.number);
            }
        },
        mixins: [myMixin],
        template:`
            
            <div @click="handleClick">{{number}}</div>
        `
    });
    // // 全局的 mixin
    // app.mixin({
    //     data() {
    //         return {
    //             number: 2,
    //             count: 22
    //         }
    //     },
    //     created() {
    //         console.log('mixin created');
    //     },
    //     methods: {
    //         handleClick() {
    //             this.number += 1;
    //             console.log('mixin: ' + this.number);
    //         }
    //     }
    // });
    
    const vm = app.mount("#root");
    
</script>
  • 自定义属性(如下例中的 number),组件中的属性优先级高于 mixin 属性的优先级
  • 由于自定义属性不属于 data、methods、computed等,所以需要使用 “this.$options.自定义属性” 方式调用
  • 也可以通过下例中的方式使 mixin 中自定义属性 number 的优先级高于 组件 中自定义属性 number 的优先级
<script>
    const myMixin = {
        number: 1
    };

    const app = Vue.createApp({
        number: 2,
        mixins: [myMixin],
        template:`
            <div >{{this.$options.number}}</div>
        `
    });

	// 可以通过这种方式使 mixin 中自定义属性 number 的优先级高于 组件 中自定义属性 number 的优先级
    // app.config.optionMergeStrategies.number = (mixinValue,appValue) => {
    //     return mixinValue || appValue;
    // };
    
    const vm = app.mount("#root");
    
</script>

2、实现 Vue 自定义指令(directive)

  • 定义局部指令时需要使用 directives 进行注册指令,类似于使用 components 注册组件
  • 自定义指令如果接收参数,可以使用生命周期函数的第二个参数(binding,第一个参数是:el)接收,然后通过 binding.value 就可以获取到使用指令时传入的值
  • 如果指令绑定某个属性(如:v-focus:hello=“value”),可以通过 binding.arg 获取到属性 hello
  • 一般使用 mounted 生命周期函数,原因是当挂载好之后才开始起作用(也可以使用其它生命周期函数,具体情况具体分析)
  • 定义全局指令
<script>
    // 全局指令
    const app = Vue.createApp({
        template:`
            <div >
                <input v-focus />
           </div>
        `
    });
    app.directive('focus',{
        mounted(el) {
            el.focus();
        }
    });
    
    const vm = app.mount("#root");
    
</script>
  • 定义局部指令
<script>
    // 局部指令
    const lgkFocus = {
        focus: {
            mounted(el) {
                el.focus();
            }
        }
    };
    const app = Vue.createApp({
        directives: lgkFocus,
        template:`
            <div >
                <input v-focus />
           </div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

3、teleport 传送门功能

  • 使用 teleport 标签可以将该标签下的 dom 元素直接传送到其它位置进行展示
<body>
    <div id="root"></div>
    <div id="hello"></div>
</body>
<script>
    
    const app = Vue.createApp({
        data() {
            return {
                message: 'hello world'
            }
        },
        template:`
            <div >
                <div>原来的</div>
                <teleport to="#hello">
                    <div>{{message}}</div>    
                </teleport>
           </div>
        `
    });
    
    const vm = app.mount("#root");
    
</script>

4、render 函数

5、插件(plugin)的定义和使用

  • plugin 插件也就是把一些通用的功能封装起来
  • 插件的定义:必须有 install() 方法,该方法里面有两个参数,第一个参数是 Vue 的实例,第二个参数是使用插件传入的参数对象
  • 插件是使用:通过 Vue 的实例打点调用 use() 方法,其中 use() 方法的第一个参数是插件的名字,第二个参数是将要传的实参
<script>
    const myPlugin = {
        install(app,options) {
            app.provide('name','hello lgk');
            app.directive('lgk-focus', {
                mounted(el) {
                    el.focus();
                }
            });
            app.config.globalProperties.$sayHello = 'hello world!';
        }
    };

    const app = Vue.createApp({
        
        template:`
            <lgk-test />
            <input v-lgk-focus />
        `
    });

    app.component('lgk-test',{
        inject: ['name'],
        mounted() {
            console.log(this.$sayHello);
        },
        template: `
            <div>{{name}}</div>
        `
    });

    app.use(myPlugin, {name: 'lgk'});
    
    const vm = app.mount("#root");
    
</script>

五、Composition API

1、Setup 函数的使用

  • setup() 函数在 created 实例被完全初始化之前执行
  • setup() 函数中 return 出去的内容会被暴露在外部,在外部的 template 模版里可以直接使用暴露出来的内容
  • setup() 函数中不能使用其外部的东西,但它外部却可以使用它 return 出去的内容
<script>
    const app = Vue.createApp({
        setup(props,context) {
            return {
                message: 'hello world'
            }
        },
        template:`
            <div>{{message}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>

2、ref、reactive、toRefs 响应式引用的用法和原理

  • 响应式引用的原理:通过 proxy 对数据进行封装,当数据变化时,触发模版等内容的更新
  • ref 处理基础类型的数据
  • 在使用 ref 处理数据时,将新值赋值给变量必须赋给的是 变量.value
  • reactive 处理非基础类型的数据
  • 在使用 reactive 处理数据时,对象类型的直接赋值给对应的对象属性,数组类型的直接赋值给对应的索引位置
<script>

    const app = Vue.createApp({
        
        setup(props,context) {
            // const {ref} = Vue;
            // let message = ref('message');
            // // 使用 proxy 会将 'message' 变成 proxy({value: 'message'}) 这样的一个响应式引用
            // setTimeout(() => {
            //     message.value = 'hello world';
            // },3000);
            // return {
            //     message
            // }
            const {reactive} = Vue;
            let message = reactive({name: 'zhangsan', age: 12, sex: 'male'});
            // 使用 proxy 会将 {name: 'zhangsan', age: 12, sex: 'male'} 变成 proxy({name: 'zhangsan', age: 12, sex: 'male'}) 这样的一个响应式引用
    
            setTimeout(() => {
                message.name = 'hello world';
                message.age = 18;
            },3000);
            return {
                message
            }
        },
        template:`
            <div>{{message.name}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>
  • readonly 表示将某个属性变为只读,不能被修改
<script>

    const app = Vue.createApp({
        
        setup(props,context) {
            
            const {reactive, readonly} = Vue;
            let message = reactive({name: 'zhangsan', age: 12, sex: 'male'});
            const messageCopy = readonly(message.name);
            setTimeout(() => {
                message.name = 'hello world';
                message.age = 18;
            },3000);
            return {
                message
            }
        },
        template:`
            <div>{{message}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>
  • 如果想将值以解构的方式返回出去,可以使用 toRefs
<script>

    const app = Vue.createApp({
        
        setup(props,context) {
            
            const {reactive, toRefs} = Vue;
            let message = reactive({name: 'zhangsan', age: 12, sex: 'male'});
            setTimeout(() => {
                message.name = 'hello world';
                message.age = 18;
            },3000);
            // toRefs 原理:proxy 会将 proxy({name: 'zhangsan', age: 12, sex: 'male'}) 
            // 变为 {name: proxy({value: 'zhangsan'}), age: proxy({value: 12}), sex: proxy({value: 'male'})} 的形式
            const {name,age,sex} = toRefs(message);
            return {
                message,name,age,sex
            }
        },
        template:`
            <div>{{message.name}}-{{name}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>

3、toRef 和 setup()函数中的 context 参数

  • toRef 相比于 toRefs 可以在响应式对象中添加默认响应式引用
  • 如果对象里没有的属性,又可能出现该属性,想将该属性具备响应式特性时,可以使用 toRef
<script>

    const app = Vue.createApp({
        
        setup(props,context) {
            
            const {reactive, toRef} = Vue;
            let message = reactive({name: 'zhangsan', age: 12});
            const sex = toRef(message,'sex');
            return {
                sex
            }
        },
        template:`
            <div>{{sex}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>
  • setup()函数中的 context 参数:context 里面有 3 个值,分别为:attrs、slots、emit
  • attrs 指的是父组件传递到子组件的 Non-props 属性
  • slots 指的是插槽
  • emit 指的是子组件触发事件让父组件去完成对应的方法
<script>
    const app = Vue.createApp({
        methods: {
            handleChangeClick() {
                alert('handleChangeClick');
            }
        },
        template:`
            <div>
                <child @change="handleChangeClick">
                    content
                </child>
            </div>
        `
    });

    app.component('child',{
        template:`
            <div @click="handleClick">123</div>
        `,
        setup(props, context) {
            const {attrs, slots, emit} = context;

            // console.log(attrs.param);

            // const {h} = Vue;
            // console.log(slots.default());
            // return () => h('div',{},slots.default())

            function handleClick() {
                emit('change');
            };
            return {
                handleClick
            }
        }

    });

    const vm = app.mount("#root");
    
</script>

4、computed 方法生成计算属性

  • Composition API 中 computed 的使用和之前的差不多,只不过需要从 Vue 中引入
<script>
    const app = Vue.createApp({
        setup() {
            const {reactive, computed} = Vue;
            let countObj = reactive({count: 0});
            // let countComputed = computed(() => {
            //     return countObj.count + 5;
            // });
            let countComputed = computed({
                get: () => {
                    return countObj.count + 5;
                },
                set: () => {
                    countObj.count = 12;
                }
            });
           
            const handleClick = () => {
                countObj.count += 1;
                // countObj.count = 12;
            };
            return {countComputed,countObj,handleClick}
        },
        
        template:`
            <div @click="handleClick">
                {{countObj.count}}---- {{countComputed}}
            </div>
        `
    });

    const vm = app.mount("#root");
    
</script>

5、watch 和 watchEffect 的使用和差异性

watch 侦听器:

  • 具备一定的惰性 lazy,即首次加载的时候不执行,只有在输入新内容的时候才会执行
  • watch 侦听器函数有两个参数,第一个是要侦听的属性,第二个参数是回调函数
  • watch 也可以变为非惰性,可以在 watch 的第三个参数中将 immediate 设置为 true
watch(要侦听的数据,回调函数,{immediate: true})
  • 回调函数参数可以拿到当前值和原始值,回调函数中第一个参数是当前值,第二个参数是原始值
  • 当侦听的是 reactive 这种数据,watch 的第一个参数要写为函数的形式
<script>
    const app = Vue.createApp({  
        setup() {
            const {watch} = Vue;
            const {ref,reactive,toRefs} = Vue;
            // const msg = ref('');
            // watch(msg,(currentValue,prevValue) => {
            //     console.log(currentValue,prevValue);
            // });
            const message = reactive({msg: ''})
            const {msg} = toRefs(message);
            watch(() => message.msg,(currentValue,prevValue) => {
                console.log(currentValue,prevValue);
            });
            return {
                msg
            }
        },
        
        template:`
            <div>
                <div>
                    输入:<input v-model="msg" />    
                </div>    
                <div>
                    输入的内容为:{{msg}}    
                </div>
            </div>
        `
    });

    const vm = app.mount("#root");
    
</script>
  • watch 侦听器不仅可以侦听单个参数,也可以侦听多个参数,只不过多个参数要放在数组中
  • watch 可以侦听多个数据的变化,用一个侦听器承载
<script>
    const app = Vue.createApp({  
        setup() {
            const {watch} = Vue;
            const {ref,reactive,toRefs} = Vue;
            // const msg = ref('');
            // watch(msg,(currentValue,prevValue) => {
            //     console.log(currentValue,prevValue);
            // });
            const message = reactive({msg: '',hello: ''})
            const {msg,hello} = toRefs(message);
            watch([() => message.msg,() => message.hello],([currentMsgValue,currentHelloValue],[prevMsgValue,prevHelloValue]) => {
                console.log(currentMsgValue,prevMsgValue,'====',currentHelloValue,prevHelloValue);
            });
            return {
                msg,
                hello
            }
        },
        
        template:`
            <div>
                <div>
                    输入:<input v-model="msg" />    
                </div>    
                <div>
                    输入的内容为:{{msg}}    
                </div>
                <div>
                    输入:<input v-model="hello" />    
                </div>    
                <div>
                    输入的内容为:{{hello}}    
                </div>
            </div>
        `
    });

    const vm = app.mount("#root");
    
</script>

watchEffect 侦听器:

  • watchEffect 立即执行,没有惰性
  • 如果侦听的数据没有变化,则不执行
  • 不需要传递需要侦听的内容,会自动感知代码依赖
  • 不需要传递很多参数,只需要传递一个回调函数
  • watchEffect 不能获取数据之前的值
<script>
    const app = Vue.createApp({  
        setup() {
            const {watch,watchEffect} = Vue;
            const {ref,reactive,toRefs} = Vue;
            // const msg = ref('');
            // watch(msg,(currentValue,prevValue) => {
            //     console.log(currentValue,prevValue);
            // });
            const message = reactive({msg: '',hello: ''})
            const {msg,hello} = toRefs(message);
            // watch([() => message.msg,() => message.hello],([currentMsgValue,currentHelloValue],[prevMsgValue,prevHelloValue]) => {
            //     console.log(currentMsgValue,prevMsgValue,'====',currentHelloValue,prevHelloValue);
            // });
            watchEffect(() => {
                console.log(message.msg);
                console.log(message.hello);
            });
            return {
                msg,
                hello
            }
        },
        
        template:`
            <div>
                <div>
                    输入:<input v-model="msg" />    
                </div>    
                <div>
                    输入的内容为:{{msg}}    
                </div>
                <div>
                    输入:<input v-model="hello" />    
                </div>    
                <div>
                    输入的内容为:{{hello}}    
                </div>
            </div>
        `
    });

    const vm = app.mount("#root");
    
</script>

6、生命周期函数的新写法

  • Composition API 的生命周期函数中没有原来 beforeCreate 和 Created 对应的生命周期函数,原因是 setup() 函数执行的时间点就在 beforeCreate、Created 两个生命周期函数之间,如果想写在这两个生命周期函数中的内容可以之间写在 setup() 函数中即可
  • beforeMount 等价于 onBeforeMount
  • mounted 等价于 onMounted
  • beforeUpdate 等价于 onBeforeUpdate
  • updated 等价于 onUpdated
  • beforeUnmount 等价于 onBeforeUnmount
  • unmounted 等价于 onUnmounted
  • Composition API 新增了两个生命周期函数:onRenderTracked 和 onRenderTriggered
  • onRenderTracked 是在每次渲染后收集响应式依赖会自动执行的函数
  • onRenderTriggered 每次触发页面重新渲染时会自动执行的函数
<script>
    const app = Vue.createApp({
        // beforeMount  <=> onBeforeMount
        // mounted  <=> onMounted
        // beforeUpdate  <=> onBeforeUpdate
        // updated  <=> onUpdated
        // beforeUnmount  <=> onBeforeUnmount
        // unmounted  <=> onUnmounted
        setup() {
            const {onBeforeMount, onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,onRenderTracked,onRenderTriggered} = Vue;
            const {ref} = Vue;
            const msg = ref('hello');
            onBeforeMount(() => {
                console.log("onBeforeMount");
            });
            onMounted(() => {
                console.log("onMounted");
            });
            onBeforeUpdate(() => {
                console.log("onBeforeUpdate");
            });
            onUpdated(() => {
                console.log("onUpdated");
            });
            onBeforeUnmount(() => {
                console.log("onBeforeUnmount");
            });
            onUnmounted(() => {
                console.log("onUnmounted");
            });
            onRenderTracked(() => {
                console.log("onRenderTracked");
            });
            onRenderTriggered(() => {
                console.log("onRenderTriggered");
            });
            const handleClick = () => {
                msg.value = 'message'
            }
            return {
                msg,
                handleClick
            }
        },
        
        template:`
            <div @click="handleClick">hello world!---{{msg}}</div>
        `
    });

    const vm = app.mount("#root");
    
</script>

7、provide、inject、模版 ref 的用法

  • provide 和 inject 搭配使用,常用于跨多层组件之间的传值
  • provide 的参数是键值对的形式,且键值对用逗号分隔(值也可以是函数)
  • inject 的参数是传过来的 key,也可以设置第二个参数为默认值
<script>
    const app = Vue.createApp({
        setup() {
            const {provide} = Vue;
            provide('key','value');
            
            return {}
        },
        
        template:`
            <child />
        `
    });
    app.component('child',{
        template: `
            <child-child />
        `
    });
    app.component('child-child',{
        template: `
            <div>{{test}}</div>
        `,
        setup() {
            const {inject} = Vue;
            const test = inject('key','默认值');
            // const test = inject('key1','默认值');
            return {
                test
            }
        }
    });

    const vm = app.mount("#root");
    
</script>
  • Composition API 的语法下获取真实的 DOM 元素节点
  • const hello = ref(null); 是固定写法,表示创建一个空的响应式对象,其中的 hello 是模版中 ref 的值
  • 模版中的 ref 表示的是获取 DOM 节点的引用,ref(null) 中的 ref 表示的是获取响应式的引用
<script>
    const app = Vue.createApp({
        setup() {
            const {ref, onMounted} = Vue;
            const hello = ref(null);
            onMounted(() => {
                console.log(hello.value);
            })
            
            return {
                hello
            }
        },
        
        template:`
            <div ref="hello">hello world</div>
        `
    });

    const vm = app.mount("#root");
    
</script>

六、Vue 项目开发配套工具

1、Vue Cli 的使用和单文件组件

(1)安装node.js环境

(2)基于node.js安装淘宝镜像

  • 在 node 环境终端安装 nrm,然后再使用淘宝镜像源
192:~ $ node -v
v15.12.0
192:~ $ npm -v
7.6.3
192:~ $ npm install nrm -g
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated mkdirp@0.3.5: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated coffee-script@1.7.1: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)

added 61 packages, and audited 315 packages in 1m

14 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice 
npm notice New minor version of npm available! 7.6.3 -> 7.12.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v7.12.1
npm notice Run npm install -g npm@7.12.1 to update!
npm notice 
192:~ $ nrm ls

* npm -------- https://registry.npmjs.org/
  yarn ------- https://registry.yarnpkg.com/
  cnpm ------- http://r.cnpmjs.org/
  taobao ----- https://registry.npm.taobao.org/
  nj --------- https://registry.nodejitsu.com/
  npmMirror -- https://skimdb.npmjs.com/registry/
  edunpm ----- http://registry.enpmjs.org/

192:~ $ nrm use taobao
                        

   Registry has been set to: https://registry.npm.taobao.org/

192:~ $ nrm ls

  npm -------- https://registry.npmjs.org/
  yarn ------- https://registry.yarnpkg.com/
  cnpm ------- http://r.cnpmjs.org/
* taobao ----- https://registry.npm.taobao.org/
  nj --------- https://registry.nodejitsu.com/
  npmMirror -- https://skimdb.npmjs.com/registry/
  edunpm ----- http://registry.enpmjs.org/

192:~ $ 
  • 或者直接使用命令安装淘宝镜像源
npm install -g cnpm --registry=https://registry.npm.taobao.org

(3)删除 vue-cli 并安装 @vue/cli

  • 删除老版本脚手架,Vue 2.x 版本脚手架使用的是:vue-cli 形式
npm uninstall vue-cli -g
  • 如果本地有 yarn,还需运行如下命令
yarn global remove vue-cli
  • 开始安装最新版 vue-cli,Vue 3.x 版本脚手架使用的是:@vue/cli 形式
npm install -g @vue/cli
  • 检查安装是否成功(查看安装版本)
vue -V

(4)利用脚手架创建新项目

  • 进入项目所要安装目录下,然后执行如下命令创建项目
vue create demo
  • 运行项目,首先先进入 demo 文件中,然后就可以运行项目了
cd demo
// 注意是 serve 不是 server
npm run serve

2、Vue-Router 的理解和使用

  • 路由是指根据 URL 的不同,展示不同的内容
  • router-link 是跳转路由的标签
<router-link to="/">home</router-link>
<router-link to="/about">About</router-link>
  • router-view 负责展示当前路由对应的组件内容
<router-view />
  • 异步加载路由
{
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */'../views/home/Home')
  }
  • 路由守卫实现基础登陆校验功能
  • 每一个路由项下面都可以加 beforeEnter() 函数,这个函数在进入这个路由之前会被执行
  • router 上整体可以加一个 beforeEach() 函数,这个函数会在路由每次进行切换的时候执行
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/home/Home'
import Login from '../views/login/Login'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
    beforeEnter(to, from, next) {
      // const isLogin = localStorage.isLogin
      // if (isLogin) {
      //   next({ name: 'Home' })
      // } else {
      //   next()
      // }
      const { isLogin } = localStorage
      isLogin ? next({ name: 'Home' }) : next()
    }
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  // console.log(to, from)
  // const isLogin = localStorage.isLogin
  // if (isLogin || to.name === 'Login') {
  //   next()
  // } else {
  //   next({ name: 'Login' })
  // }
  const { isLogin } = localStorage;
  (isLogin || to.name === 'Login') ? next() : next({ name: 'Login' })
})

export default router

  • 在 vue-router 里面提供一个 useRouter() 方法,可以通过该方法获取 router 实例
  • 如果在 JS 代码里面想做路由的一些跳转,可以通过路由实例的 push() 方法(router.push())实现跳转到下一个页面
<script>
import { useRouter } from 'vue-router'

export default {
  name: 'Login',
  setup() {
    const router = useRouter()
    const handleLogin = () => {
      localStorage.isLogin = true
      router.push({ name: 'Home' })
    }
    return {
      handleLogin
    }
  }
}
</script>

3、VueX 的语法详解

  • VueX 是数据管理框架
import { createStore } from 'vuex'

export default createStore({
  state: {
      message: 'hello'
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
  • VueX 中 state 创建了全局唯一的仓库,用来存放全局的数据
  • 组件中使用 state 中的 数据
{{this.$store.state.message}}

修改 VueX 中全局数据的步骤:

  • 第一步,通过 dispatch 方法派发一个方法(change)到 actions
this.$store.dispatch('change');
  • 第二步,actions 感知到 change 这个方法,执行 store 中 actions 下面的 change 方法
  • 第三步,commit 提交一个叫做 change 方法触发 mutations
actions: {
	change() {
		this.commit('change');
	}
},
  • 第四步,mutation 感知到提交的 change 改变,执行 change 方法
  • 第五步,在 mutations 中的 change 方法中 改变数据
mutations: {
	change() {
		this.state.message = 'hello world';
	}
},
  • 对于同步执行的数据,可以不用 dispatch 派发方法到 actions,可以直接 commit 提交到 mutations 去执行修改数据
this.$store.commit('change');
  • 在 VueX ,默认 mutations 里面只允许写同步代码,不允许写异步代码,如果想写异步代码,可以写在 actions 里面
  • 在 actions 和 mutations 中的方法若是有参数,actions 里面方法的第一个参数是 store,第二个才是传入的参数;mutations 里面方法的第一个参数是 state,第二个参数才是传入的参数
mutations: {
	change(state,param) {
		state.message = param;
	}
},
actions: {
	change(store,param) {
		store.commit('change',param);
	}
},

4、在 Composition API 中使用 VueX

<script>

import {useStore} from 'vuex';
import {toRefs} from 'vue';

export default {
  name: 'Home',
  setup() {
    const store = useStore();
    // const message = store.state.message;
    const {message} = toRefs(store.state);
    return {
      message
    }
  } 
}
</script>

5、使用 axios 发送 Ajax 请求

  • 首先安装 axios
npm install axios --save

// yarn add axios
  • 然后就可以引用
import axios from 'axios';

6、基础样式集成及开发模拟器的使用

(1)统一不同浏览器之间的显示差异

  • 安装 normalize.css
npm install normalize.css --save
  • 在 main.js 中引入 normalize.css,并且在 src 目录下创建一个 style/base.scss 文件
import 'normalize.css' 
import './style/base.scss'
// base.scss 文件
// 1 rem = html fontsize

html {
    font-size: 100px;
}

(2)在 scss 环境下提取公共样式

  • 在 src 目录下创建一个 style/viriables.scss 文件,文件中的公告样式名前加 $ 符
// 例如
$content-fontcolor: #333;
$content-bgColor: #f1f1f1;
$search-bgColor: #F5F5F5;
  • 在需要使用的文件中的 <style> 标签中使用 @import 引入样式即可
<style lang="scss" scoped>
@import '../style/viriables.scss';

// 样式
</style>

七、项目目录结构

在这里插入图片描述

  • node_modules:用于存放依赖包,如果被删除了,可以在终端控制台输入:npm install 进行安装
  • index.html:整个项目的默认模版
  • favicon.ico:用于显示在浏览器页面 tab 栏,在 index.html 文件中的 <head>标签中引用
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
  • .browserslistrc:用于配置浏览器相关的配置
> 1%     // 打包过程尽量支持全球范围内用户使用量大于 1% 的浏览器
last 2 versions    // 支持最新两个版本浏览器
not dead        // 仍在维护的浏览器
  • .editorconfig:用于配置编辑器的默认配置
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2  // 使用 tab 键时使用2个空格作为 tab 的间距
trim_trailing_whitespace = true  // 去除多余的空白
insert_final_newline = true  // 在每个文件的末尾创建新的一行
  • .eslintrc.js:使用 Eslint 时的相关配置
  • babel.config.js:主要用于配置 vue 里面用到 Babel 的配置
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ]
}
  • package-lock.json:保证多人共同协作开发时,反复安装依赖使用固定版本
  • package.json:用于放置项目配置相关的依赖
  • README.md:项目描述性的文本内容
  • src 是项目源代码目录
  • main.js:是整个项目的入口文件
  • App.vue 组件:
  • assets 目录:用于放一些静态文件
  • component 目录:放一些公共的组件
  • router 目录:定义路由相关的配置
  • store 目录:放置 VueX 相关的数据
  • views 目录:放置页面级别的组件

八、虚拟 DOM(Virtual DOM)和 diff

  • vdom 是实现 Vue 和 React 的重要基石
  • diff 算法是 vdom 中最核心、最关键的部分
  • vdom 是一个热门话题,也是面试中的热门问题

1、vdom


总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值