Vue3系统入门与项目实战学习笔记
文章目录1 Vue 语法初探1-1 课前须知1-2 初学编写 HelloWorld 和 Counter1-3 编写字符串反转和内容隐藏小功能1-4 编写TodoList 小功能,了解循环和双向绑定1-5 组件概念初探,对 TodoList 进行组件代码拆分2 Vue 基础语法2-1 Vue 中应用和组件的基础概念2-2 理解 Vue 中的生命周期函数2-3 常用模版语法讲解2-4 数据,方法,计算
文章目录
课程资源:链接:https://pan.baidu.com/s/1OhjafwGM48nny7lPDpC-6Q 提取码:qv0o
1 Vue 语法初探
1-1 课前须知
-
如何使用vue完成项目的开发
-
深度理解特性背后的原理
1-2 初学编写 HelloWorld 和 Counter
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hello world</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Vue.createApp({ // 创建vue实例
// template: '<div>hello world</div>' // 在root元素里面用此模板展示内容
// }).mount('#root'); // 在id为root的元素上使用vue
/**
* 1.找到root节点,往上面挂一个元素
* 2.元素的内容就是template里面的内容
* 3.用{{}}}:表示这是js表达式,是一个变量
* <div id="root">
* <div>{{content}}</div>
* </div>
*/
Vue.createApp({
data() { // 定义数据
return {
content: 1
}
},
// 当页面加载完成,就会执行mounted函数,自动执行函数
mounted() { // 自动执行
// console.log('mounted')
setInterval(() => {
this.content += 1
}, 1000)
},
template: '<div>{{content}}</div>' // 渲染内容
}).mount('#root'); // 渲染节点
</script>
</html>
此后的代码省略了html的head、body部分,只写js部分代码
1-3 编写字符串反转和内容隐藏小功能
// 反转字符串
// Vue.createApp({
// data() {
// return {
// content: 'hello world'
// }
// },
// methods: { // 定义函数
// handleBtnClick() {
// // 将content内容反转
// this.content = this.content.split('').reverse().join('')
// }
// },
// template: `
// <div>
// {{ content }}
// <!-- 给button元素绑定指令v-on:click,v-on:绑定事件,click:绑定的是点击事件,
// 事件会执行一个函数,handleBtnClick表示执行的函数名
// -->
// <button v-on:click="handleBtnClick">反转</button>
// </div>
// `
// }).mount('#root');
// 显示/隐藏
Vue.createApp({
data() {
return {show: true}
},
methods: {
handleBtnClick() {
// 显示/隐藏
this.show = !this.show
}
},
template: `
<div>
<!-- v-if:标签的存在与否,此处取决于show的值-->
<span v-if="show">hello world</span>
<button v-on:click="handleBtnClick">显示/隐藏</button>
</div>
`
}).mount('#root');
1-4 编写TodoList 小功能,了解循环和双向绑定
// 循环list数据
// Vue.createApp({
// data() {
// return {
// list: ['hello', 'world', 'dell', 'lee']
// }
// },
// template: `
// <ul>
// <!-- 循环list数据-->
// <li v-for="(item, index) of list">{{item}} {{index}}</li>
// </ul>
// `
// }).mount('#root');
// 增加
Vue.createApp({
data() {
return {
inputValue: '',
list: []
}
},
methods: {
handleAddItem() {
this.list.push(this.inputValue)
this.inputValue = '' // 增加完之后,input框清空
}
},
template: `
<div>
<!-- v-model:将数据与输入框进行双向绑定-->
<input v-model="inputValue"/>
<button v-on:click="handleAddItem">增加</button>
<ul>
<li v-for="item of list">{{ item }}</li>
</ul>
</div>
`
}).mount('#root');
1-5 组件概念初探,对 TodoList 进行组件代码拆分
const app = Vue.createApp({ // 创建一个vue实例app
data() {
return {
inputValue: '',
list: []
}
},
methods: {
handleAddItem() {
this.list.push(this.inputValue)
this.inputValue = ''
}
},
template: `
<div>
<!-- v-model:将数据与输入框进行双向绑定-->
<input v-model="inputValue"/>
<button
v-on:click="handleAddItem"
v-bind:title="inputValue"
>
<!-- v-bind:将标签上的数据与某个属性绑定-->
<!-- 在标签内部表示数据,直接用{{}}-->
增加 <!-- {{ inputValue }}-->
</button>
<ul>
<todo-item
v-for="(item, index) of list"
v-bind:content="item"
v-bind:index="index"
/>
</ul>
</div>
`
})
// 在app上注册组件:组件是页面的一部分
app.component('todo-item', {
props: ['content', 'index'], // 接收外部传递的内容
template: '<li>{{index}} -- {{content}}</li>'
})
// 在app上挂载实例
app.mount('#root')
2 Vue 基础语法
2-1 Vue 中应用和组件的基础概念
// createApp:创建一个Vue应用,存储到app变量当中
// 传入的参数:这个应用最外层的组件该如何展示
// mvvm设计模式: m->model 数据,v->view 视图,vm->viewModel 视图数据连接层
const app = Vue.createApp({
// 以下内容是app的参数
// model
data() {
return {
message: 'hello world'
}
},
// view
template: "<div>{{message}}</div>"
})
// vm:Vue的根组件,viewModel
const vm = app.mount('#root')
// 可以通过vm操作数据
// vm.$data.message = 'bye'
2-2 理解 Vue 中的生命周期函数
/**
* 生命周期函数:在某一时刻会自动执行的函数 (钩子=生命周期函数)
* 关键点:某一时刻
* 自动执行:eg.mounted()
*/
const app = Vue.createApp({
data() {
return {
message: 'hello world'
}
},
// 在实例生成之前自动执行的函数
beforeCreate() {
console.log('beforeCreate')
},
// 在实例生成之后自动执行的函数
created() {
console.log('created')
},
// 在组件内容被渲染到页面之前自动执行的函数
beforeMount() {
console.log(document.getElementById('root').innerHTML, 'beforeMount') // beforeMount
},
// 在组件内容被渲染到页面之后自动执行的函数
mounted() {
console.log(document.getElementById('root').innerHTML, 'mounted') // <div>hello world</div> mounted
},
// 当数据发生变化时,会自动执行的函数
beforeUpdate() {
console.log(document.getElementById('root').innerHTML, 'beforeUpdate') // <div>hello world</div> beforeUpdate
},
// 当数据发生变化,页面重新渲染后,会自动执行的函数
updated() {
console.log(document.getElementById('root').innerHTML, 'updated') // <div>bye</div> beforeUpdate
},
// 当vue应用场景失效时,会自动执行的函数
beforeUnmount() {
console.log(document.getElementById('root').innerHTML, 'beforeUnmount') // <div>hello world</div> beforeUnmount
},
// 当vue应用场景失效时,且 dom 完全销毁后,会自动执行的函数
unmounted() {
console.log(document.getElementById('root').innerHTML, 'unmounted') // unmounted
},
// 如果没有template属性,内容写在了body下的div root下,生命周期函数也是如此
template: "<div>{{message}}</div>" // 先将模板变成函数,再渲染
})
const vm = app.mount('#root')
2-3 常用模版语法讲解
const app = Vue.createApp({
data() {
return {
message: 'hello world',
disabled: false,
show: true,
event: 'click' // 动态参数
}
},
methods: {
handleClick() {
alert('click')
},
// pd(e) {
// e.preventDefault() // 阻止默认行为
// }
},
template: `
<!--
{{message}}:花括号内部可写js表达式
v-once:表达式只执行一次,可以提高渲染性能
-->
<div v-once>{{ message }}</div>
<!--
v-html="message":在div上通过html方式展示message变量
v-bind:title="message":展示标签内容,并与数据message进行绑定
v-bind:title等价于:title(简写)
-->
<div v-bind:title="message">hello world</div>
<!-- 与disabled值进行绑定,设置输入框input的状态 -->
<input v-bind:disabled="disabled"/>
<!-- v-if="show":div是否展示有show决定 -->
<div v-if="show">{{ message }}</div>
<!--
1.v-on:事件绑定,事件方法handleClick写在methods中
2.v-on:等价于@(简写)
3.:[event]:动态属性
v-on:click="handleClick"
@click="handleClick"
@[event]="handleClick"
-->
<div @click="handleClick">{{ message }}</div>
<!--
阻止默认行为:
1.在函数内部完成功能:e.preventDefault()
2.使用修饰符:.prevent(常用)
-->
<!-- <form action="https://www.baidu.com" @click="pd">-->
<form action="https://www.baidu.com" @click.prevent="handleClick">
<button>提交</button>
</form>
`
})
app.mount('#root')
2-4 数据,方法,计算属性和侦听器
/**
* data methods computed watcher
* computed和methods实现相同的功能时,选computed,因为有缓存
* computed和watcher实现相同的功能时,选computed,因为更简洁
*/
const app = Vue.createApp({
data() {
return {
message: 'hello world',
count: 2,
price: 5,
newTotal: 10,
}
},
methods: {
// 方法中this指向vue实例
// 不要使用箭头函数,因为箭头函数的this指向其外层的this
handleClick() {
console.log('click', this)
},
formatString(string) {
return string.toUpperCase()
},
// 只要页面重新渲染,才会重新计算
getTotal() {
return Date.now()
// return this.count * this.price
}
}, computed: {
total() {
// 当计算属性依赖的内容发生变更时,才会重新执行计算
return Date.now()
// return this.count * this.price
}
},
// 当遇到异步情况的时候,可以使用watch
// watch可以监听变量的改变,进行异步操作
watch: {
// price发生变化时函数会执行
// price() {
// setTimeout(() => {
// console.log('price change')
// }, 1000)
// },
// 也可以实现计算属性的功能,实际上watch就是计算属性的底层实现
price(cur, pre) {
console.log(cur, pre)
this.newTotal = cur * this.count
}
},
template: `
<!-- methods中的函数既可以在绑定事件的时候使用,也可以在js表达式{{}}中使用-->
<div @click="handleClick">{{ formatString(message) }}</div>
<!-- computed:计算属性
当计算属性computed和方法methods实现的效果相同时,选computed,计算属性内部有缓存机制,会更高效
-->
<div>{{ message }} total:{{ total }} getTotal():{{ getTotal() }}</div>
<!-- 计算属性computed和监听watch实现的效果相同时,选computed -->
<div>newTotal:{{ newTotal }}</div>
`
})
const vm = app.mount('#root')
2-5 样式绑定语法
const app = Vue.createApp({
data() {
return {
classString: 'red',
classObject: {red: true, green: true}, // 变量名:展示的样式值,变量值:是否展示
classArray: ['red', 'green', {brown: true}],
styleString: 'color: yellow', // 不建议使用
styleObject: { // 建议使用对象的形式来写
color: 'orange',
background: 'skyblue'
}
}
},
template: `
<!-- 1.通过字符串方式,改变样式颜色 -->
<div v-bind:class="classString">hello world</div>
<!-- 对象 -->
<div v-bind:class="classObject">hello world</div>
<!-- 数组-->
<div :class="classArray">hello world</div>
<!-- 2.div父组件,demo子组件 -->
<div :class="classString">
hello world
<!--
demo组件上有两个标签,直接在demo上设置属性不能用
解决办法:1)分别给one two写class属性
2)给需要设置属性的标签上写:class="$attrs.class",使其去获取父组件的属性
-->
<demo class="green"/>
</div>
<!-- 3.行内样式 -->
<!-- 1)直接在行内样式写 -->
<div style="color: yellow">hello world</div>
<!-- 2)与styleString数据绑定 (不建议使用)-->
<div :style="styleString">hello world</div>
<!-- 3)使用对象的形式表示 (建议使用)-->
<div :style="styleObject">hello world</div>
`
})
app.component('demo', {
template: `
<div :class="$attrs.class">one</div>
<div>two</div>
`
})
const vm = app.mount('#root')
2-6 条件渲染
const app = Vue.createApp({
data() {
return {
show: true,
conditionOne: false,
conditionTwo: true,
}
},
template: `
<!-- v-if & v-show -->
<!-- v-if通过控制dom来展示元素,如果show=false,直接删除该div -->
<div v-if="show">hello world</div>
<!-- v-show通过控制style属性来展示元素,如果show=false,style="display: none" -->
<div v-show="show">hello world</div>
<!-- 总结:如果需要频繁改变dom元素显示与否,选v-show -->
<!--if / else if /else-->
<div v-if="conditionOne">if</div>
<div v-else-if="conditionTwo">else</div>
<div v-else>else</div>
`
})
const vm = app.mount('#root')
2-7 列表循环渲染
const app = Vue.createApp({
data() {
return {
listArray: ['hello', 'world'],
listObject: {
name: 'mys',
job: 'student'
},
}
},
methods: {
handleAddBtnClick() {
// 1.使用数组的变更函数 push pop shift unshift splice sort reverse
// this.listArray.push('hello')
// 2. 直接替换数组
// this.listArray = ['bye', 'world'].filter(item => item === 'bye')
// 3.直接更新数组的内容
// this.listArray[1] = 'hhhh'
// 直接添加对象的内容,也可以自动的展示出来
this.listObject.age = '18'
this.listObject.sex = 'female'
}
},
template: `
<div>
<!-- 改变值 -->
<!-- :key:表示是否可以复用,最好是唯一值 -->
<div v-for="(item, index) in listArray" :key="index">
{{ item }} -- {{ index }}
</div>
<button @click="handleAddBtnClick">新增1</button>
<!--
for循环的优先级高于if,同时写if不生效
可以用一个占位符template写v-for
再用一个div写v-if
-->
<div>
<!-- template占位符,不会在页面上做任何渲染 -->
<template
v-for="(value, key, index) in listObject"
:key="index"
>
<div v-if="key !== 'name'">
{{ key }} -- {{ value }} -- {{ index }}
</div>
</template>
</div>
<button @click="handleAddBtnClick">新增2</button>
<div v-for="item in 5">{{ item }}</div>
<button @click="handleAddBtnClick">新增3</button>
</div>
`
})
const vm = app.mount('#root')
2-8 事件绑定
template: `
<div>
{{ counter }}
<!-- 1.event原生事件,如果函数需要传递其他参数和原生event,可以使用$event传递原生事件 -->
<!-- <button @click="handleBtnClick(2, $event)">button</button>-->
<!-- 2.如果事件绑定多个函数,函数间可以用,隔开,注意需要加() -->
<!-- <button @click="handleBtnClick1(), handleBtnClick2()">button</button>-->
<!--
3.事件修饰符:
.stop 停止冒泡
.self 自身标签出发才有效,子标签触发无效
.prevent 阻止默认行为
.capture 把事件的运营模式变成捕获模式
.once 事件的绑定只执行一次
.passive
-->
<div @click.self="handleDivClick">
div自身
<button @click.stop="handleBtnClick">button</button>
</div>
<!--
4.按键修饰符:
.enter 按下enter才执行函数
.tab/.delete/.esc/.up ......
-->
<input @keydown.enter="handleKeyDown"/>
<!--
5.鼠标修饰符:
.left/.right/.middle
-->
<div @click.left="handleClick">鼠标修饰符</div>
<!--
6.精确饰符:
.exact 只有精确按下指定的键才生效
-->
<div @click.ctrl.exact="handleClick">精确饰符</div>
</div>
`
})
2-9 表单中双向绑定指令的使用
const app = Vue.createApp({
data() {
return {
message: 'hello',
checkbox: [],
radio: '',
select: '',
options: [{
text: 'A', value: 'A',
}, {
text: 'B', value: 'B',
}, {
text: 'C', value: {value: 'C'},
}],
checkboxValue: 'hello',
lazy: 'lazy',
number: 12,
}
},
template: `
<div>
<!--
1.v-model:双向绑定 输入框与变量message绑定
input/textarea/checkbox/radio/select
-->
{{ message }}
<input v-model="message"/>
<textarea v-model="message"/>
{{ checkbox }}
<!-- checkbox:可以写多个,可以用一个数组来保存选中的value -->
aa <input type="checkbox" v-model="checkbox" value="aa"/>
bb <input type="checkbox" v-model="checkbox" value="bb"/>
{{ radio }}
cc <input type="radio" v-model="radio" value="cc"/>
dd <input type="radio" v-model="radio" value="dd"/>
{{ select }}
<select v-model="select" multiple>
<!-- 不加multiple可以写下面的提示 -->
<!-- <option disabled value="">请选择内容</option>-->
<!-- options中的value也可以是一个对象 -->
<option v-for="item in options" :value="item.value">{{ item.text }}</option>
</select>
{{ checkboxValue }}
<!-- 使用true-value/false-value可以改变checkbox的值 -->
<input type="checkbox" v-model="checkboxValue" true-value="hello" false-value="world"/>
<!--
2.修饰符
.lazy 当input框失去焦点时才生效
.number 将输入框的类型变为number
.trim 去除字符串前后空格
-->
{{ lazy }}
<input v-model.lazy="lazy"/>
{{ typeof number }}
<input v-model.number="number"/>
</div>
`
})
const vm = app.mount('#root')
3 探索组件的理念
3-1 组件的定义及复用性,局部组件和全局组件
/**
* 定义一个局部组件,在app中不能直接使用,
* 需要写上:components: {'mys': counter_part}引入局部组件
* 定义局部组件,通常将首字符大写
*/
const CounterPart = {
data() {
return {
count: 1
}
},
template: `<div @click="count += 1">{{count}}</div>`
}
const HelloWorld = {
template: `<div>hello world</div>`
}
const app = Vue.createApp({
/**
* 创建一个vue实例,渲染根组件
* 组件:对页面的展示
* 组件可以被复用,且其数据相互独立
* 组件在app中定义了,即使没使用,也会挂在app上
* 在app中定义的组件是全局组件,在处处都能使用,性能不高,使用简单,名字通常用小写字母,中间用-隔开
* 局部组件需要定义并注册后才能使用,性能高,但是使用麻烦,名字通常大写字母开头,驼峰式命名
* 局部组件使用时,需要名字和组件间映射对象,如果不写映射,按照上面的命名方式,vue底层会自动映射
*/
// 在vue实例中使用components引入局部组件
components: {
// 'CounterPart': CounterPart,
// CounterPart 命名和组件名相同可以简写
// 'hello-world': HelloWorld
// vue会自动进行映射,将驼峰命名格式映射为:counter-part短横线分割的格式
CounterPart, HelloWorld
},
template: `
<div>
<!-- 全局组件 -->
<hello/>
<world/>
<counter-parent/>
<counter/>
<counter/>
<!-- 局部组件 -->
<counter-part/>
<hello-world/>
</div>
`
})
// 定义子组件 (全局组件)
app.component('hello', {
template: `<div>hello</div>`
})
app.component('world', {
template: `<div>world</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')
3-2 组件间传值及传值校验
const app = Vue.createApp({
data() {
return {
num: 123,
// num: () => alert('num')
}
},
template: `
<div>
<!--
1.父组件调用子组件,传递内容给子组件使用
2.静态参数 & 动态参数
静态传参:不带:,直接使用,一般只能传递字符串
动态传参:带:,比如 :content,由data里面的数据决定
-->
<test :content="num"/>
</div>
`
})
/**
* 3.父组件->子组件:接收数据、类型校验
* type:String,Boolean,Array,Object,Function,Symbol
* required: 必选
* default:默认值
*/
// 创建一个全局组件
app.component('test', {
// props: ['content'], 直接接收父组件传递来的数据
// 如果需要对父组件传递来的数据做验证
props: {
// 如果传递来的数据不是String类型的,控制台会出现警告提示:type check failed for prop "content"
// content: String,
// content:Function,
content: {
type: Number,
// default: 123, // 默认值
// required: true, // 如果没写default,不传参会有警告
// 深度校验
validator: function (value) { // value父组件传递来的值
return value < 1000
},
// default也可以是一个函数
default: function () {
return 456
}
},
},
methods: {
handleClick() {
alert('handleClick')
this.content() // 调用父组件传递来的函数
}
},
template: `
<div @click="this.handleClick">{{ content }}</div>`
})
const vm = app.mount('#root')
3-3 单向数据流的理解
const app = Vue.createApp({
data() {
return {
params: {
content: 123,
a: 1,
b: 2,
},
dataAbc: 123,
num: 1,
}
},
template: `
<div>
<!-- <test :content="num" :a="a" :b="b"/> -->
<!-- 1.如果参数太多,可以把参数封装到一个对象params中 -->
<!-- <test v-bind="params"/>等价于:
<test :content="params.content" :a="params.a" :b="params.b"/>
-->
<test v-bind="params"/>
<!-- 2.如果参数名太长,用-分隔,不能使用驼峰命名,但是注意:子组件接收参数时需要用驼峰命名
即:属性传时使用:data-abc 接收时:dataAbc
-->
<test data-abc="123"/>
<counter :count="num"/>
</div>
`
})
app.component('test', {
props: ['content', 'a', 'b', 'dataAbc'],
template: `
<div>{{ content }} -- {{ a }} -- {{ b }} -- {{ dataAbc }}</div>
`
})
/**
* 3.单向数据流
* 父组件可以向子组件传递数据,但数据是只读的,子组件不能修改
* 避免组件之间的数据耦合
*/
app.component('counter', {
props: ['count'],
data() {
return {
myCount: this.count
}
},
template: `
<!-- <div @click="count += 1">{{ count }}</div> 不能修改父组件传递过来的数据-->
<div @click="myCount += 1">{{ myCount }}</div>
`
})
const vm = app.mount('#roo
3-4 Non-Props 属性是什么
/**
* Non-props属性:
* 父组件给子组件传递数据时,子组件不通过props接收,
* 此时底层会将父组件传递过来的属性放在最外层div属性上,
* 如果在子组件上写:inheritAttrs: false,就不会放在最外层div上
* 使用:通常用于 style class属性
*/
const app = Vue.createApp({
template: `
<div>
<counter msg="hello" msg2="world"/>
</div>
`
})
app.component('counter', {
// inheritAttrs: false,
mounted() {
console.log(this.$attrs.msg) // hello
},
template: `
<!-- 如果有多个div,Non-prop属性就不会生效,需要使用v-bind="$attrs"决定是否接收属性
v-bind="$attrs": 接收所有属性
:msg="$attrs.msg": 接收指定属性
-->
<div v-bind="$attrs">Counter</div>
<div :msg="$attrs.msg">Counter</div>
<div>Counter</div>
`,
})
const vm = app.mount('#root')
3-5 父子组件间如何通过事件进行通信
/**
* 父子组件通信的流程:
* 1.子组件接收父组件传递来的count
* 2.点击时触发了事件:handleClick,该事件向父组件传递:add事件,也能传递参数
* 3.父组件监听到add事件,通过handleAdd进行处理,修改count
* 4.最后修改了的count传递给了子组件
*/
const app = Vue.createApp({
data() {
return{
count: 1
}
},
methods: {
handleAdd(param) {
this.count += param
}
},
template: `
<div>
<!-- 注意:触发事件时:驼峰命名,监听时间:-分隔命名 -->
<counter :count="count" @add="handleAdd"/>
</div>
`
})
app.component('counter', {
props: ['count'],
// emits:表示触发了哪些事件
emits: ['add'],
// embeds: {
// // 可以对参数进行校验
// add: (count) => {
// if (count > 0) {
// return true
// }
// return false
// }
// },
methods: {
handleClick() {
this.$emit('add', 2)
// 事件处理的逻辑也可以直接在子组件内完成,再传递给父组件
// this.$emit('add', this.count + 3)
}
},
template: `
<div @click="handleClick">{{ count }}</div>
`,
})
const vm = app.mount('#root')
使用v-model简化
/**
* 使用v-model简化
* 1.父组件中定义一个数据count,通过v-model传递给子组件
* 2.子组件使用modelValue接收参数,注意:接收参数名只能是:modelValue
* 3.点击触发事件handleClick,该事件向父组件传递的事件名只能是:update:modelValue
* 传递的参数modelValue会替换父组件的count
*/
const app = Vue.createApp({
data() {
return {
count: 1
}
},
template: `
<counter v-model="count"/>
<!-- 如果想要指定modelValue的名字,可以使用:app,下面全部改成app即可 -->
<!-- <counter v-model:app="count"/> -->
`
})
app.component('counter', {
props: ['modelValue'],
methods: {
handleClick() {
this.$emit('update:modelValue', this.modelValue + 3)
}
},
template: `
<div @click="handleClick">{{ modelValue }}</div>
`,
})
const vm = app.mount('#root')
3-6 组件间双向绑定高级内容
**1、父子组件间的通信 v-model 传递多个参数 **
/**
* 父子组件间的通信 v-model
* 传递多个参数
*/
const app = Vue.createApp({
data() {
return {
count: 1,
count1: 1,
}
},
template: `
<!-- 如果需要传递2个参数,可以写2个v-model,并通过:count的方式指定名字 -->
<counter v-model:count="count" v-model:count1="count1"/>
`
})
app.component('counter', {
props: ['count', 'count1'],
methods: {
handleClick() {
this.$emit('update:count', this.count + 3)
},
handleClick1() {
this.$emit('update:count1', this.count1 + 3)
}
},
template: `
<div @click="handleClick">{{ count }}</div>
<div @click="handleClick1">{{ count1 }}</div>
`,
})
const vm = app.mount('#root')
2、修饰符的使用
/**
* 父子组件间的通信 v-model
* 修饰符的使用:
* .uppercase
*
* 1.父组件中:v-model.uppercase
* 2.父组件会传递一个modelModifiers参数接收修饰符,注意:modelModifiers名字是固定的
* 3.在点击事件handleClick,获取修饰符的值,并调用相应的函数进行处理
* 4.向父组件传递事件:update:modelValue和新的参数newValue
* 5.父组件获取到事件后,对count变量的值进行修改
*/
const app = Vue.createApp({
data() {
return {
count: 'a',
}
},
template: `
<counter v-model.uppercase="count"/>
`
})
app.component('counter', {
props: {
'modelValue': String,
// 传递过来的修饰符
'modelModifiers': {
// 如果不传修饰符,默认空对象;如果传了修饰符会放到对象中
default: () => ({})
}
},
// mounted() {
// console.log(this.modelModifiers)
// },
methods: {
handleClick() {
let newValue = this.modelValue + 'b'
if (this.modelModifiers.uppercase) {
newValue = newValue.toUpperCase()
}
this.$emit('update:modelValue', newValue)
},
},
template: `
<div @click="handleClick">{{ modelValue }}</div>
`,
})
const vm = app.mount('#root')
3-7 使用插槽和具名插槽解决组件内容传递问题
/**
* slot 插槽
* slot不能绑定事件,可以在用一个span标签包裹slot,用于绑定事件
* slot中可以传递:标签、字符串、子组件
*
* slot中使用的数据作用域问题:
* 父模板里调用的数据属性,都用的是父模板里的数据
* 子模板里调用的数据属性,都用的是子模板里的数据
*
* slot的简写:v-slot:header => #header
*/
const app = Vue.createApp({
data() {
return {
text: '提交',
}
},
template: `
<!--
需求:第1个myform:提交组件是 div;第2个myform:提交组件是 button
将myform变成双标签,在标签内写入需要的东西
可以将需要的内容放入data中,再获取
-->
<myform>
<!-- slot -->
<div>{{ text }}</div>
</myform>
<myform>
<button>{{ text }}</button>
</myform>
<myform>
</myform>
<layout>
<!-- 具名插槽:
用占位符template指定不同的slot
-->
<template v-slot:header>
<div>header</div>
</template>
<!-- slot简写 -->
<template #footer>
<div>footer</div>
</template>
</layout>
`
})
app.component('myform', {
methods: {
handleClick() {
alert('click')
}
},
template: `
<div>
<input/>
<span @click="handleClick">
<!-- 可以设定默认值 -->
<slot>default value</slot>
</span>
</div>
`,
})
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
})
const vm = app.mount('#root')
3-8 作用域插槽
/**
* 作用域插槽
* 1.父组件调用list的子组件,即传递给子组件的slot div
* 2.子组件调用slot,通过div的形式循环显示内容
* 3.调用slot时,将item数据通过slotProps数据对象传递给slot
*
* 当子组件渲染的内容由父组件决定时
* 通过作用域插槽,能够让父组件调用子组件的数据
*/
const app = Vue.createApp({
template: `
<!-- <list v-slot="slotProps"> -->
<!-- <div>{{ slotProps.item }}</div> -->
<!-- </list> -->
<!-- 可以使用解构的方式简化 -->
<list v-slot="{item}">
<div>{{ item }}</div>
</list>
`
})
app.component('list', {
data() {
return {list: [1, 2, 3]}
},
template: `
<div>
<slot v-for="item in list" :item="item"/>
</div>
`
})
const vm = app.mount('#root')
3-9 动态组件和异步组件
/**
* 需求:通过一个变量控制input-item、common-item的展示与隐藏
*
* 动态组件:根据数据的变化,结合component标签,来随时动态切换组件的显示
* 异步组件:异步执行某些组件的逻辑
*/
const app = Vue.createApp({
data() {
return {currentItem: 'input-item'}
},
methods: {
handleClick() {
this.currentItem = this.currentItem === 'input-item' ? 'common-item' : 'input-item'
}
},
template: `
<!-- <input-item v-show="currentItem === 'input-item'"/>-->
<!-- <common-item v-show="currentItem === 'common-item'"/>-->
<!-- keep-alive缓存,component动态组件 -->
<keep-alive>
<component :is="currentItem"/>
</keep-alive>
<button @click="handleClick">切换</button>
<div>
<sync-common-item /> <!-- 同步组件 -->
<async-common-item /> <!-- 异步组件 -->
</div>
`
})
app.component('input-item', {
template: `
<input />
`
})
app.component('common-item', {
template: `
<div>hello world</div>
`
})
// 同步
app.component('sync-common-item', {
template: `
<div>hello world</div>
`
})
// 异步
app.component('async-common-item', Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div>this is an async component</div>`
})
}, 4000)
})
}))
const vm = app.mount('#root')
3-10 基础语法知识点查缺补漏
/**
* v-once:让某个元素标签只渲染一次
* ref:获取dom节点 / 组件的一个用法 (直接操作dom是要慎重)
* provide / inject 多层组件传值,使用provide提供数据,inject注入数据
*/
const app = Vue.createApp({
data() {
return {count: 1}
},
// provide: {
// count: 2,
// },
// 如果是传递data中的数据,要使用函数形式
// 此处传递的数据是一次性的,需要类似双向绑定的效果需要响应的知识,后面会讲
provide() {
return {
count: this.count
}
},
mounted() {
// 获取dom最好在mounted中,因为此时页面已经加载完成。但是最好不要直接操作dom
// console.log(this.$refs.count.innerHTML = 'hello')
// console.log(this.$refs.common.syaHello())
},
template: `
<div>
<div ref="count">
{{ count }}
</div>
<common-item ref="common"/>
<!-- 层层传递数据count:父组件->child->child-child -->
<child :count="count"/>
<button @click="count += 1">Add</button>
</div>
`
})
app.component('common-item', {
methods: {
syaHello() {
alert('hello')
}
},
template: `<div>hello world</div>`
})
app.component('child', {
props: ['count'],
// template: `<child-child :count="count"/>`
template: `<child-child/>`
})
app.component('child-child', {
// props: ['count'],
inject: ['count'],
template: `<div>{{ count }}</div>`
})
const vm = app.mount('#root')
4 Vue 中的动画
4-1 使用 Vue 实现基础的 CSS 过渡与动画效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-1</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
/* 动画 */
.animation {
animation: leftToRight 3s;
}
@keyframes leftToRight {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
0% {
transform: translateX(0px);
}
}
/* 过渡 */
.transition {
transition: 3s background-color ease;
}
.blue {
background-color: blue;
}
.green {
background-color: green;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 过渡 动画
const app = Vue.createApp({
data() {
return {
animate: {
animation: false
},
transit: {
transition: true,
blue: true,
green: false,
},
styleObj: {
background: 'red'
}
}
},
methods: {
handleClick() {
this.animate.animation = !this.animate.animation
},
handleClick1() {
this.transit.transition = !this.transit.transition
this.transit.blue = !this.transit.blue
this.transit.green = !this.transit.green
},
handleClick2() {
if (this.styleObj.background === 'red') {
this.styleObj.background = 'yellow'
} else {
this.styleObj.background = 'red'
}
}
},
template: `
<div>
<!-- 动画 -->
<div :class="animate">hello</div>
<button @click="handleClick">切换</button>
<!-- 过渡:css -->
<div :class="transit">hello</div>
<button @click="handleClick1">切换</button>
<!-- 过渡:style样式 -->
<div class="transition" :style="styleObj">hello</div>
<button @click="handleClick2">切换</button>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
4-2 使用 transition 标签实现单元素组件的过渡和动画效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-2</title>
<script src="https://unpkg.com/vue@next"></script>
<!-- 引入动画样式库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<style>
/*入场动画:v-enter-from/to/active*/
.v-enter-from {
opacity: 0;
color: red;
}
.v-enter-to {
opacity: 1;
}
/*出场动画*/
/*可以不写,因为出厂前opacity本身就是1*/
/*.v-leave-from {*/
/* opacity: 1;*/
/*}*/
.v-leave-to {
opacity: 0;
}
/*在整个动画的过程中如何执行:入场、出场效果一样,可以写在一起*/
.v-enter-active,
.v-leave-active,
.hello,
.bye {
animation: shake 3s;
transition: all 3s ease-in;
}
/*动画*/
@keyframes shake {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
100% {
transform: translateX(0px);
}
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
/**
* 单元素、单组件的入场、出场动画,可以给组件同时添加动画、过渡效果
*/
const app = Vue.createApp({
data() {
return {
show: true
}
},
methods: {
handleClick() {
this.show = !this.show
},
// 在入场之前可以做的事
handleBeforeEnter(el) {
el.style.color = 'red'
},
// 执行时的操作
handleEnterActive(el, done) {
const animation = setInterval(() => {
const color = el.style.color
if (color === 'red') {
el.style.color = 'yellow'
} else {
el.style.color = 'red'
}
}, 1000)
// 5s后清除动画
setTimeout(() => {
clearInterval(animation)
done() // 表明动画结束
}, 5000)
},
// 执行完的操作
handleEnterEnd() {
alert(123)
},
},
template: `
<div>
<!--
1.可以在transition中重命名:<transition name="hello">
.v-enter-active => .hello-enter-active
固定写法:.v-enter-from .v-enter-active .v-enter-to
.v-leave-from .v-leave-active .v-leave-to
2.也可以自定义名字:
<transition
enter-active-class="hello"
leave-active-class="bye"
>
在style中直接使用.hello / .bye即可
-->
<!-- <transition-->
<!-- enter-active-class="hello"-->
<!-- leave-active-class="bye"-->
<!-- >-->
<!-- 3.可以直接引入第三方animate.css的动画库: https://animate.style/-->
<!-- <transition-->
<!-- enter-active-class="animate__animated animate__bounce"-->
<!-- leave-active-class="animate__animated animate__bounce"-->
<!-- >-->
<!--
4.type="transition":当同时有过渡和动画效果时,如果时间不一致,可以指定以其中的一个为准,控制同步执行
(1)type="transition" 以transition为准
(2):duration="1000":指定执行时间
:duration="{enter: 1000, leave:3000}" 也可以分别指定时间
-->
<!-- <transition :duration="1000"> -->
<!--
5.如果想用js来控制动画效果,可以取消css控制 :css="false"
钩子:生命周期函数,在某个时刻被调用
@before-enter="handleBeforeEnter" 函数参数:el
@enter="handleEnterActive" el done
@after-enter="handleEnterEnd" el
@before-leave
@leave
@after-leave
-->
<transition
:css="false"
@before-enter="handleBeforeEnter"
@enter="handleEnterActive"
@after-enter="handleEnterEnd"
>
<div v-if="show">hello world</div>
<!-- <div v-show="show">hello world</div>-->
</transition>
<button @click="handleClick">切换</button>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
4-3 组件和元素切换动画的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-2</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
.v-enter-from {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 3s ease-in;
}
.v-enter-to {
opacity: 1;
}
.v-leave-from {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
/**
* 多个单元素标签之间的切换 transition
*
* 多个单组件标签之间的切换
* 1.v-if v-else
* 2.动态组件 <component :is="component" />
*/
const ComponentA = {
template: `<div>hello world</div>`
}
const ComponentB = {
template: `<div>bye world</div>`
}
const app = Vue.createApp({
data() {
return {
show: false,
component: 'component-a',
}
},
components: {
'component-a': ComponentA,
'component-b': ComponentB,
},
methods: {
handleClick() {
this.show = !this.show
},
handleClick1() {
if (this.component === 'component-a') {
this.component = 'component-b'
} else {
this.component = 'component-a'
}
},
},
template: `
<div>
<!-- mode="out-in":动画效果先隐藏再展示
appear:初次展示动画效果
-->
<transition mode="out-in" appear>
<div v-if="show">hello world</div>
<div v-else="show">bye world</div>
</transition>
<button @click="handleClick">切换</button>
<transition mode="out-in" appear>
<component-a v-if="show" />
<component-b v-else="show" />
</transition>
<button @click="handleClick">切换</button>
<transition mode="out-in" appear>
<component :is="component" />
</transition>
<button @click="handleClick1">切换</button>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
4-4 列表动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-4</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
.list-item {
display: inline-block;
margin: 10px;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: translateY(30px);
}
.v-enter-active,
.v-leave-active {
transition: all 1s ease-in;
}
.v-enter-to,
.v-leave-from {
opacity: 1;
transform: translateY(0px);
}
/*其他列表项的移动效果*/
.v-move {
transition: all 1s ease-in;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
/**
* 列表动画
* transition-group
* .v-move
*/
const app = Vue.createApp({
data() {
return {
list: [1, 2, 3],
}
},
methods: {
handleClick() {
this.list.unshift(this.list.length + 1)
},
},
template: `
<div>
<transition-group>
<span class="list-item" v-for="item in list" :key="item">{{ item }}</span>
<button @click="handleClick">增加</button>
</transition-group>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
4-5 状态动画
/**
* 状态动画 svg
*/
const app = Vue.createApp({
data() {
return {
number: 1,
animateNumber: 1,
}
},
methods: {
handleClick() {
this.number = 10
if (this.animateNumber < this.number) {
const animation = setInterval(() => {
this.animateNumber += 1
if (this.animateNumber === 10) {
clearInterval(animation)
}
}, 100)
}
},
},
template: `
<div>
<div>{{ animateNumber }}</div>
<button @click="handleClick">增加</button>
</div>
`
})
const vm = app.mount('#root')
5 Vue 中的高级语法
5-1 Mixin 混入的基础语法
/**
* mixin 混入 (vue3引入后不推荐)
* 1.组件data,methods 优先级高于mixin data,methods优先级
* 2.生命周期函数先执行mixin中的,再执行组件中的
* 3.自定义的属性,组件的属性优先级高于mixin属性的优先级
* 4.改变mixin优先级
*
* 局部 mixin
* 全局 mixin(不推荐)
*/
// 局部mixin
const myMixin = {
// data() {
// return {
// number: 2,
// }
// },
number: 2,
created() {
console.log('mixin created')
},
methods: {
handleClick() {
console.log('mixin handleClick')
}
},
}
const app = Vue.createApp({
// data() {
// return {
// number: 1,
// }
// },
number: 1,
mixins: [myMixin], // 使用mixin
created() {
console.log('created')
},
methods: {
// handleClick() {
// console.log('handleClick')
// }
},
template: `
<div>
<!-- <div>{{ number }}</div>-->
<!-- <button @click="handleClick">点击</button>-->
<!-- vue中所有没有被处理的内容都会被挂在options中 -->
<div>{{ this.$options.number }}</div>
<child/>
</div>
`
})
// 修改mixin属性的优先级
app.config.optionMergeStrategies.number = (mixinVal, appValue) => {
return mixinVal || appValue
}
// 全局mixin
// app.mixin({
// data() {
// return {
// number: 2,
// }
// },
// created() {
// console.log('mixin created')
// },
// methods: {
// handleClick() {
// console.log('mixin handleClick')
// }
// },
// })
app.component('child', {
template: "<div>child</div>"
})
const vm = app.mount('#root')
5-2 开发实现 Vue 中的自定义指令
/**
* 自定义组件 directive
* 1.局部
* (1) 定义指令
* (2) 在app中使用指令
* 2.全局
* app.directive
*/
// 局部自定义指令
// const directives = {
// focus: {
// mounted(el) {
// el.focus()
// }
// }
// }
const app = Vue.createApp({
data() {
return {show: true}
},
// directives,
template: `
<div>
<div v-if="show">
<input v-focus/>
</div>
</div>
`
})
// 全局自定义指令
app.directive('focus', {
// 执行前
beforeMount() {
console.log('beforeMount')
},
// 执行
mounted(el) {
el.focus()
},
// 更新前
beforeUpdate() {
console.log('beforeUpdate')
},
// 更新
updated() {
console.log('updated')
},
// 失效前
beforeUnmount() {
console.log('beforeUnmount')
},
// 失效
unmounted() {
console.log('unmounted')
}
})
const vm = app.mount('#root')
小例子
/**
* 小例子:控制输入框的位置
*/
const app = Vue.createApp({
data() {
return {distance: 100}
},
template: `
<div>
<!-- 参数binding.arg=left 参数的值:binding.value=distance -->
<div v-pos:left="distance" class="header">
<input/>
</div>
</div>
`
})
// app.directive('pos', {
// mounted(el, binding) {
// el.style.top = binding.value + 'px'
// },
// })
// 如果只有一个函数,可以简写
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px')
})
const vm = app.mount('#root')
.header {
position: absolute;
}
5-3 Teleport 传送门功能
/**
* teleport:传送门
*/
const app = Vue.createApp({
data() {
return {
show: false
}
},
methods: {
handleBtnClick() {
this.show = !this.show
},
},
template: `
<div class="area">
<button @click="handleBtnClick">按钮</button>
<!-- 传送门:使得mask挂载到body下 -->
<teleport to="body">
<div class="mask" v-show="show"></div>
</teleport>
</div>
`
})
const vm = app.mount('#root')
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
background-color: skyblue;
}
/*蒙层*/
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: black;
opacity: 0.5;
}
5-4 更加底层的 render 函数
/**
* render函数
* template底层编译会生成render函数
* template -> render -> h -> 虚拟DOM(JS对象) -> 真是DOM -> 展示到页面上
*/
const app = Vue.createApp({
template: `
<my-title :level="2">
hello mys
</my-title>
`
})
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue
/**
* 返回:虚拟DOM 1.让vue的性能更快 2.使得vue可以跨平台使用
* 虚拟DOM eg.
* <div>hello</div>
* {
* tagName: 'div, 标签名
* text: hello, 标签属性
* attributes: {} 节点内容
* }
* 第三个参数也可以是一个数组,里面可以继续嵌套
*/
// this.$slots.default():获取slot中的值 注意:slots要加上s
// return h('h' + this.level, {}, this.$slots.default())
return h('h' + this.level, {}, [this.$slots.default(),
h('h4', {}, 'mys')])
}
// template: `
// <h1 v-if="level === 1"><slot /></h1>
// <h2 v-if="level === 2"><slot /></h2>
// `
})
const vm = app.mount('#root')
5-5 插件的定义和使用
/**
* plugin插件:把通用性的功能封装起来
*/
const myPlugin = {
// app:使用插件的vue实例 options:传递的对象
// 实例拿到之后,可以做很多扩展
install(app, options) {
// console.log(app, options)
app.provide('name', 'mys')
app.directive('focus', {
mounted(el) {
el.focus()
}
})
app.mixin({
mounted() {
// 会输出2次,根组件、子组件都会打印
console.log('mixin')
}
})
// 底层扩展全局属性
app.config.globalProperties.$sayHello = 'hello world'
}
}
const app = Vue.createApp({
template: `
<my-title />
`
})
app.component('my-title', {
inject: ['name'], // 声明provide传递的参数
mounted() {
console.log(this.$sayHello)
},
template: `<div>{{name}}<input v-focus /></div>`
})
app.use(myPlugin, {name: 'mys'})
const vm = app.mount('#root')
5-6 数据校验插件开发实例
/**
* 读数据做校验的插件
*/
const app = Vue.createApp({
data() {
return {
name: 'mys',
age: 18
}
},
// 校验
rules: {
age: {
validate: age => age > 10,
message: 'too young'
},
name: {
validate: name => name.length >= 3,
message: 'too short'
}
},
template: `
<div>name:{{ name }},age:{{ age }}</div>
`
})
const validatePlugin = (app, options) => {
app.mixin({
created() {
for (let key in this.$options.rules) {
const item = this.$options.rules[key]
// this: app实例
// 监听实例数据变化
this.$watch(key, (value) => { // 注意:此处要传入value参数,表示修改后的值
const result = item.validate(value)
if (!result) console.log(item.message)
})
}
}
})
}
app.use(validatePlugin)
const vm = app.mount('#root')
6 Composition API
6-1 Setup 函数的使用
/**
* Composition API:维护代码更方便
*/
const app = Vue.createApp({
data() {
return {
name: 'mys',
}
},
/**
* setup在实例被完全初始化之前执行
* setup中不能使用this,因为实例还没有被初始化
* setup不能调用外部实例方法,而实例方法可以调用setup
*/
setup(props, context) {
return {
name: 'mys',
handleClick: () => {
console.log('handleClick')
}
}
},
template: `
<div @click="handleClick">{{ name }}</div>
`
})
const vm = app.mount('#root')
6-2 ref,reactive 响应式引用的用法和原理
/**
* 响应式的引用
* 原理:通过proxy对数据进行封装,当数据变化时触发模板等内容的更新
* ref:处理基础类型的数据
* reactive:处理非基础类型的数据
*
* 可以替代data
*/
const app = Vue.createApp({
template: `
<!-- 底层判断出这是一个ref数据,会自动调用x.value -->
<div>{{ name }}</div>
<!-- <div>{{ nameObj.name }}</div>-->
<!-- <div>{{ arr[0] }} {{ copyArr[0] }}</div> -->
`,
setup(props, context) {
// 1. ref
// const {ref} = Vue
// // 通过proxy: 'mys' => proxy({value: 'mys'}) 一个响应式引用
// let name = ref('mys')
// setTimeout(() => {
// name.value = 'lee'
// }, 2000)
// return {name}
// 2.reactive
const {reactive, toRefs} = Vue
// 通过proxy: {name: 'mys'} => proxy({name: 'mys'}) 一个响应式引用
const nameObj = reactive({name: 'mys'})
setTimeout(() => {
nameObj.name = 'lee'
}, 2000)
// // return {nameObj}
// 如果用解构,将nameObj中的name值返回,最后不会出现响应式,因为响应式是对象proxy,而此处只是对象的值,不是响应式的
// const {name} = nameObj
// 使用toRefs包装能够实现响应式
// 转换:proxy({name: 'mys'}) => {name: proxy({value: 'mys})},此时name是一个proxy对象,是响应式
const {name} = toRefs(nameObj)
return {name}
// 3.readonly限制响应式引用的修改
// const {reactive, readonly} = Vue
// const arr = reactive([123])
// const copyArr = readonly(arr)
// setTimeout(() => {
// arr[0] = 456
// copyArr[0] = 456
// }, 2000)
// return {arr, copyArr}
},
})
const vm = app.mount('#root')
6-3 toRef 以及 context 参数
/**
* toRefs:如果找不到指定数据,不会给一个默认的响应式引用,而是undefined,就不具备响应式
* toRef: 如果找不到指定数据,就给一个默认为空的响应式数据
*/
const app = Vue.createApp({
template: `
<div>{{ age }}</div>
<!-- <child app="app"/>-->
<child @change="handleChange">parent</child>
`,
methods: {
handleChange() {
console.log('handleChange')
}
},
setup(props, context) {
const {reactive, toRef} = Vue
const data = reactive({name: 'mys'})
const age = toRef(data, 'age')
setTimeout(() => {
age.value = 'lee'
}, 2000)
return {age}
},
})
/**
* context
* attrs:父组件传递过来的 None-props属性
* slots:父组件传递来的插槽 类似:this.$slots
* emit:触发事件 this.$emit('change')
*/
app.component('child', {
template: `
<div @click="handleClick">child</div>
`,
// mounted() {
// console.log(this.$slots)
// this.$emit('change')
// },
setup(props, context) {
const {h} = Vue
const {attrs, slots, emit} = context
// console.log(attrs) // None-props属性
// return () => h('div', {}, slots.default()) // parent
function handleClick() {
emit('change')
}
return {
handleClick
}
},
})
const vm = app.mount('#root')
6-4 使用 Composition API 开发TodoList
// 封装list相关的内容
const listReactiveEffect = () => {
const {reactive} = Vue
const list = reactive([])
const addItemToList = (item) => {
list.push(item)
}
return {list, addItemToList}
}
// 封装inputValue的内容
const inputReactiveEffect = () => {
const {ref} = Vue
const inputValue = ref('123')
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return {inputValue, handleInputValueChange}
}
const app = Vue.createApp({
// 流程调度中转
setup() {
const {list, addItemToList} = listReactiveEffect()
const {inputValue, handleInputValueChange} = inputReactiveEffect()
return {
list, addItemToList,
inputValue, handleInputValueChange
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="() => addItemToList(inputValue)">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
`,
})
const vm = app.mount('#root')
6-5 computed方法生成计算属性
/**
* computed计算属性
*/
const app = Vue.createApp({
setup() {
const {ref, computed} = Vue
const count = ref(0)
const handleClick = () => {
count.value += 1
}
// const countAddFive = computed(() => {
// return count.value + 5
// })
// computed中也能是一个对象
let countAddFive = computed({
get: () => {
return count.value + 5
},
set: (param) => {
count.value = param - 5
}
})
setTimeout(() => {
countAddFive.value = 100
}, 3000)
return {
count, handleClick, countAddFive
}
},
template: `
<div>
<span @click="handleClick">{{ count }}</span> -- {{countAddFive }}
</div>
`,
})
const vm = app.mount('#root')
6-6 watch 和 watchEffect 的使用和差异性
const app = Vue.createApp({
setup() {
const {ref, reactive, watch, toRefs, watchEffect, watchEffectRefs} = Vue
// const name = ref('mys')
const nameObj = reactive({
name: 'mys',
englishName: 'lee'
})
const {name} = toRefs(nameObj)
/**
* watch监听器
* 1.具备一定的惰性 lazy 第一次不执行,发生变化才执行
* 2.参数currentValue/preValue:当前值和之前值
* 3.当监听reactive类型的数据时,需要使用箭头函数:() => nameObj.name
* 4.可以用一个监听器来监听多个数据的变化
*/
// 1.监听ref类型数据
// watch(name, (currentValue, preValue) => { }
// 2.监听reactive类型数据
// watch(() => nameObj.name, (currentValue, preValue) => {
// console.log(currentValue, preValue)
// })
// 3.监听多个数据
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [preName, preEng]) => {
console.log('watch', curName, preName, '--', curEng, preEng)
}, {
immediate: true // 把watch变成立即执行
})
/**
* watchEffect监听器
* 1.立即执行,没有惰性 immediate
* 2.不需要传递监听的内容,会自动感知代码依赖,不需要传递很多参数,只需要传递一个回调函数
* 3.不能获取之前数据的值
*
* 应用:监听Ajax请求;异步请求
* watch watchEffect都可以通过定时器停止
*/
const stop = watchEffect(() => {
console.log('watchEffect', nameObj.name, nameObj.englishName)
setTimeout(() => {
stop()
}, 3000)
})
return {name, nameObj}
},
template: `
<div>
name:<input v-model="nameObj.name "/>
<div>{{ nameObj.name }}</div>
englishName:<input v-model="nameObj.englishName "/>
<div>{{ nameObj.englishName }}</div>
</div>
`,
})
const vm = app.mount('#root')
6-7 生命周期函数的新写法
const app = Vue.createApp({
/**
* 生命周期函数
* beforeMount => onBeforeMount
* mounted => onMounted
* beforeUpdate => onBeforeUpdate
* beforeUnmounted => onBeforeUnmounted
* unmounted => onUnmounted
* 没有beforeCreate/create,直接写在setup中即可
* 新的生命周期函数:
* onRenderTracked:每次渲染后重新收集响应式依赖
* onRenderTriggered:每次触发页面重新渲染时自动执行
*/
setup() {
const {
ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmounted, onUnmounted,
onRenderTracked, onRenderTriggered
} = Vue
const name = ref('mys')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onRenderTracked(() => {
console.log('onRenderTracked')
})
onRenderTriggered(() => {
console.log('onRenderTriggered')
})
handleClick = () => {
name.value = 'lee'
}
return {name, handleClick}
},
template: `
<div @click="handleClick">
{{name}}
</div>
`,
})
const vm = app.mount('#root')
6-8 Provide,Inject,模版 Ref 的用法
/**
* provide inject
*/
const app = Vue.createApp({
setup() {
const {provide, ref, readonly} = Vue
const name = ref('mys')
provide('name', readonly(name)) // 不让子组件修改name
provide('changeName', (value) => {
name.value = value
})
return {}
},
template: `
<div>
<child/>
<domTest/>
</div>
`,
})
app.component('child', {
setup() {
const {inject} = Vue
// 获取父组件传来的name,如果没有,默认值为hello
const name = inject('name', 'hello')
const changeName = inject('changeName')
const handleClick = () => {
// 如果子组件想要修改数据,需要告诉父组件,让父组件来修改 => 单向数据流
// 下面的方法虽然可以修改,但是不推荐使用
// name.value = 'lee'
changeName('lee')
}
return {name, handleClick}
},
template: `
<div @click="handleClick">{{ name }}</div>
`
})
/**
* Composition API语法下 获取真实的DOM元素节点
* 1.在template中定义一个ref引用 ref="hello"
* 2.在setup中定义一个与ref名字相同的常量并且等于空 const hello = ref(null)
* 3.在生命周期函数onMounted中获取dom元素
* 4.return常量 return {hello}
*/
app.component('domTest', {
setup() {
const {ref, onMounted,} = Vue
const hello = ref(null)
onMounted(() => {
console.log(hello.value)
})
return {hello}
},
template:`
<div>
<div ref="hello">hello world</div>
</div>
`
})
const vm = app.mount('#root')
7 Vue 项目开发配套工具讲解
7-1 VueCLI 的使用和单文件组件
1、安装node npm nrm
(1)Node环境安装
下载地址:https://nodejs.org/zh-cn/ (下载长期支持版本LTS)
安装完成,在终端输入:node -v
查看版本号
(2)NPM:Node Package Manager
查看版本:npm -v
(3)NRM(Npm Registry Manager )是npm的镜像源管理工具源切换,找到当前最快的安装源**
安装:npm install -g nrm
NRM安装不成功
如果安装nrm出现下面信息,安装失败,解决办法参考:https://blog.csdn.net/blue_698/article/details/117874021
- 执行:
npm config list -l
,出现下面结果
-
执行
npm cache clean --force
清除缓存 -
执行
nrm ls
出现下面结果即解决
NRM常用命令
测试源的速度:nrm test npm
切换源:nrm use taobao
查看可用源:nrm ls
查看当前源:nrm current
增加定制源:nrm add imooc http://192.168.1.100:6666
删除源:nrm del imooc
2、删除老版本工具
如果之前使用过vue,可以通过下面两个命令删除:
npm uninstall vue-cli -g
yarn global remove vue-cli
3、安装vue新版本工具
可以快速搭建vue工程的一个工具:npm install -g @vue/cli
,注意:用管理员身份打开终端
4、创建项目
5、也可以通过IDE来打开项目并启动,这里我用的是webstorm
6、项目结构说明
main.js
import { createApp } from 'vue' // 创建Vue的实例 app
import App from './App.vue' // app来自当前文件夹下的App.vue
createApp(App).mount('#app')
App.vue
<!--
单文件组件:App.vue文件代表一个组件
1.template 模板
2.script 核心逻辑
3.style 样式
-->
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App', // 根组件
components: { // 局部组件
HelloWorld
}
}
</script>
<style>
</style>
components/HelloWorld.vue
<template>
<h1>{{ msg }}</h1>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<style>
</style>
7-2 使用单文件组件编写 TodoList
1、在components下写ListItem.vue子组件
<template>
<li>{{ msg }}</li>
</template>
<script>
export default {
name: "ListItem",
props: {
msg: String
}
}
</script>
<style scoped>
</style>
2、在单文件组件App.vue中写:template script style
<template>
<div>
<input v-model="inputValue"/>
<button class="button" @click="handleAddItem">提交</button>
</div>
<ul>
<list-item
v-for="(item, index) in list"
:key="index"
:msg="item"
/>
</ul>
</template>
<script>
import {reactive, ref} from 'vue'
// 引入ListItem组件
import ListItem from './components/ListItem';
export default {
name: 'App', // 根组件
// 注册ListItem组件
components: {ListItem},
setup() {
const inputValue = ref('')
const list = reactive([])
const handleAddItem = () => {
list.push(inputValue.value)
inputValue.value = ''
}
return {inputValue, list, handleAddItem}
},
}
</script>
<style>
.button {
margin-left: 20px;
}
</style>
7-3 Vue-Router 路由的理解和使用
1、创建项目
步骤上面类似,需要修改:
2、哈希路由:根据不同的URL展示不同的效果
3. 流程
1.通过入口main.js,引入router路由
2.在router文件夹下定义路由 =>将不同的组件与不同的路径相对应
3.在views下写各个路由所对应的组件内容
4.在App.vue中引入路由跳转及其组件内容
5.通过app实例运行项目
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 哈希路由:根据URL的不同,展示不同的效果
createApp(App).use(router).mount('#app')
router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
// 访问不同的路径,展示的组件不同
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// 异步加载路由:访问的时候再去加载
// 缺点:可能会有卡顿 具体怎么选择根据项目来决定
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "about" */ '../views/LoginView.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
views/LoginView.vue
<template>
<div class="login">
<h1>LoginPage</h1>
</div>
</template>
App.vue
<template>
<nav>
<!-- router-link:跳转路由的标签 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/login">Login</router-link>
</nav>
<!-- router-view:展示当前路由对应的组件内容 -->
<router-view/>
</template>
<style>
</style>
7-4 VueX 的语法详解
1、创建项目
2、流程
(1)获取数据
1.通过main.js引入router、store
2.在store文件夹下创建数据仓库,用来存放全局数据,在state中定义数据
3.在views中通过computed计算属性来获取数据并展示在页面上
4.通过app实例运行项目
(2)修改数据
1.通过dispatch方法,派发一个action,名为change
2.感知到change的action,执行store中的actions下面的change方法
3.commit提交一个名为change的数据改变
4.mutation感知到提交的change改变,执行change方法,改变数据
dispatch 和 actions关联
commit 和 mutation关联
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
store/index.js
import { createStore } from 'vuex'
// VueX:数据管理框架
// Vue 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
state: {
name: 'mys'
},
getters: {
},
// 潜移默化的约定:mutations中只能写同步的代码,不能写异步的。异步代码通常放到actions中
mutations: {
// 第四步:mutation被执行
change(state, str) {
// 第五步:在mutation里面修改数据
state.name = str
}
},
actions: {
// 第二步:store感知到触发了一个change的action,执行change方法
change(store, str) {
// 第三步:提交一个commit,触发一个mutation
setTimeout(() => {
this.commit('change', str)
}, 2000)
}
},
modules: {
}
})
views/AboutView.vue
<template>
<div class="about">
<h1 @click="handleClick">This is an about page</h1>
<h1>{{ myName }}</h1>
</div>
</template>
<script>
export default {
name: 'AboutView',
computed: {
myName() {
return this.$store.state.name
}
},
methods: {
/**
* 改变store中的数据步骤:
* 1.通过dispatch方法,派发一个action,名为change
* 2.感知到change的action,执行store中的actions下面的change方法
* 3.commit提交一个名为change的数据改变
* 4.mutation感知到提交的change改变,执行change方法,改变数据
*/
handleClick() {
// 第一步:想要改变数据,vue要求第一步必须派发一个action
// change:名字 hello world:参数
this.$store.dispatch('change', 'hello world')
}
},
}
</script>
App.vue
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</template>
<style>
</style>
7-5 CompositionAPI 中如何使用 VueX
不同之处:在setup()中通过VueX提供的useStore获取全局数据对象
views/AboutView.vue
<template>
<div class="about">
<h1 @click="handleClick">This is an about page</h1>
<h1>{{ name }}</h1>
</div>
</template>
<script>
import {useStore} from 'vuex'
import {toRefs} from 'vue'
export default {
name: 'AboutView',
setup() {
// 通过useStore获取全局数据对象
const store = useStore()
const {name} = toRefs(store.state)
const handleClick = () => {
store.dispatch('change', 'hello')
}
return {
name, handleClick
}
}
}
</script>
7-6 使用 axios 发送ajax 请求
1、安装axios工具
npm install axios --save
2、在store/index.js发请求
actions: {
change(store) {
axios.get('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/register')
.then((response) => {
const msg = response.data.message
console.log(msg)
store.commit('change', msg)
})
}
},
8 “京东到家”项目首页开发
8-1 工程初始化
8-2 工程目录代码简介及整理
8-3 基础样式集成及开发模拟器的使用
1、安装normalize.css文件
npm install normalize.css --save
2、src下新建style文件夹,创建base.css
base.css
/* rem与px转换:1rem = 1 html font-size = 100px */
html {
font-size: 100px;
}
3、在main.js中引入
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
import './style/base.css'
createApp(App).use(store).use(router).mount('#app')
4、将浏览器调整为移动端模式
未完…
更多推荐
所有评论(0)