一、安装
安装Vue官方脚手架以及创建项目
Vue 官网地址:cn.vuejs.org/
Vue3.x Githtub地址: github.com/vuejs/vue-n…
Vue3.x 文档地址: v3.vuejs.org/
Vue3.x 中文文档地址:v3.cn.vuejs.org/
注意: 安装脚手架创建项目之前之前,我们的电脑上必须得安装Nodejs,推荐安装nodejs稳定版本
- 安装Vue-cli,在同一个电脑上面只需要安装一次
npm uninstall -g vue-cli // 卸载2.0 // 更新npm版本
npm install -g npm
npm install -g @vue/cli // 更新,下载安装
E:\node_global>vue -V // 查看 版本
- 通过Vue-cli创建项目 首先cd到项目要存放的文件夹目录下
vue create hello-vue3
- 项目启动
npm run serve - cd 到 hello-vue3 运行 npm i 安装依赖
- vue-cli3运行npm run serve修改为npm run dev
- 找到package.json文件,打开文件找到"scripts",将"serve"改为"dev"
- "serve": "vue-cli-service serve" 这一行,把前面的 serve 修改 dev 后保存文件
二、绑定数据
v-bind动态参数
<a v-bind:[attributeName]="url"> ... </a>
这里attributeName将被动态地评估为JavaScript表达式,并且其评估值将用作参数的最终值。例如,如果您的组件实例具有一个数据属性attributeName,其值为"href",则此绑定将等效于v-bind:href
export default {
name: "App",
data() {
return {
attributeName: "href",
linkUrl: "http://www.itying.com",
};
},
};
<a v-bind:[attributeName]="linkUrl"> 这是一个地址 </a>
或者
<a :[attributeName]="linkUrl"> 这是一个地址 </a>
v-for循环对象
(value, name, index) in myObject
export default {
name: "App",
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2020-03-22'
}
};
},
};
<ul id="v-for-object" class="demo">
<li v-for="(value, name, index) in myObject" :key="index">
{{ name }}: {{ value }}--{{index}}
</li>
</ul>
三、类和样式绑定
v-bind:class绑定对象
export default {
name: "App",
data() {
return {
isActive: true,
hasError: false
};
},
};
<div class="static" :class="{ 'active': isActive, 'error': hasError }">
v-bind:class演示
</div>
v-bind:style 绑定内联样式
第一种绑定方式
data() {
return {
activeColor: 'red',
fontSize: 30
}
}
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
第二种绑定方式
data() {
return {
baseStyles: {
color: 'orange',
fontSize: '13px'
},
overridingStyles: {
width: "100px",
height: "100px",
background: "blue"
}
}
}
<div :style="[baseStyles, overridingStyles]"></div>
自动前缀
当您使用需要一个CSS属性供应商前缀的:style,例如transform,Vue公司会自动检测并添加适当的前缀到应用的样式。
多个值
您可以为样式属性提供多个(前缀)值的数组,例如:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
这只会呈现浏览器支持的数组中的最后一个值。在此示例中,它将display: flex为支持非前缀版本的flexbox的浏览器呈现。
案例:循环数据 第一个数据高亮显示
<ul>
<li v-for="(item,index) in list" :key="index" :class="{'red':index==0,'blue':index==1}">{{item}}</li>
</ul>
四、事件
vue中监听事件可以使用v-on:click或者 @click ,@click 为v-on:click的简写。
$event
有时我们还需要在内联语句处理程序中访问原始DOM事件。您可以使用特殊$event变量将其传递给方法。
<button data-aid='123' @click="eventFn($event)">事件对象</button>
eventFn(e){
console.log(e);
// e.srcElement dom节点
e.srcElement.style.background='red';
console.log(e.srcElement.dataset.aid); /*获取自定义属性的值*/
}
多个参数时,$event放在最后
<button @click="warn('Form cannot be submitted yet.', $event)">Submit</button>
多事件处理程序
可以在事件处理程序中使用逗号分隔多个事件处理程序
methods: {
one(event) {
// first handler logic...
},
two(event) {
// second handler logic...
}
}
<button @click="one($event), two($event)">Submit</button>
事件修饰符
vue中阻止冒泡 阻止默认行为,可以通过事件对象event.preventDefault()或event.stopPropagation()实现,还可以通过事件修饰符实现。
// vue中给我们提供了很多的修饰符:
.stop
.prevent
.capture
.self
.once
.passiv
// 例:stopPropagation
<a @click.stop="doThis"></a>
// preventDefault
<a @click.prevent="doThat"></a>
// stopPropagation And preventDefault
<a @click.stop.prevent="doThat"></a>
按键修饰符
监听键盘事件时,我们通常需要检查特定的键。Vue允许在监听关键事件时v-on或@在监听关键事件时添加按键修饰符:
<input @keyup.enter="submit" />
Vue为最常用的键提供别名:
.enter.tab.delete(同时捕获“删除”和“退格”键).esc.space.up.down.left.right
五、 input、checkbox、radio、select、 textarea中的双向数据绑定
<template>
<h2>人员登记系统</h2>
<div class="people_list">
<ul>
<li>姓 名: <input type="text" v-model="peopleInfo.username" /></li>
<li>年 龄: <input type="text" v-model="peopleInfo.age" /></li>
<li>性 别:</li>
<input type="radio" value="1" id="sex1" v-model="peopleInfo.sex" />
<label for="sex1">男</label>
<input type="radio" value="2" id="sex2" v-model="peopleInfo.sex" />
<label for="sex2">女</label>
<li>
城 市:
<select name="city" id="city" v-model="peopleInfo.city">
<option v-for="(item, index) in peopleInfo.cityList" :key="index" :value="item">
{{ item }}
</option>
</select>
</li>
<li>
爱 好:
<span v-for="(item, index) in peopleInfo.hobby" :key="index">
<input type="checkbox" :id="'check' + index" v-model="item.checked" />
<label :for="'check' + key"> {{ item.title }}</label>
</span>
</li>
<li>
备 注:
<textarea name="mark" id="mark" cols="30" rows="4" v-model="peopleInfo.mark"></textarea>
</li>
</ul>
<button @click="doSubmit()" class="submit">获取表单的内容</button>
</div>
</template>
业务逻辑:
export default {
data() {
return {
peopleInfo: {
username: "",
age: "",
sex: "2",
cityList: ["北京", "上海", "深圳"],
city: "上海",
hobby: [{
title: "吃饭",
checked: false,
},
{
title: "睡觉",
checked: false,
},
{
title: "敲代码",
checked: true,
},
],
mark: "",
},
};
},
methods: {
doSubmit() {
console.log(this.peopleInfo);
},
},
};
六、JavaScript表达式 、条件判断、 计算属性和watch侦听
注意:双向数据绑定主要用于表单中
在 <template> 元素上使用 v-if 条件渲染分组
因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-if 和 v-show的区别
v-if是dom操作,v-show只是css的显示隐藏,一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好
计算属性
1、在模板中表达式非常便利,但是它们实际上只用于简单的操作。
2、模板是为了描述视图的结构。在模板中放入太多的逻辑会让模板过重且难以维护。这就是为什么 Vue.js 将绑定表达式限制为一个表达式。如果需要多于一个表达式的逻辑,应当使用计算属性。
computed: {
reverseMsg() {
return this.message.split("").reverse().join("")
},
```
searchList() {
var tempArr = [];
this.listData.forEach((value) => {
if (value.indexOf(this.keyword) != -1 && this.keyword != ""){
tempArr.push(value)
}
})
return tempArr;
}
<template>
{{reverseMsg}}
<ul>
<li v-for="(item,index) in searchList" :key="index">{{item}}</li>
</ul>
</template>
watch监听数据变化
Vue.js 提供了一个方法 watch ,它用于观察 Vue 实例上的数据变动。当一些数据需要根据其它数据变化时,watch 很诱人 —— 特别是如果你来自 AngularJS 。不过,通常更好的办法是使用计算属性而不是一个命令式的 watch 回调
export default {
data() {
return {
firstName: "",
lastName: "",
fullName: ""
};
},
watch: {
firstName: function (val) {
this.fullName = val + this.lastName;
},
lastName: function (val) {
this.fullName = this.firstName + val;
}
}
};
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
{{ fullName }}
七、Vue3.x中集成Sass/scsss
安装sass-loader node-sass
npm install -D sass-loader node-sass
style中配置sass/scss
lang可以配置scss ,scoped表示这里写的css只有当前组件有效
<style lang = "scss" scoped>
h2 {
text-align: center;
}
</style>
八、Vue3.x中的模块化以及封装Storage
Vue3.x 实现一个完整的toDoList(待办事项) 以及类似京东App搜索缓存数据功能
localStorage里面的方法
localStorage.setItem(key,value)
localStorage.getItem(key)
localStorage.removeItem(key);
localStorage.clear();
封装localStorage
1、新建models/storage.js
var storage = {
set(key,value){
localStorage.setItem(key,JSON.stringify(value));
},
get(key){
return JSON.parse(localStorage.getItem(key));
},
remove(key){
localStorage.removeItem(key);
}
}
export default storage;
2、引入
import storage from './model/storage.js';
九、组件
Props验证
条件不满足时,只会在浏览器控制台中提示警告。可在开发公共组件时使用。
父子 prop 之间单向下行绑定:父级 prop 的更新会向下流动到子组件中,反之不行。父级组件数据更新,子组件中所有的 prop 随之更新。防止子组件意外变更父级组件的状态,从而导致数据流向难以理解。
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function() {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function(value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
},
// 具有默认值的函数
propG: {
type: Function,
// 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
default: function() {
return 'Default function'
}
}
父组件主动获取子组件的数据和执行子组件方法
- 调用子组件的时候定义一个ref
<v-header ref="xxx"></v-header>
- 父组件主动获取子组件数据
this.$refs.xxx.属性
- 父组件主动执行子组件方法
this.$refs.xxx.方法
子组件主动获取父组件的数据和执行父组件方法
- 子组件主动获取父组件的数据
this.$parent.数据
- 子组件主动获取父组件的方法
this.$parent.方法
子组件给父组件传值
(注意: Vue官方推荐始终使用 kebab-case 的事件名)
- 子组件DatePicker.vue
<template>
<button @click="run">通过广播方式实现子组件给父组件传值</button>
</template>
<script>
export default {
// 建议定义所有发出的事件,以便更好地记录组件应该如何工作。
emits: ["run-parent"],
data() {
return {}
},
methods: {
run() {
this.$emit("run-parent", "这是子组件传过来的值")
}
}
}
</script>
- 父组件Home.vue
<template>
<div>
<date-picker @run-parent="getChild">
</date-picker>
</div>
</template>
<script>
import DatePicker from "./DatePicker"
export default {
data() {
return {
title: "你好vue"
}
},
components: {
DatePicker
},
methods: {
getChild(data) {
alert(data)
}
}
}
</script>
<style lang="scss">
</style>
自定义事件验证
只能控制台打印错误提示
// 子组件
export default {
// 建议定义所有发出的事件,以便更好地记录组件应该如何工作。
emits: {
submit: ({
username,
password
}) => {
if (username && password) {
return true
} else {
console.warn('传入的参数不能为空!')
return false
}
}
},
data() {
return {
username: "张三",
password: ""
}
},
methods: {
run() {
this.$emit("submit", {
username: this.username,
password: this.password
})
}
}
}
第三方插件mitt 实现非父子组件传值
Vue3.x以后从实例中移除了 $on,$off 和 $once 方法,$emit 仍然是现有 API 的一部分,只能实现子组件触发父组件的方法。
- 使用mitt之前先安装mitt模块
npm install --save mitt
- 新建model/event.js 引入
import mitt from 'mitt' // 引入
const VueEvent = mitt(); // 实例化
export default VueEvent; // 暴漏实例
引入后可通过 emit 广播数据,通过 on 监听数据。
import VueEvent from '../model/event'
export default {
methods: {
doLogin() {
VueEvent.emit("login");
}
}
}
import VueEvent from '../model/event'
export default {
mounted() {
VueEvent.on("login", () => {
alert("doLogin")
})
}
}
组件使用v-model实现双向数据绑定
1、单个v-mode数据绑定
默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:
<my-component v-model:foo="bar"></my-component>
在本例中,子组件将需要一个 foo prop 并发出 update:foo 要同步的事件:
const app = Vue.createApp({})
app.component('my-component', {
props: {
foo: String
},
template: `
<input
type="text"
:value="foo"
@input="$emit('update:foo', $event.target.value)">
`
})
2、多个 v-model 绑定
通过利用以特定 prop 和事件为目标的能力,正如我们之前在v-model 参数中所学的那样,我们现在可以在单个组件实例上创建多个 v-model 绑定。
每个 v-model 将同步到不同的 prop,而不需要在组件中添加额外的选项。
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
username.vue
<input
type="text"
:value="firstName"
@input="$emit('update:firstName',$event.target.value)">
<input
type="text"
:value="lastName"
@input="$emit('update:lastName',$event.target.value)">
props: {
firstName: String,
lastName: String
}
非 Prop 的Attribute 继承
- 一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应props定义的 attribute。常见的示例包括
class、style和id属性。 - 当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。
- 同样的规则适用于事件监听器
// 父组件:
<date-picker @change="submitChange"></date-picker>
// 子组件:
mounted() {
console.log(this.$attrs) // { onChange: () => {} }
}
示例:
// 子组件DatePicker.vue
<template>
<select>
<option value="1">Yesterday</option>
<option value="2">Today</option>
<option value="3">Tomorrow</option>
</select>
</template>
// 父组件
<date-picker @change="showChange"></date-picker>
methods: {
showChange(event) {
console.log(event.target.value) // 获取子组件选择的值
}
}
- 自定义 Attribute 继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置
inheritAttrs: false。例如:
禁用 attribute 继承的常见情况是需要将 attribute 应用于根节点之外的其他元素。
通过将 inheritAttrs 选项设置为 false,你可以访问组件的 $attrs property,该 property 包括组件 props 和 emits property 中未包含的所有属性 (例如,class、style、v-on 监听器等)。
// 子组件:
<div class="date-picker">
<input type="date" v-bind="$attrs" />
</div>
export default {
inheritAttrs: false,
data() {
return {}
}
}
// 父组件:
<template>
<date-picker data-status="activated"></date-picker>
</template>
// 渲染完成的效果:
<div class="date-picker">
<input type="datetime" data-status="activated" />
</div>
十、生命周期函数
生命周期
export default {
data () {
return {}
},
beforeCreate () {
console.log('实例刚刚被创建1')
},
create () {
console.log('实例已经创建完成2')
},
beforeMount () {
console.log('模板编译之前3')
},
mounted () {
/* 请求数据,操作dom,放在这个里面 mounted */
console.log('模板编译完成/数据渲染完成4')
},
beforeUpdate () {
console.log('数据更新之前')
},
updated () {
console.log('数据更新完成')
},
activated () {
console.log('keep-alive 缓存的组件激活时调用')
},
deactivated () {
console.log('keep-alive 缓存的组件停用时调用')
},
beforeUnmount () { // vue2.x中的beforeDestroy
/* 页面销毁的时候要保存一些数据,就可以监听这个销毁的生命周期函数 */
console.log('实例销毁之前')
},
unmounted () { // vue2.x 中的 destroyed
console.log('实例销毁完成')
}
}
- beforeCreate: 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
- created:
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,
$elproperty 目前尚不可用。 - beforeMount:
在挂载开始之前被调用:相关的
render函数首次被调用。 - mounted:
实例被挂载后调用,这时
Vue.createApp({}).mount()被新创建的vm.$el替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时vm.$el也在文档内。
注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm。$nextTick:
mounted() {
this.$nextTick(function () {
// 仅在渲染整个视图之后运行的代码
})
}
- beforeUpdate: 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
- updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或侦听器取而代之。
注意,updated 不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 vm.$nextTick:
updated() {
this.$nextTick(function () {
// 仅在渲染整个视图之后运行的代码
})
}
- activated: 被 keep-alive 缓存的组件激活时调用。
- deactivated: 被 keep-alive 缓存的组件停用时调用。
- beforeUnmount: 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
- unmounted: 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
动态组件 keep-alive
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,这个时候就用用keep-alive
在不同路由切换的时候想保持组件的状态也可以使用keep-alive
<keep-alive>
<life-cycle v-if="isShow"></life-cycle>
</keep-alive>
this.$nextTick
Vue中可以把获取Dom节点的代码放在mounted里面,但是如果要在渲染完成数据后获取DOM节点就需要用到this.$nextTick
mounted() {
this.$nextTick(function () {
// 仅在渲染整个视图之后运行的代码
})
},
mounted() {
var oDiv1 = document.querySelector("#msg");
console.log("1-" + oDiv1.innerHTML);
this.msg = "$nextTick演示";
var oDiv2 = document.querySelector("#msg");
console.log("2-" + oDiv2.innerHTML);
this.$nextTick(() => {
// 仅在渲染整个视图之后运行的代码
var oDiv3 = document.querySelector("#msg");
console.log("3-" + oDiv3.innerHTML);
})
},
十一、接口
Vue3.x全局绑定Axios
安装
npm install axios --save
全局绑定
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Axios from 'axios'
// 原来的代码
// createApp(App).mount('#app')
// 修改后的代码
const app = createApp(App);
app.config.globalProperties.Axios = Axios; // 使用this.Axios
app.mount('#app');
storage.js绑定全局
// main.js
import Storage from './models/storage'
app.config.globalProperties.Storage = Storage; // 使用this.Storage
使用fetch-jsonp请求jsonp接口
axios不支持jsonp请求,可以使用fetch-jsonp模块
// 安装
npm install fetch-jsonp --save
(--save会把依赖写入dependencies,工具类--save -dev 是写入devDependencies)
全局配置绑定
// main.js
import fetchJsonp from 'fetch-jsonp'
app.config.globalProperties.fetchJsonp = fetchJsonp;
请求接口时需要配置cb
getData() {
let api = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=php'
this.fetchJsonp(api, {
jsonpCallback: 'cb',
})
.then(function (response) {
return response.json()
}).then((data) => {
console.log(data)
this.list = data.s // 用到this一定要注意this指向
}).catch(function (error) {
console.log(error)
})
}
使用函数防抖实现搜索
getData() {
if (this.keyword != "") {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
let api = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + this.keyword
this.fetchJsonp(api, {
jsonpCallback: 'cb',
})
.then(function (response) {
return response.json()
}).then((json) => {
this.list = json.s
})
}, 200)
}
}
十二、Mixin
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
Mixin
1、新建mixin/base.js
const baseMixin = {
data() {
return{
apiDomain: ""
}
},
methods: {
success() {
console.log('succss')
}
}
}
export default baseMixin;
2、使用mixin
<script>
import BaseMixin from '../mixin/base'
export default {
mixins: [BaseMixin],
data() {return {}}
}
</script>
全局配置Mixin
import { createApp } from 'vue'
import App from './App.vue'
import BaseMixin from './mixin/base'
const app=createApp(App);
app.mixin(BaseMixin)
app.mount('#app');
十三、模态对话框
Teleport
Vue3.x中的组件模板属于该组件,有时候我们想把模板的内容移动到当前组件之外的DOM 中,这个时候就可以使用 Teleport。
表示teleport内包含的内容显示到body中
<teleport to="body">
内容
</teleport>
<teleport to="#app">
内容
</teleport>
十四、Composition API
共享和重用代码
compositon-api提供了以下几个函数:
- setup
- ref
- reactive
- watchEffect
- watch
- computed
- toRefs
- 生命周期的hooks
setup 组件选项
定义数据、方法
ref、reactive 定义数据
- ref用来定义响应式的 字符串、 数值、Bool类型、数组
- reactive 用来定义响应式的对象
import { ref, reactive } from "vue"
export default {
name: "Home组件",
setup(){
// red 定义响应式数据 定义字符串、num、bool、数组
// reactive 定义响应式数据 定义对象
let title = ref("我是一个标题")
let userinfo = reactive({
username: "张三",
age: 20
})
// 获取 ref 里面定义的数据
var GetTitle = () => {
console.log(title.value)
}
// 获取 reactive 里面定义的数据
var GetUserName = () => {
console.log(userinfo.username)
}
// 修改 ref 里面定义的数据
var SetTitle = () => {
title.value = "修改后的ref"
}
// 修改 reactive 里面定义的数据
var SetUserName = () => {
userinfo.username = "李四"
}
return {
title,
userinfo,
GetTitle,
GetUserName,
SetTitle,
SetUserName
}
}
}
使用 this
在 setup() 内部,this 不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。
toRefs - 解构响应式对象数据
<template>
<div>
<h1>解构响应式对象数据</h1>
<p>Username: {{username}}</p>
<p>Age: {{age}}</p>
</div>
</template>
<script>
import {
reactive,
toRefs
} from "vue";
export default {
name: "解构响应式对象数据",
setup() {
const user = reactive({
username: "张三",
age: 10000,
});
return {
...toRefs(user)
};
},
};
</script>
computed - 计算属性
import {
computed
} from "vue";
export default {
name: "解构响应式对象数据",
setup() {
const fullName = computed(() => {
return user.firstName + " " + user.lastName
})
return {
fullName
};
},
};
readonly “深层”的只读代理
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理
<script>
import { reactive, readonly } from "vue";
export default {
name: "Readonly",
setup() {
const original = reactive({ count: 0 });
original = readonly(original);
const user = { // 这种也是只读,非响应式
firstName: "",
lastName: "",
}
return { original, user };
},
};
</script>
监听数据变化
watchEffect
import { reactive, toRefs, watchEffect } from 'vue'
export default {
setup(){
let data = reactive({
num: 1
})
watchEffect(() => { // 无论数据改不改变,都会最少执行一次;
// 可以详细监听对象内的属性
console.log(`num = ${data.num}`)
})
setInterval(() => {
data.num++
}, 1000)
return {
...toRefs(data)
}
}
}
watch
import { reactive, toRefs, watch } from 'vue'
export default {
setup(){
let data = reactive({
num: 1
})
watch(data, () => {
console.log(`num = ${data.num}`)
})
setInterval(() => {
data.num++
}, 1000)
return {
...toRefs(data)
}
}
}
watch 与watchEffect区别
- watchEffect 刚开始会执行一次;
- watch 懒加载,只有变化时才会触发,也就是说仅在侦听的源变更时才执行回调;
- watch 访问侦听状态变化前后的值
// 侦听单个数据源
watch(keywords, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
- watch 更明确哪些状态的改变会触发侦听器重新运行;
<template>
<div>
<h1>watch - 侦听器</h1>
<p>count1: {{data.count1}}</p>
<p>count2: {{data.count2}}</p>
<button @click="stopAll">Stop All</button>
</div>
</template>
<script>
import {
reactive,
watch
} from "vue";
export default {
name: "Watch",
setup() {
const data = reactive({
count1: 0,
count2: 0
});
// 侦听单个数据源
const stop1 = watch(data, () =>
console.log("watch1", data.count1, data.count2)
);
// 侦听多个数据源
const stop2 = watch([data], () => {
console.log("watch2", data.count1, data.count2);
});
setInterval(() => {
data.count1++;
}, 1000);
return {
data,
stopAll: () => {
stop1();
stop2();
},
};
},
};
</script>
组合式api生命周期钩子
setup 在 beforeCreate、created 之前执行
| 选项式 API | Hook inside setup |
|---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
Props
export default {
//接收父组件数据
props: {
title: String
},
setup(props) {
console.log(props.title)
}
注意:
因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来安全地完成此操作。
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
Provider Inject 父子组件传值
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
非组合式api中的写法
父组件数据改变,子组件不会随之改变,不会响应式改变数据
// 父组件
export default {
components: {
MyMarker
},
data () {
return {
title: '标题'
}
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
},
// 或者另一种写法
provide () {
return {
title: this.title
}
}
}
// 子组件
export default {
inject: ['location', 'geolocation']
}
组合式api中的写法
provider inject 实现父子组件传值时,子组件改变数据也会影响父组件
// 父组件
import Home from './compontents/Home'
import { ref, reactive, toRefs, provide } from 'vue'
export default {
name: 'app',
data () {},
components: { Home },
setup() {
let title = ref('app父组件里面的title')
let userinfo = reactive({
username: '张三',
age: 20
})
let setTitle = () => {
title.value = "父组件改变的标题"
}
// provide 是 key value的写法
provide('title', title)
provide('userinfo', userinfo)
return {
title,
setTitle,
...toRefs(userinfo)
}
}
}
// 子组件
import { inject} from 'vue'
export default{
setup() {
let title = inject('title')
let userinfo = inject('userinfo')
return {
title,
userinfo
}
}
}
十五、Typescript
Ts基础教程:www.itying.com/goods-905.h…
# 1. Install Vue CLI, if it's not already installed
npm install --global @vue/cli
# 2. Create a new project, then choose the "Manually select features" option
vue create my-project-name
# If you already have a Vue CLI project without TypeScript, please add a proper Vue CLI plugin:
vue add typescript
选择vue3 -> 打开项目所在文件夹 -> vue add typescript -> Still proceed?输入 Yes -> Use class-style component syntax?输入 No (这样便和官方文档一致)
定义组件
- vue3.x中集成ts后请确保组件的
script部分已将语言设置为 TypeScript - 要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用
defineComponent全局方法定义组件
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
})
</script>
可以对数据进行类型校验
<script lang="ts">
import { defineComponent } from 'vue'
let title:string = "我是一个home组件"
export default({
data () {
return {
title
}
},
methods: {
setTitle():void{ // void 无返回值
this.title=123 // 报错
}
}
})
</script>
约束组件中的数据类型
<script lang="ts">
import { defineComponent } from 'vue'
// data数据的接口
interface News{
title: string,
descripton:string,
count:number|string, // 定义多种类型
content?:string
}
let newsData:News={
title: '标题',
descripton: '描述',
count:12
}
// 或者
let newsData={
title: '标题',
descripton: '描述',
count:12
} as News
export default({
data () {
return newsData
},
methods: {
// 无传参
setTitle():void{ // void 无返回值
this.title=123 // 报错,只能字符串
this.count=123
},
// 有传参
setDescripton(descripton:string):void{
this.descripton=descripton
}
},
computed:{ // 计算属性
reverseTitle():string{ // 要求返回string类型
// return 123 报错
return this.title.split("").reverse().join("")
}
}
})
</script>
与组合式 API 一起使用
- 实现接口的第一种写法
let user:User=reactive({})
- 实现接口的第二种写法
let user=reactive<User>({})
- 实现接口的第三种写法
let user=reactive({}) as User
- 使用ref指定传入类型
let count=ref<number|string>("20")
- 计算属性限制返回类型
let reverseUsername=computed(():string => {
return user.username.split("").reverse().join("")
})
<script lang="ts">
import { defineComponent, reactive, toRefs,ref } from 'vue'
interface User{
username:string,
age:number,
setUsername(username:string):void, // 参数为string类型,无返回值
getUsername():string // 返回值为string类型
}
export default defineComponent({
setup(){
// 1.实现接口的第一种写法
let user:User=reactive({
username:"张三",
age:29,
serUsername(username:string){
this.username = username
},
getUsername(){
return this.username
}
})
// 2.实现接口的第二种写法
let user=reactive<User>({
})
// 3.实现接口的第三种写法
let user=reactive({
}) as User
// ref限制类型
let count=ref<number|string>("20")
// 计算属性限制返回类型
let reverseUsername=computed(():string => {
return user.username.split("").reverse().join("")
})
return{
...toRefs(user),
count,
reverseUsername
}
}
})
</script>
十六、Router 路由
路由可以让应用程序根据用户输入的不同地址动态挂载不同的组件。 next.router.vuejs.org/
npm install vue-router@next --save
配置路由
新建src/routes.ts 配置路由
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from "./components/Home.vue"
import News from "./components/News.vue"
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/news', component: News }
],
})
export default router
routes中配置路由,且必须是数组
挂载路由
在main.ts中挂载路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './routes'
// createApp(App).mount('#app')
const app = createApp(App)
//挂载路由
app.use(router)
app.mount('#app')
渲染组件
App.vue中通过router-view渲染组件
<template>
<ul>
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<router-link to="/news">新闻</router-link>
</li>
</ul>
<router-view></router-view>
</template>
<script lang="ts">
import {
defineComponent
} from 'vue';
export default defineComponent({
name: 'App',
});
</script>
动态路由
1. 配置动态路由传值
routes: [
{ path: '/', component: Home },
{ path: '/newsContent/:id', component: NewsContent },
],
路由跳转
<li v-for="(item,index) in list" :key="index">
<router-link :to="`/newsContent/${index}`">{{item}}</router-link>
</li>
获取路由
this.$route.params
2. Get传值
routes: [
{ path: '/', component: Home },
{ path: '/newsContent', component: NewsContent },
],
<router-link to="`/newsContent?id=${id}`">Get传值</router-link>
this.$route.query // 获取get传值
路由编程式导航(Js跳转路由)
this.$router.push({ path: 'news' })
this.$router.push({ path: '/newscontent/123'})
this.$router.push({ path: '/newscontent', query:{id:14} }
HTML5 History 模式和 hash 模式
- hash 模式: 路由前面会有
# - 如果开启h5History 模式,需要后台配置伪静态
history: createWebHashHistory(), // hash 模式
history: createWebHistory(), // Html5 History模式
注意:开启Html5 History模式后,发布到服务器需要配置伪静态:
命名路由
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
- 要链接到一个命名路由,可以给
router-link的to属性传一个对象:
{ path: '/user/:id', name: 'user',component: user },
// 动态路由用params
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
这跟代码调用 router.push() 是一回事:
// 动态路由用params
this.$router.push({ name: 'user', params: { userId: 123 }})
这两种方式都会把路由导航到 /user/123 路径。
this.$router.push({name:'content',query:{id:222}})
路由重定向
重定向也在routes配置中完成。要从重定向/a到/b:
const routes = [{ path: '/home', redirect: '/' }]
重定向也可以针对命名路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至使用函数进行动态重定向:
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
相对重定向
也可以重定向到相对位置:
const routes = [
{
path: '/users/:id/posts',
redirect: to => {
// the function receives the target route as the argument
// return redirect path/location here.
},
},
]
路由别名
重定向是指用户访问时/home,URL将被替换/,然后与匹配/。但是什么是别名?
别名/as/home表示用户访问时/home,URL保持不变/home,但将被匹配,就像用户正在访问时一样/。
以上内容可以在路由配置中表示为:
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
嵌套路由
routes: [
{ path: '/', component: Home, alias: '/home' },
{
path: '/news', component: News,
children: [ //子路由
{ path: '', redirect:"/news/add"},
{ path: 'add', component: NewsAdd },
{ path: 'edit', component: NewsEdit },
]
},
{ path: '/user', component: User },
],
十七、Vuex 状态管理模式
- vuex可以实现vue不同组件之间的状态共享 (解决了不同组件之间的数据共享)
- 可以实现组件里面数据的持久化 Vuex的几个核心概念
- State 定义数据
- Getters 相当于计算属性
- Mutations 相当于方法
- Actions 提交,触发方法,异步操作
- Modules 模块
基本使用
安装依赖
npm install vuex@next --save
src目录下面新建一个vuex的文件夹,vuex 文件夹里面新建一个store.js
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count:1
}
},
mutations: { // 方法
incCount(state){
state.count++
},
setCount(state,num){ // 传值
state.count = num
}
}
})
export default store
main.ts中挂载Vuex
import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'
import store from './vuex/store'
let app=createApp(App);
//挂载路由
app.use(route)
//挂载vuex
app.use(store)
app.mount('#app'
State、Mutations
- 获取state的数据
- 由于全局配置了Vuex
app.use(store),所以直接可以通过下面方法获取store里面的值。
computed:{
count() {
this.$store.state.count
}
}
// this.$store.state.count
- 调用方法更改数据,触发mutations
this.$store.commit('incCount')
this.$store.commit('setCount',15) // 传值
- 通过
mapState助手获取State
import { defineComponent } from 'vue'
import { mapState } from 'vuex'
export default defineComponent({
computed: {
...mapState(['count', 'list']) // 第一种写法,变量名称一致
...mapState({ //第二种写法,起别名
myCount: (state) => state.count,
myList: (state) => state.list
})
}
})
Getters
Getter有点类似计算属性,可以认为是 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
- 定义Getters
const store = createStore({
state: {
count:1,
msg: '你好'
},
getters: {
reverseMsg: state => {
return state.msg.split("").reverse().join("")
},
num(state){
return state.count+10
}
}
})
- 获取Getters里面的数据
computed:{
num(){
return this.$store.state.count
},
revMsg(){
return this.$store.getters.reverseMsg // 第一种获取方法
}
}
- 通过
mapGetters辅助函数
import { defineComponent } from 'vue'
import { mapState, mapGetters } from 'vuex'
export default defineComponent({
computed: {
...mapState(['count', 'list'])
...mapGetters(['num', 'reverseMsg']) // 第一种写法
...mapGetters({revmsg: 'reverseMsg'}) // 第二种写法,起别名
}
})
Actions
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
mutations:{ // 方法
incCount(state){
state.count++
},
setMsg(state, msg){
state.msg = msg
}
},
getters:{ // 计算属性
},
actions:{ // 执行mutations里面的方法,异步操作放在actions
incCount(context){
context.commit("incCount") // 执行mutations incCOUNT
},
incSetMsg(context,msg){
setTimeout(()=>{
context.commit("setMsg",msg) // 执行mutations setMsg
},1000)
},
// 另一种写法,解构
incSetMsg({commit},msg){
setTimeout(()=>{
commit("setMsg",msg) // 执行mutations setMsg
},1000)
}
}
分发 Action(触发Action中的方法)dispatch
this.$store.dispatch('increment')
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
- moduleA.js
let moduleA = {
state(){
return{
count: 0
}
},
mutations: {
increment (state) {
// `state` is the local module state
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
export default moduleA
- store.js
import { createStore } from 'vuex'
import moduleA from './moduleA'
const store = createStore({
modules:{
'moduleA': moduleA
}
})
export default store
- 获取state数据
this.$store.state.moduleA.count
- 调用方法
this.$store.commit('increment')
十八、Vuex 结合组合式合成API
组合式api中没有this.$store,可以使用useStore来替代