Vue 基础
Vue 中应用和组件的概念
createApp
表示创建一个 Vue 应用, 存储到 app 变量中- 传入的参数表示,这个应用最外层的组件,应该如何展示
- MVVM 设计模式:M -> Model 数据, V -> View 视图, VM -> ViewModel 视图数据连接层
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 5</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
message: 'hello world'
}
},
template: "<div>{{message}}</div>"
});
// 注册组件
app.component('cmp', {
props: ['title'],
template: '<span>{{ title }}</span>'
});
// vm 代表的就是 Vue 应用的根组件
const vm = app.mount('#root');
</script>
</html>
Vue 生命周期与生命周期函数
生命周期函数:某一时刻会自动执行的函数
beforeCrate
:在实例生成之前会自动执行的函数created
:在实例生成之后会自动执行的函数beforeMount
:在组件内容被渲染到页面之前自动执行的函数mounted
:在组件内容被渲染到页面之后自动执行的函数beforeUpdate
:当数据发生变化时会自动执行的函数updated
:当数据发生变化,页面重新渲染后,会自动执行的函数beforeUnmount
:当 Vue 应用失效时,自动执行的函数unmounted
:当 Vue 应用失效时,且 dom 完全销毁后,自动执行的函数
常用模板语法
-
{{}}
:插值表达式 -
v-html
:将数据作为 HTML 代码进行渲染 -
v-bind(:)
:数据绑定 -
v-on(@)
:事件绑定:[propertyName]
、@[eventName]
:动态属性/事件,属性/事件名从 data 中获取(见源码)- 修饰符:控制事件触发方式,例:
@click.prevent
:阻止自带的事件(如源码,点击事件后会跳转到指定 url,使用 prevent 修饰符消除此默认操作)
-
v-slot(#)
:具名 -
v-once
:仅在第一次使用 data 中的数据(后续使用的属性值仍会改变,但节点仅渲染一次) -
v-if
、v-if-else
、v-else
:分支逻辑,仅在条件符合的情况下渲染节点 -
v-show
:控制节点显示v-if
/v-show
区别:v-if
不显示时会将节点直接移除,v-show
不显示则仅是将节点的display
属性设置为 none,若需要频繁控制节点显示隐藏,使用v-show
对性能会更加友好
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 7</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root">
<form v-if='show' action="https://www.baidu.com" @click.prevent="handleClick">
<button type="submit">提交</button>
</form>
<!-- 动态属性 -->
<span v-else :[bindName]="message">
{{ message }}
</span>
</div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
message: "hello world",
show: false,
bindName: 'title'
}
},
methods: {
handleClick() {
alert('click')
}
},
template: ``
});
const vm = app.mount('#root');
</script>
</html>
v-for
:列表循环渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 11</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
listArray: ['dell', 'lee', 'teacher'],
listObject: {
firstName: 'dell',
lastName: 'lee',
job: 'teacher'
}
}
},
methods: {
handleAddBtnClick() {
// 1. 使用数组的变更函数 push, pop, shift, unshift, splice, sort, reverse
// this.listArray.push('hello');
// this.listArray.pop();
// this.listArray.shift();
// this.listArray.unshift('hello');
// this.listArray.reverse();
// 2. 直接替换数组
// this.listArray = ['bye', 'world']
// this.listArray = ['bye', 'wolrd'].filter(item => item === 'bye');
// 3. 直接更新数组的内容
// this.listArray[1] = 'hello'
// 直接添加对象的内容,也可以自动的展示出来
// this.listObject.age = 100;
// this.listObject.sex = 'male';
}
},
template: `
<div>
<!-- 使用 key 属性区分新旧数据,避免列表发生变化时旧数据重复渲染影响性能 -->
<template
v-for="(value, key, index) in listObject"
:key="index">
<div v-if="key !== 'lastName'">
{{ value }} -- {{ key }}
</div>
</template>
<div v-for="item in 10">{{ item }}</div>
<button @click="handleAddBtnClick">新增</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
v-for
、v-if
存在于同一个节点时,v-for
拥有更高的优先级,此时v-if
失效
数据,方法,计算属性和侦听器
computed
和method
都能实现的一个功能,建议使用 computed,因为有缓存computed
和watched
都能实现的功能,建议使用computed
因为更加简洁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 8</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// data & methods & computed & watcher
const app = Vue.createApp({
data() {
return {
message: "hello world",
count: 2,
price: 5,
newTotal: 10,
}
},
watch: {
// price 发生变化时,函数会执行
price(current, prev) {
this.newTotal = current * this.count;
}
},
computed: {
// 当计算属性依赖的内容发生变更时,才会重新执行计算
total() {
return Date.now() + this.count;
// return this.count * this.price
}
},
methods: {
formatString(string) {
return string.toUpperCase();
},
// 只要页面重新渲染,才会重新计算
getTotal() {
return Date.now();
// return this.count * this.price;
},
},
template: `
<div> {{message}} {{newTotal}} </div>
`
});
const vm = app.mount('#root');
</script>
</html>
样式绑定语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 9</title>
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
// 直接传入字符串进行样式类配置
classString: 'red',
// 通过对象控制样式类是否生效
classObject: {red: false, green: true},
// 通过数组配置样式类,可接受对象形式元素
classArray: ['red', 'green', {brown: false}],
// 字符串形式配置内联样式
styleString: 'color: yellow;background: orange',
// 对象形式配置内联样式
styleObject: {
color: 'orange',
background: 'yellow'
}
}
},
template: `
<div :style="styleObject">
Hello World
</div>
`
});
app.component('demo', {
// $attr.class:获取组件的指定(class)属性
template: `
<div :class="$attrs.class">one</div>
<div :class="$attrs.class">two</div>
`
})
const vm = app.mount('#root');
</script>
</html>
事件绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 12</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// event, $event
// 事件修饰符:
// stop(阻止事件冒泡)
// prevent(阻止默认事件)
// capture
// self(仅自身触发,不适用于子节点)
// once(仅生效一次)
// passive
// 按键修饰符(@keydown):enter, tab, delete, esc, up, down, left, right
// 鼠标修饰符:left, right, middle
// 精确修饰符:exact
const app = Vue.createApp({
methods: {
// 若方法未传入参数,则第一位为原生事件对象
handleClick(event) {
// 方法有参数时接收原生事件对象
handleClick(val, event) {
console.log('click')
},
handleClick1(val, event) {
console.log('click')
},
},
template: `
<div>
<div @click.ctrl.exact="handleClick">123</div>">123</div>
</div>
`
// 方法存在自定义参数情况下传入原生事件对象
// <div @click.ctrl.exact="handleClick(val, $event)
// 要一次性执行多个方法,可直接将方法调用传入并以 ‘,’ 隔开,不支持传入方法本身
// <div @click.ctrl.exact="handleClick(val, $event), handleClick1(val, $event)"></div>
});
const vm = app.mount('#root');
</script>
</html>
双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 13</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// input, textarea, checkbox, radio, select
// 修饰符 lazy(控件失焦时更新), number(转化输入数据类型), trim(去除头尾空格)
const app = Vue.createApp({
data() {
return {
message: 'hello',
}
},
template: `
<div>
{{ message }}
<input v-model.trim="message"/>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
Vue 组件
概念定义
组件的定义:
- 组件具备复用性
- 全局组件,只要定义了,处处可以使用,性能不高,但是使用起来简单,名字建议 小写字母单词,中间用横线间隔
- 局部组件,定义了,要注册之后才能使用,性能比较高,使用起来有些麻烦,建议大些字母开头,驼峰命名
- 局部组件使用时,要做一个名字和组件间的映射对象,你不写映射,Vue 底层也会自动尝试帮你做映射
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 14</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<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,
// 'hello-world': HelloWorld,
Counter, HelloWorld,
},
template: `
<div>
<hello-world/>
<counter/>
</div>
`
});
// 通过 component 创建的组件可全局使用,但会永久占用空间
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>
</html>
组件间传值及传值校验
- 使用
provide
、inject
进行多层组件传值,祖组件使用provide
向后代组件提供数据,后代组件使用inject
从祖组件注入数据。provide
和inject
绑定并不是可响应的,除非传递一个数据对象(例如组件自身对象)。 - 若想要给后代组件传递自身可变属性,可传递一个方法,后代组件接收到并运行即可获取当前数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 15</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {num: 1234}
},
// 第一种
provide(){
return{
foo:'halo'
}
},
// 第二种
provide:{
foo:'halo~~~~',
// 返回自身的属性
num: () => this.num
},
template: `
<div>
<test :content="num"/>
</div>
`
});
// type(数据类型):String, Boolean, Array, Object, Function, Symbol
// required 必填
// default 默认值
app.component('test', {
props: {
content: {
type: Number,
// 对从父组件接受的属性值进行校验
validator: function (value) {
return value < 1000;
},
default: function () {
return 456;
}
}
},
// 从祖节点注入
inject:['foo'],
template: `
<div>{{ content }}</div>`
});
const vm = app.mount('#root');
</script>
</html>
单向数据流的理解
v-bind="params"
等价于:content="params.content" :a="params.a" :b="params.b" :c="params.c"
- 属性传的时候,使用
content-abc
这种命名,接的时候,使用contentAbc
命名 - 单项数据流的概念: 子组件可以使用父组件传递过来的数据,但是绝对不能修改传递过来的数据
Non-props
- 若父组件向子组件传值时子组件并没有接收(使用
props
),则 Vue 会将父组件传递的内容作为属性放置在子组件最外层的 dom 节点上。 - 要想规避 “1” 中规则,可使用
inheritAttrs: false
特性,阻止子组件继承父组件传递的属性。 this.$attrs
:父组件传递的属性对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 17</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>
<counter msg="hello" msg1="hello1" />
</div>
`
});
app.component('counter', {
// inheritAttrs: false,
mounted() {
console.log(this.$attrs.msg);
},
template: `
<div :msg="$attrs.msg">Counter</div>
<div v-bind="$attrs">Counter</div>
<div :msg1="$attrs.msg1">Counter</div>
`
});
const vm = app.mount('#root');
</script>
</html>
组件间事件通信
this.$emit()
:触发父组件事件,由父组件调用子组件时设置监听器。$emit()
中可传值,传递的值会自动传入父组件监听器对应的方法中。$emit()
可实现类似v-modal
的双向绑定效果,当使用默认v-modal
时,子组件内接收的属性名必须为 “modalValue",此时$emit
触发的事件为"update:modalValue"
,传递过去的值也会赋值给父组件中v-modal
中对应的属性。若想自定义属性名,可在父组件中使用v-modal:[属性名]
的形式监听对应变量的变化。- 使用
modeModifiers
定义属性修改器。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 18</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {count: 1}
},
template: `
<counter v-model.tostring="count"/>
`
});
app.component('counter', {
props: {
'modelValue': String,
'modeModifiers': {
tostring: (val) => val.toString();
}
},
methods: {
handleClick() {
this.$emit('update:modelValue', this.modelValue + 3);
}
},
template: `
<div @click="handleClick">{{ modelValue }}</div>
`
});
const vm = app.mount('#root');
</script>
</html>
使用插槽、具名插槽和作用域插槽解决组件内容传递问题
- slot 中使用的数据,作用域的问题
- 父模版里调用的数据属性,使用的都是父模版里的数据
- 子模版里调用的数据属性,使用的都是子模版里的数据
- 可直接在子组件的 slot 标签内定义默认节点,当父组件未传入插槽内容时,默认显示该节点内容。
- 具名插槽与作用域插槽
- 子组件中用 name 属性来表示插槽的名字,不传为默认插槽,父组件传入时指定插槽名字可使节点内容显示在指定区域。
- 在 vue2.6 中,父组件中插槽的 “slot” 写法被软废弃(3.0正式废弃),取而代之的是内置指令
v-slot
,可以缩写为【#】,子组件中用法不变 - 默认插槽名为 default,可以省略 default 直接写
v-slot
,
缩写为 # 时不能不写参数,写成#default
(这点所有指令都一样,v-bind
、v-on
) - 多个插槽混用时,v-slot不能省略default
v-slot
属性只能在<template>
上使用,但在【只有默认插槽时】可以在组件标签上使用- 作用域插槽则是通过
slot-scope
获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性 slot-scope
属性弃用,作用域插槽通过v-slot:xxx="slotProps"
的slotProps
来获取子组件传出的属性- 可以通过解构获取
v-slot={item}
,还可以重命名v-slot="{item: newItem}"
和定义默认值v-slot="{item = '默认值'}"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 20</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
// 具名插槽
<layout>
<template #header>
<div>header</div>
</template>
<template v-slot:footer>
<div>footer</div>
</template>
</layout>
// 作用域插槽
// 可使用解构语法直接获取指定属性名
<list v-slot="{item}">
<div>{{item}}</div>
</list>
`
});
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
});
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');
</script>
</html>
动态组件与异步组件
- 动态组件: 根据数据的变化,结合
compoent
这个标签,来随时动态切换组件的现实- 要想切换组件时保存数据,可使用
keep-alive
标签来缓存第一次渲染后的输入状态以及变更情况。
- 要想切换组件时保存数据,可使用
- 异步组件: 是异步执行某些组件的逻辑,这叫做异步组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 21</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>
// 动态组件
<keep-alive>
<compoent :is="compoentFlag">
</keep-alive>
<common-item />
<async-common-item />
</div>
`
});
app.component('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');
</script>
</html>
Vue 动画
基础 CSS 过渡与动画效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 23</title>
<style>
/* 动画
@keyframes leftToRight {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
0% {
transform: translateX(0px);
}
}
.animation {
animation: leftToRight 3s;
} */
/* 过渡
.transition {
transition: 3s background-color ease;
}
.blue {
background: blue;
}
.green {
background: green;
} */
.transition {
transition: 3s background-color ease;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
styleObj: {
background: 'blue'
}
}
},
methods: {
handleClick() {
if (this.styleObj.background === 'blue') {
this.styleObj.background = 'green';
} else {
this.styleObj.background = 'blue'
}
}
},
template: `
<div>
<div class="transition" :style="styleObj">hello world</div>
<button @click="handleClick">切换</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
使用 transition 标签实现单元素组件的过渡和动画效果
- 使用
transition
标签包裹节点可为节点添加过渡或动画效果。 - 默认情况下使用
v-[动作(enter-from、enter-to、leave-from、deave-to)]
命名 css 类即可触发对应效果,同时可使用name
指定效果前缀。 - 可使用
enter-from-class
、enter-to-class
之类的属性自定义效果类,此时直接传入类名即可生效。 - 可使用 animate.css 之类的第三方动画库
- 使用
transition
的type
属性指定基准效果(animation / translation),效果的持续时间以基准效果为准。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 24</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<style>
@keyframes shake {
0% {
transform: translateX(-100px)
}
50% {
transform: translateX(-50px)
}
100% {
transform: translateX(50px)
}
}
.hello-leave-active {
animation: shake 3s;
}
.hello-enter-active {
animation: shake 3s;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 单元素,单组件的入场出场动画
const app = Vue.createApp({
data() {
return {
show: false
}
},
methods: {
handleClick() {
this.show = !this.show;
}
},
template: `
<div>
<transition name="hello">
<div v-if="show">hello world</div>
</transition>
// 使用第三方动画库
<transition
enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__bounce">
<div v-if="show">hello world</div>
</transition>
<button @click="handleClick">切换</button>
</div>
`
});
const app = Vue.createApp({
data() {
return {
show: false
}
},
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 = 'green';
} else {
el.style.color = 'red';
}
}, 1000)
setTimeout(() => {
clearInterval(animation);
done();
}, 3000)
},
handleEnterEnd(el) {
alert(123);
}
},
template: `
<div>
<transition
:css="false"
@before-enter="handleBeforeEnter"
@enter="handleEnterActive"
@after-enter="handleEnterEnd"
>
<div v-show="show">hello world</div>
</transition>
<button @click="handleClick">切换</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
组件和元素切换动画的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<title>lesson 25</title>
<style>
.v-leave-to {
opacity: 0;
}
.v-enter-from {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s ease-in;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 多个单元素标签之间的切换
// 多个单组件之间的切换
const ComponentA = {
template: '<div>hello world</div>'
}
const ComponentB = {
template: '<div>bye world</div>'
}
const app = Vue.createApp({
data() {
return {component: 'component-a'}
},
methods: {
handleClick() {
if (this.component === 'component-a') {
this.component = 'component-b';
} else {
this.component = 'component-a';
}
},
},
components: {
'component-a': ComponentA,
'component-b': ComponentB,
},
template: `
<div>
<transition mode="out-in" appear>
<component :is="component"/>
</transition>
<button @click="handleClick">切换</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
列表动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<title>lesson 26</title>
<style>
.v-enter-from {
opacity: 0;
transform: translateY(30px);
}
.v-enter-active {
transition: all .5s ease-in;
}
.v-enter-to {
opacity: 1;
transform: translateY(0);
}
.v-leave-active {
transition: all .5s ease-in;
}
.v-leave-to {
opacity: 1;
transform: translateY(-30px);
}
.v-move {
transition: all .5s ease-in;
}
.list-item {
display: inline-block;
margin-right: 10px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 列表动画的实现
const app = Vue.createApp({
data() {
return {list: [1, 2, 3]}
},
methods: {
handleClick() {
this.list.unshift(this.list.length + 1);
},
handleDelete() {
this.list.pop();
},
},
template: `
<div>
<button @click="handleClick">增加</button>
<button @click="handleDelete">删除</button>
<transition-group>
<span class="list-item" v-for="item in list" :key="item">{{ item }}</span>
</transition-group>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
状态动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 27</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 状态动画
const app = Vue.createApp({
data() {
return {
number: 1,
animateNumber: 1
}
},
methods: {
handleClick() {
this.number = 100 + this.number;
if (this.animateNumber < this.number) {
const animation = setInterval(() => {
this.animateNumber += 1;
if (this.animateNumber === this.number) {
clearInterval(animation);
}
}, 10);
}
},
},
template: `
<div>
<div>{{ animateNumber }}</div>
<button @click="handleClick">增加</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
Vue 高级语法
Mixin 混入基础
- 组件
data
,methods
优先级高于 mixindata
,methods
优先级 - 生命周期函数,先执行 mixin 里面的,再执行组件里面的
- 自定义的属性,组件种的属性优先级高于 mixin 属性的优先级
- 混入会造成代码维护困难,一般情况下可使用 Composition API(Vue 3.0)或者插件替代。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 28</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const myMixin = {
number: 1
}
const app = Vue.createApp({
mixins: [myMixin],
number: 2,
template: `
<div>
<div>{{ this.$options.number }}</div>
</div>
`
});
// 修改自定义属性的使用优先级
app.config.optionMergeStrategies.number = (mixinVal, appValue) => {
return mixinVal || appValue;
}
const vm = app.mount('#root');
</script>
</html>
开发实现 Vue 中的自定义指令
- 使用 Vue 核心对象的
directive
方法自定义全局指令- 可直接传入一个由生命周期函数组成的对象,若对象内仅存在
mounted
、updated
生命周期函数,则可以是直接使用函数替代。 - 定义全局指令时,会自动传入两个参数,第一个
el
是 Dom 节点本身,第二个binding
为绑定的参数,其中arg
为绑定的参数别名,value
为绑定的值。
- 可直接传入一个由生命周期函数组成的对象,若对象内仅存在
- 使用组价的
directives
属性临时导入自定义指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 29</title>
<style>
.header {
position: absolute
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
临时导入自定义指令 directives
const directives = {
focus: {
mounted(el) {
el.focus();
}
}
}
const app = Vue.createApp({
directives: directives,
data() {
return {
distance: 110
}
},
template: `
<div>
<div v-pos:right="distance" class="header">
<input/>
</div>
</div>
`
});
// 自定义全局指令 directive
app.directive('focus', {
mounted(el) {
el.focus();
}
})
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px');
})
const vm = app.mount('#root');
</script>
</html>
Teleport 传送门功能
使用 Teleport 传送门可将节点或组件传送到指定标签上,teleport
标签中使用 to
属性指定传送的目标标签,类似 Dom 节点定位方式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 30</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 300px;
background: green;
}
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
opacity: 0.5;
color: #fff;
font-size: 100px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
<div id="hello"></div>
</body>
<script>
// teleport 传送门
const app = Vue.createApp({
data() {
return {
show: false,
message: 'hello'
}
},
methods: {
handleBtnClick() {
this.show = !this.show;
}
},
template: `
<div class="area">
<button @click="handleBtnClick">按钮</button>
<teleport to="#hello">
<div class="mask" v-show="show">{{ message }}</div>
</teleport>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
render 函数
template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 31</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// render function
const app = Vue.createApp({
template: `
<my-title :level="2">
hello dell
</my-title>
`
});
app.component('my-title', {
props: ['level'],
render() {
const {h} = Vue;
return h('h' + this.level, {}, [
this.$slots.default(),
h('h4', {}, 'dell')
])
}
})
const vm = app.mount('#root');
</script>
</html>
插件的定义和使用
- plugin 插件, 是把通用性的功能封装起来
- 使用 Vue 核心对象的
use
方法载入插件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 32</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const myPlugin = {
install(app, options) {
app.provide('name', 'Dell Lee');
app.directive('focus', {
mounted(el) {
el.focus();
}
})
app.mixin({
mounted() {
console.log('mixin')
}
})
// 扩展全局变量
app.config.globalProperties.$sayHello = 'hello world';
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
inject: ['name'],
mounted() {
// 使用全局变量
console.log(this.$sayHello);
},
template: `<div>{{name}}<input v-focus /></div>`
})
app.use(myPlugin, {name: 'dell'});
const vm = app.mount('#root');
</script>
</html>
数据校验插件开发实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 33</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 对数据做校验的插件
const app = Vue.createApp({
data() {
return {name: 'dell', age: 23}
},
rules: {
age: {
validate: age => age > 25,
message: 'too young, to simple'
},
name: {
validate: name => name.length >= 4,
message: 'name too short'
}
},
template: `
<div>name:{{ name }}, age:{{ age }}</div>
`
});
const validatorPlugin = (app, options) => {
app.mixin({
created() {
for (let key in this.$options.rules) {
const item = this.$options.rules[key];
this.$watch(key, (value) => {
const result = item.validate(value);
if (!result) console.log(item.message);
})
}
}
})
}
app.use(validatorPlugin);
const vm = app.mount('#root');
</script>
</html>
Composition API
Setup 函数的使用
setup
函数执行于created
之前,需要两个参数:props
(外部传入属性),context
(上下文)setup
函数中无法使用this
(处于created
之前,组件未创建成功。)setup
中返回的属性 / 方法会暴露在组件中供组件使用。- 组件创建挂载完成后可直接调用
setup
函数,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 34</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{ name }}</div>
`,
methods: {
test() {
console.log(this.$options.setup());
}
},
mounted() {
this.test();
},
// created 实例被完全初始化之前
setup(props, context) {
return {
name: 'dell',
handleClick: () => {
alert(123)
}
}
}
});
const vm = app.mount('#root');
</script>
</html>
ref,reactive 响应式引用的用法和原理
- 原理:通过 proxy 对数据进行封装,当数据变化时,触发模版等内容的更新
ref
处理基础类型的数据- proxy ,
'dell'
变成proxy({value: 'dell'})
这样的一个响应式引用 - 调用
ref
生成变量时,使用变量名.value
,但 Vue 3 底层会自动调用 value ,因此使用时与一般情况相同即可
- proxy ,
reactive
处理非基础类型的数据- proxy ,
{ name: 'dell' }
变成proxy({ name: 'dell'})
这样的一个响应式引用 - 若
setup
中直接将reactive
引用的属性导出(let { name } = reactive({ name: val });
),此时仅导出一个普通变量,若要使此变量可响应,可使用toRefs
方法。
- proxy ,
- Vue 3 中
ref
与reactive
可代替data
函数,因此使用此语法时可省略data
函数。 readonly
生成一个只读数据toRefs
可将一个reactive
引用的值变为ref
引用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 35</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{ name }}</div>
`,
setup(props, context) {
// const { ref } = Vue;
//
// let name = ref('dell');
// setTimeout(() => {
// name.value = 'lee'
// }, 2000)
// return { name }
const {reactive, readonly, toRefs} = Vue;
//
const nameObj = reactive({name: 'dell', age: 28});
setTimeout(() => {
nameObj.name = 'lee'
}, 2000)
// toRefs proxy({ name: 'dell', age: 28}), {
// name: proxy({ value: 'dell'}),
// age: proxy({value: 28})
// }
const {name, age} = toRefs(nameObj);
return {name}
}
});
const vm = app.mount('#root');
</script>
</html>
toRef 以及 context 参数
toRef
旨在当响应式引用中没有属性的时候给予一个默认值,此时变量无需导出:let age = toRef({ name = 'liu' }, 'default')
context
:包含上下文环境,常用属性:attrs
:包含所有Non-props
(父组件中传入但未在自组价中使用props
接收的参数)slots
:包含所有插槽,等价于this.$slots
emit
:等价于this.$emit()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 36</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// toRef, context
const app = Vue.createApp({
methods: {
handleChange() {
alert('change');
}
},
template: `
<child @change="handleChange">parent</child>`,
});
app.component('child', {
template: '<div @click="handleClick">123123</div>',
setup(props, context) {
const {h} = Vue;
const {attrs, slots, emit} = context;
function handleClick() {
emit('change');
}
return {handleClick}
}
})
const vm = app.mount('#root');
</script>
</html>
使用 Composition API 开发 TodoList
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 37</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 关于 list 操作的内容进行了封装
const listRelativeEffect = () => {
const {reactive} = Vue;
const list = reactive([]);
const addItemToList = (item) => {
list.push(item);
}
return {list, addItemToList}
}
// 关于 inputValue 操作的内容进行了封装
const inputRelativeEffect = () => {
const {ref} = Vue;
const inputValue = ref('');
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return {inputValue, handleInputValueChange}
}
const app = Vue.createApp({
setup() {
// 流程调度中转
const {list, addItemToList} = listRelativeEffect();
const {inputValue, handleInputValueChange} = inputRelativeEffect();
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');
</script>
</html>
computed方法生成计算属性
- 使用 Vue 的
computed
方法,传入一个回调方法进行属性计算 - 可传入带有
get
、set
方法的对象来设置计算属性的取值赋值方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 38</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 计算属性
const app = Vue.createApp({
setup() {
const {reactive, computed} = Vue;
const countObj = reactive({count: 0});
const handleClick = () => {
countObj.count += 1;
}
let countAddFive = computed({
get: () => {
return countObj.count + 5;
},
set: (param) => {
countObj.count = param - 5;
}
})
setTimeout(() => {
countAddFive.value = 100;
}, 3000)
return {countObj, countAddFive, handleClick}
},
template: `
<div>
<span @click="handleClick">{{ countObj.count }}</span> -- {{ countAddFive }}
</div>
`,
});
const vm = app.mount('#root');
</script>
</html>
watch 和 watchEffect 的使用和差异性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 39</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const {reactive, watch, watchEffect, toRefs} = Vue;
const nameObj = reactive({
name: 'dell', englishName: 'lee'
})
// watch 侦听器
// 具备一定的惰性 lazy
// 参数可以拿到原始和当前值
// 侦听 reactive 的值时,需要传入一个侦听函数,返回需要侦听的值。
// 可以侦听多个数据的变化,用一个侦听器承载
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, preEng]) => {
console.log('watch', curName, prevName, '---', curEng, preEng);
}, {immediate: true})
// watchEffect 侦听器,偏向于 effect
// 立即执行,没有惰性 immediate
// 不需要传递你要侦听的内容,自动会感知代码依赖,不需要传递很多参数,只要传递一个回调函数
// 不能获取之前数据的值
const stop = watchEffect(() => {
console.log(nameObj.name);
console.log(nameObj.englishName);
setTimeout(() => {
stop();
}, 5000)
})
const {name, englishName} = toRefs(nameObj);
return {name, englishName}
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`,
});
const vm = app.mount('#root');
</script>
</html>
生命周期函数的新写法
- Composition API 的
setup
函数执行时间介于旧生命周期的beforeCreated
与created
之间,因此 Composition API 未提供这两个生命周期函数,相关的操作直接在setup
函数内进行即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 40</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
// beforeMount => onBeforeMount
// mounted => onMounted
// beforeUpdate => onBeforeUpdate
// beforeUnmount => onBeforeUnmount
// unmouted => onUnmounted
setup() {
const {
ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onRenderTracked, onRenderTriggered
} = Vue;
const name = ref('dell')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
// 每次渲染后重新收集响应式依赖
onRenderTracked(() => {
console.log('onRenderTracked')
})
// 每次触发页面重新渲染时自动执行
onRenderTriggered(() => {
console.log('onRenderTriggered')
})
const handleClick = () => {
name.value = 'lee'
}
return {name, handleClick}
},
template: `
<div @click="handleClick">
{{name}}
</div>
`,
});
const vm = app.mount('#root');
</script>
</html>
Provide,Inject,模版 Ref 的用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 41</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
provide, inject
dom ref
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue;
const name = ref('dell');
// 限制后代组件对 name 属性的修改
provide('name', readonly(name));
// 为后代组件开放 name 属性修改方法
provide('changeName', (value) => {
name.value = value;
});
return { }
},
template: `
<div>
<child />
</div>
`,
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name');
const changeName = inject('changeName');
const handleClick = () => {
changeName('lee');
}
return { name, handleClick }
},
template: '<div @click="handleClick">{{name}}</div>'
})
// Composition API 的语法下,获取真实的 DOM 元素节点
// 使用 ref(null) 定义一个 ref 节点,接收的变量名需与目标节点的 ref 属性相同
const app = Vue.createApp({
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');
</script>
</html>
Vue 项目开发配套工具讲解
VueCLI 的使用和单文件组件
nrm
工具:包含国内常用镜像源nrm ls
:查看镜像列表nrm use <镜像名>
:使用对应镜像
npm install -g @vue/cli
安装最新脚手架工具vue create <项目名>
:创建新项目
- 单文件组件:一个文件对应一个组件,由
<template>
(模板区域)、<script>
(逻辑渲染区域)、<style>
(样式区域)三个区域组成 - 简单实例
<!-- index.html - 入口 HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
// main.js
import { createApp } from 'vue'
import App from './App.vue'
// 将创建的核心对象绑定在 ID 为 "app" 的节点上
createApp(App).mount('#app')
<!-- App.vue - 根组件 -->
<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>
<!-- HelloWorld.vue - 子组件 -->
<template>
<h1>{{ msg }}</h1>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<style>
</style>
使用单文件组件编写 TodoList
- 子组件会继承父组件样式,但当
<sytle>
带有scoped
属性时,即<style scoped>
此时样式仅在当前组件中生效。 - 前端工程化的演进历程以及演进过程中的关键节点
- Node 对于前端工程化的意义
<!-- App.vue - 根组件 -->
<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';
import ListItem from './components/ListItem';
export default {
name: 'App',
components: {ListItem},
setup() {
const inputValue = ref('');
const list = reactive([]);
const handleAddItem = () => {
list.push(inputValue.value);
inputValue.value = '';
};
return {handleAddItem, inputValue, list}
}
}
</script>
<style>
.button {
margin-left: 20px;
color: red;
}
</style>
<!-- ListItem.vue - 子组件 -->
<template>
<li class="button">{{ msg }}</li>
</template>
<script>
export default {
name: 'ListItem',
props: {
msg: String
}
}
</script>
<style>
</style>
Vue-Router 路由的理解和使用
- 路由是指根据U川的不同,展示不同的内容
- 带
#
为hash路由 - 异步加载路由:用到的时候再加载,减少首页的加载,但是
会造成页面跳转的时候加载较慢
- 带
<router-link>
:是跳转路由的标签<router-view>
:负责展示当前路由对应的组件路由- route 实例
// route.js - 路由
import {createRouter, createWebHashHistory} from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
const routes = [
{
path: '/',
name: 'Home',
// 页面加载完成即导入
component: Home
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/about',
name: 'About',
// 异步加载路由,仅在触发后请求加载
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
<!-- App.vue - 入口文件 -->
<template>
<div id="nav">
<!-- router-link 是跳转路由的标签 -->
<router-link to="/">Home</router-link>
|
<router-link to="/about">About</router-link>
|
<router-link to="/login">Login</router-link>
</div>
<!-- router-view 负责展示当前路由对应的组件内容 -->
<router-view/>
</template>
<style></style>
VueX 的语法详解
- VueX:数据管理框架
- VueX创建了一个全局唯一的仓库
store
,用来存放全局的数据 - 数据定义由
store
中的state
完成
- VueX创建了一个全局唯一的仓库
- VueX 全局数据修改流程(异步)
dispatch
方法,派发一个 action,方法名为change
this.$store.dispatch('change', str)
dispatch
和actions
做关联,change
为actions
中的方法
- 感知到
change
这个 action,执行store
中actions
下面的change
方法 commit
提交一个 mutation,方法名为change
store.commit('change', str)
commit
和 mutation 做关联,change
为mutations
中的方法
- 感知到提交的
change
改变,执行store
中mutations
下面的change
方法改变数据mutations
里面只允许写同步代码,不允许写异步代码(非强制,仅为代码设计要求)- 异步代码交由
actions
处理
- 若仅需同步修改数据,可跳过
dispatch
流程,直接使用commit
触发 mutation actions
中的方法第一个参数为store
对象;mutations
中的方法第一个参数为store
中的state
对象- 简单实例
// 全局store的定义
// store/index.js
import {createStore} from 'vuex'
// VueX 数据管理框架
// VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
// 定义全局数据
state: {
name: 'dell'
},
// mutation 里面只允许写同步代码,不允许写异步代码
// commit 和 mutation 做关联
mutations: {
change(state, str) {
state.name = str;
}
},
// dispatch 和 actions 做关联
actions: {
change(store, str) {
setTimeout(() => {
store.commit('change', str)
}, 2000)
}
}
})
<!-- 组件使用 -->
<!-- views/Home.vue -->
<template>
<div class="home">
<h1 @click="handleClick">This is a home page</h1>
<h1>{{ myName }}</h1>
</div>
</template>
<script>
export default {
name: 'Home',
computed: {
myName() {
return this.$store.state.name;
}
},
methods: {
handleClick() {
// 异步修改全局数据
this.$store.dispatch('change', 'hello world');
// 同步修改全局数据
this.$store.commit('change', 'hello world');
}
}
}
</script>
CompositionAPI 中如何使用 VueX
- CompositionAPI 使用的是
useStore
获取 store 对象 - 简单实例
// store/index.js
import {createStore} from 'vuex'
export default createStore({
state: {name: 'dell'},
mutations: {
changeName(state, str) {
state.name = str;
}
},
actions: {
getData(store) {
setTimeout(() => {
store.commit('changeName', 'hello')
}, 2000)
}
}
})
<!-- views/Home.vue -->
<template>
<div class="home">
<h1 @click="handleClick">This is a home page</h1>
<h1>{{ name }}</h1>
</div>
</template>
<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';
export default {
name: 'Home',
setup() {
const store = useStore();
const {name} = toRefs(store.state);
const handleClick = () => {
store.dispatch('getData')
}
return {name, handleClick}
}
}
</script>