Vue3基础
1、Vue入门
1.1、Vue介绍
Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同是,Vue被设计为可以自底向上逐层应用。Vue核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。目前最新版本是Vue3.x。
Vue3.x发布于2020年9月19日,在Vue2.x的基础上面进行了一些优化,对TypeScript有更好的支持。Vue3.x的语法和Vue2.x非常相似,如果已经会用Vue2.x,那么Vue3.x会非常简单。
Vue官网地址:https://cn.vuejs.org/
Vue3.x github地址:https://github.com/vuejs/vue-next
Vue3.x文档地址:https://v3.cn.vuejs.org/
1.2、Vue官方脚手架
注意:安装脚手架创建项目之前,必须安装Nodejs,推荐安装nodejs-16.X稳定版本
文档地址:https://v3.cn.vuejs.org/guide/installation.html
Vue-cli地址:https://cli.vuejs.org/
Vite地址:https://github.com/vitejs/vite
1.3、创建项目
通过Vue-cli脚手架工具可以快速搭建Vue项目,目前Vue官方提供了2个脚手架,Vue-cli和Vite。
1.3.1、安装vue-cli
yarn global add @vue/cli
#OR
npm install -g @vue/cli
#OR
cnpm install -g @vue/cli
1.3.2、安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
1.3.3、安装yarn
npm i -g yarn
1.3.4、vue-cli创建项目
vue create hello-vue
yarn serve
1.3.5、vite创建项目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
1.3.6、yarn创建项目
yarn create vite-app <project-name>
cd <project-name>
yarn
yarn dev
1.4、目录结构
1.5、开发工具及插件
1.5.1、VSCode
1.5.2、Voar
2、Vue基础
2.1、绑定数据
2.1.1、main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
2.1.2、App.vue
<template>
<!-- 模板中写HTML页面 -->
<div>
<h1>{{msg}}</h1>
<p>绑定对象:{{userinfo.username}}---{{userinfo.age}}</p>
</div>
</template>
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: '你好Vue',
userinfo: {
username: '张三',
age: 20
}
}
}
}
</script>
<style>
/* sytle中写css样式 */
h1 {
text-align: center;
color: #666;
}
</style>
2.2、绑定html
<template>
<!-- 模板中写HTML页面 -->
<div>
<p>{{h2}}</p>
<p>绑定HTML:<span v-html="h2"></span></p>
</div>
</template>
<script>
// script中写业务逻辑
export default {
data(){
return {
h2: "<h2>你好Vue,我是一个HTML标签</h2>"
}
}
}
</script>
<style>
/* sytle中写css样式 */
h1 {
text-align: center;
color: #666;
}
</style>
2.3、绑定属性
2.3.1、业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
logoSrc: "https://www.itying.com/themes/itying/images/logo.gif"
}
}
}
</script>
2.3.2、template模板
1、绑定属性(v-bind)
<template>
<!-- 模板中写HTML页面 -->
<div>
<p><img v-bind:src="logoSrc" alt="logo"></p>
</div>
</template>
2、绑定属性
<template>
<!-- 模板中写HTML页面 -->
<div>
<p><img :src="logoSrc" alt="logo"></p>
</div>
</template>
3、自定义属性
<template>
<!-- 模板中写HTML页面 -->
<div>
<p title="你好Vue">鼠标放上去试试</p>
<p v-bind:title="title">鼠标放上去试试</p>
<p :title="title">鼠标放上去试试</p>
</div>
</template>
<script>
// script中写业务逻辑
export default {
data(){
return {
title: '自定义属性'
}
}
}
</script>
4、v-bind动态参数
<a v-bind:[attributeName]="url">...</a>
注:attributeName将被动的评估为JavaScript表达式,且其评估值将用作参数的最终值。如果组件实例具有一个数据属性attributeName,其值为href,则此绑定将等效于v-bind:href。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
attrHref: "href",
baiduLink: "https://www.baidu.com"
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<p><a href="https://www.baidu.com">跳转百度</a></p>
<p><a v-bind:[attrHref]="'https://www.baidu.com'">跳转百度</a></p>
<a :[attrHref]="baiduLink">跳转百度</a>
</div>
</template>
2.4、数据循环
2.4.1、v-for
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
people: ['刘总','王总','谢总']
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<p>循环遍历</p>
<ul>
<li v-for="(item,index) in people" :key="index">{{item}}</li>
</ul>
</template>
2.4.2、循环属性
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
news: [
{title: "新闻"},
{title: "电影"},
{title: "综艺"}
]
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<ul>
<li v-for="(item,index) in news" :key="index">{{item.title}}</li>
</ul>
</template>
2.4.3、循环对象
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
obj: {
title: "How to do lists in Vue",
author: "jcshen",
published: "2022-06-07"
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<ul>
<li v-for="(value,attr,index) in obj" :key="indx">{{attr}}:{{value}}---{{index}}</li>
</ul>
</template>
2.4.4、循环嵌套
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
tv: [
{
cate: "国内新闻",
news: [
{title: "民生"},
{title: "娱乐"},
{title: "八卦"}
]
},
{
cate: "国际新闻",
news: [
{title: "政治"},
{title: "经济"},
{title: "文化"}
]
}
]
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<ul>
<li v-for="(item,index) in tv" :key="index">
{{item.cate}}
<ol>
<li v-for="(i,k) in item.news" :key="k">{{i.title}}</li>
</ol>
</li>
</ul>
</template>
2.5、事件模板语法类和样式
2.5.1、事件方法入门
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: '你好Vue',
}
},
methods: {
setMsg(){
this.msg = "我是改变后的msg"
},
getMsg(){
alert(this.msg)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
{{msg}}
<br>
<button @click="setMsg()">改变msg的值</button>
<button @click="getMsg()">获取msg的值</button>
</div>
</template>
2.5.2、v-bind绑定Class
当v-bind与class一起使用时,Vue提供了特殊的增强功能style。除了字符串外,表达式可以求值为对象或数组。
v-bind:class绑定字符串
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: '你好Vue',
myclass: "red"
}
},
methods: {
changeClass(){
this.myclass="blue"
}
}
}
</script>
<style>
/* sytle中写css样式 */
.red {
background-color: red;
width: 100px;
height: 100px;
}
.blue {
background-color: blue;
width: 100px;
height: 100px;
}
</style>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
{{msg}}
<br>
<button @click="setMsg()">改变msg的值</button>
<button @click="getMsg()">获取msg的值</button>
<br>
<div class="red">
</div>
<br>
<button @click="changeClass()">设置背景色</button>
<div :class="myclass"></div>
</div>
</template>
class绑定多个动态属性
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: '你好Vue',
myclass: "red",
isActive: true,
hasError: true
}
},
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div :class="{'active':isActive, 'red':hasError}">v-bind:class属性</div>
</template>
<template>
<!-- 模板中写HTML页面 -->
<div class="blue" :class="{'active':isActive}">v-bind:class属性</div>
</template>
Css
<style>
/* sytle中写css样式 */
.active {
display: block;
font-size: 30px;
color: orange;
}
.red {
background-color: red;
width: 100px;
height: 100px;
}
.blue {
background-color: blue;
width: 100px;
height: 100px;
}
</style>
数组语法
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
activeClass: 'active',
errorClass: 'error',
baseClass: 'base'
}
},
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div :class="[errorClass,baseClass]">数组绑定Class</div>
</template>
Css
<style>
/* sytle中写css样式 */
.base {
width: 150px;
height: 150px;
}
.error{
background-color: orange;
font-size: 30px;
color: red;
}
</style>
数组语法结合三目运算
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
isActive: true,
activeClass: 'active',
errorClass: 'error'
}
},
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div :class="[isActive?activeClass:errorClass]">数组结合三目运算绑定Class</div>
</template>
Css
<style>
/* sytle中写css样式 */
.error{
background-color: orange;
font-size: 30px;
color: red;
}
.active {
display: block;
font-size: 30px;
color: orange;
}
</style>
v-bind:style绑定内联样式
第一种绑定方式
业务逻辑
data(){
return {
activeColor: "red",
fontSize: 30
}
}
template模板
<div :style="{'color': activeColor,'fontSize':+ 'px'}"></div>
第二种绑定方式
业务逻辑
data(){
return {
styleObject: {
color: "red",
fontSize: "13px"
}
}
}
template模板
<div :style="styleObject"></div>
第三种绑定方式 数组方式
业务逻辑
data(){
return {
baseStyle: {
color: "Orange",
fontSize: "13px"
},
orangeStyle: {
width: "100px",
height: "100px",
background: "orange"
}
}
}
template模板
<div :style="[baseStyle,orangeStyle]"></div>
2.5.3、案例
循环数据,第一个数据高亮显示
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
list: ['马总','刘总','李总']
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<ul>
<li v-for="(item,index) in list" :key="index" :class="{'red':index==0,'blue':index==1}">{{item}}</li>
</ul>
</div>
</template>
Css
<style>
/* sytle中写css样式 */
.red {
color: red;
}
.blue {
color: blue;
}
</style>
2.5.4、事件方法
方法传值
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue",
title: "我是一个标题"
}
},
methods: {
setMsg(){
this.msg = "改变msg值";
},
getMsg(){
alert(this.msg);
},
setTitle(data){
this.title = data;
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
{{msg}}
<br><br>
<button @click="setMsg()">设置msg值</button>
<br><br>
<button v-on:click="getMsg()">获取msg值</button>
<br><br>
{{title}}
<button v-on:click="setTitle('111')">设置title值</button>
</div>
</template>
方法调用方法
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue",
title: "我是一个标题"
}
},
methods: {
setMsg(){
this.msg = "改变msg值";
},
getMsg(){
alert(this.msg);
},
setTitle(data){
this.title = data;
},
run(){
this.getMsg();
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
{{msg}}
<button @click="run()">方法调用方法</button>
</div>
</template>
2.5.5、事件对象
有时需要在内联语句处理程序中访问原始DOM事件。可使用特殊$event变量将其传递给方法
单个参数
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue"
}
},
methods: {
eventFn(e){
console.log(e);
//e.srcElement dom节点
e.srcElement.style.background='red';
// 获取自定义属性的值
console.log(e.srcElement.dataset.aid);
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<button data-aid="123" @click="eventFn($event)">事件对象</button>
</div>
</template>
多个参数
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue"
}
},
methods: {
warn(message,event){
if(event) {
event.preventDefault();
}
alert(message)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<button @click="warn('传入的参数',$event)">提交</button>
</div>
</template>
2.5.6、多个事件处理程序
在事件处理程序中使用逗号分隔多个事件处理程序
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue",
title: "我是一个标题"
}
},
methods: {
one(event){
console.log('one')
},
two(event){
console.log('two')
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<button @click="one(),two()">触发多个事件</button>
</div>
</template>
2.5.7、事件修饰符
Vue中阻止冒泡阻止默认行为,可通过事件对象event.preventDefault()或event.stopPropagation()实现,还可以通过事件修饰符实现。
Vue提供了很多修饰符
.stop
.prevent
.capture
.self
.once
.passiv
stopPropagation
<a @click.stop="doThis"></a>
preventDefault
<a @click.prevent="doThis"></a>
stopPropagation And preventDefault
<a @click.stop.prevent="doThis"></a>
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue"
}
},
methods: {
handleLink(e){
e.preventDefault();
},
preventLink(){
console.log('prevent')
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<a href="https://baidu.com" target="_blank" @click="handleLink($event)">阻止跳转百度</a>
<br><br>
<a href="https://baidu.com" target="_blank" @click.prevent="preventLink()">阻止跳转百度</a>
</div>
</template>
2.5.8、按键修饰符
监听键盘事件时,通常需要检查特定的键。Vue允许在监听事件时v-on或@在监听关键事件时添加按键修饰符
<input @keyup.enter="submit">
Vue为最常用的键提供别名
.enter
.tab
.delete(同时捕获"删除"和"退格"键)
.esc
.space
.up
.down
.left
.right
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
msg: "你好Vue"
}
},
methods: {
doSearch(e){
console.log(e.keyCode)
if(e.keyCode == 13){
alert("回车键")
}
},
hadnleClick(){
alert("回车键")
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<input type="text" @keyup="doSearch($event)">
<br><br>
<input type="text" @keyup.enter="hadnleClick()">
</div>
</template>
2.6、Dom和表单操作
2.6.1、人员登记系统
2.6.2、Dom操作
原生js获取数据
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
empinfo:"",
}
},
methods: {
querySumbit(){
var info = document.querySelector("#username");
alert(info.value);
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>姓名: <input type="text" id="username"></li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
ref获取数据
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
empinfo:"",
}
},
methods: {
querySumbit(){
console.log(this.$refs.age.value)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>年龄:<input type="text" ref="age"></li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
2.6.3、双向数据绑定
MVVM就是常说的双向数据绑定,Vue就是一个MVVM的框架。M表示model,V表示View。在MVVM的框架中model改变会影响视图view,view视图改变反过来影响model。注意:双向数据绑定主要用于表单中。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
username: "zhangsan",
empinfo:{
age: 20,
addr: "武汉"
}
}
},
methods: {
querySumbit(){
console.log(this.username);
console.log(this.empinfo)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>姓名: <input type="text" v-model="username"></li>
<li>{{username}}</li>
<li>年龄:<input type="text" v-model="empinfo.age"></li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
2.6.4、input双向数据绑定
2.6.5、select双向数据绑定
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
username: "zhangsan",
empinfo:{
age: 20,
addrs: ["北京","上海","深圳"],
sex: 1,
city: "上海"
}
}
},
methods: {
querySumbit(){
console.log(this.username);
console.log(this.empinfo)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>姓名: <input type="text" v-model="username"></li>
<li>年龄:<input type="text" v-model="empinfo.age"></li>
<li>性别:
<input type="radio" v-model="empinfo.sex" value="1"><label for="sex">男</label>
<input type="radio" v-model="empinfo.sex" value="2"><label for="sex">女</label>
</li>
<li>城市:
<select v-model="empinfo.city">
<option v-for="(item,index) in empinfo.addrs" :key="index" :value="item">{{item}}</option>
</select>
</li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
2.6.6、radio双向数据绑定
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
username: "zhangsan",
empInfo:{
age: 20,
addr: "武汉",
sex: 1,
}
}
},
methods: {
querySumbit(){
console.log(this.username);
console.log(this.empinfo)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>性别:
<input type="radio" v-model="empinfo.sex" value="1"><label for="sex">男</label>
<input type="radio" v-model="empinfo.sex" value="2"><label for="sex">女</label>
</li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
2.6.7、textarea双向数据绑定
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
username: "zhangsan",
empinfo:{
age: 20,
addrs: ["北京","上海","深圳"],
sex: 1,
city: "上海",
hobby: [
{
title: "吃饭",
checked: true
},
{
title: "睡觉",
checked: false
},
{
title: "写代码",
checked: false
}
],
mark: ""
}
}
},
methods: {
querySumbit(){
console.log(this.username);
console.log(this.empinfo)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>姓名: <input type="text" v-model="username"></li>
<li>年龄:<input type="text" v-model="empinfo.age"></li>
<li>性别:
<input type="radio" v-model="empinfo.sex" value="1"><label for="sex">男</label>
<input type="radio" v-model="empinfo.sex" value="2"><label for="sex">女</label>
</li>
<li>城市:
<select v-model="empinfo.city">
<option v-for="(item,index) in empinfo.addrs" :key="index" :value="item">{{item}}</option>
</select>
</li>
<li>爱好:
<span v-for="(item,index) in empinfo.hobby" :key="index">
<input type="checkbox" :id="'ch_'+index" v-model="item.checked"/>
<label :for="'ch_'+index">{{item.title}}</label>
</span>
</li>
<li>备注:
<textarea cols="30" rows="4" v-model="empinfo.mark"></textarea>
</li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
<br><br>
<div><pre>{{empinfo}}</pre></div>
</template>
2.6.8、checkbox双向数据绑定
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
username: "zhangsan",
empinfo:{
age: 20,
addrs: ["北京","上海","深圳"],
sex: 1,
city: "上海",
hobby: [
{
title: "吃饭",
checked: true
},
{
title: "睡觉",
checked: false
},
{
title: "写代码",
checked: false
}
]
}
}
},
methods: {
querySumbit(){
console.log(this.username);
console.log(this.empinfo)
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="emp_list">
<ul>
<li>姓名: <input type="text" v-model="username"></li>
<li>年龄:<input type="text" v-model="empinfo.age"></li>
<li>性别:
<input type="radio" v-model="empinfo.sex" value="1"><label for="sex">男</label>
<input type="radio" v-model="empinfo.sex" value="2"><label for="sex">女</label>
</li>
<li>城市:
<select v-model="empinfo.city">
<option v-for="(item,index) in empinfo.addrs" :key="index" :value="item">{{item}}</option>
</select>
</li>
<li>爱好:
<span v-for="(item,index) in empinfo.hobby" :key="index">
<input type="checkbox" :id="'ch_'+index" v-model="item.checked"/>
<label :for="'ch_'+index">{{item.title}}</label>
</span>
</li>
<button @click="querySumbit()" class="submit">获取表单内容</button>
</ul>
</div>
</template>
2.7、JavaScript表达式
2.7.1、JavaScript表达式
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
number: 1,
msg: "西游记",
flag: true,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<span>{{number + 1}}</span>
<br>
<span>{{flag ? 'YES' : 'NO'}}</span>
<br>
<span>{{msg.split('').reverse().join('')}}</span>
</div>
</template>
2.8、条件判断
2.8.1、v-if
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
number: 1,
msg: "西游记",
flag: true,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<div v-if="flag==true">this is true</div>
</div>
</template>
2.8.2、v-if v-else
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
number: 1,
msg: "西游记",
flag: false,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<span v-if="flag==true">this is true</span>
<span v-else>this is false</span>
</div>
</template>
template模板
注:v-else元素必须紧跟在带v-if或者v-else-if的元素后面,否则将不会被识别
<template>
<!-- 模板中写HTML页面 -->
<div >
<span v-if="Math.random() > 0.5">大于0.5</span>
<span v-else>小于0.5</span>
</div>
</template>
2.8.3、v-else-if
注:与v-else相似,v-else-if元素必须紧跟v-if或v-else-if元素
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
type: "D",
msg: "西游记",
flag: false,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div >
<span v-if="type === 'A'">A</span>
<span v-else-if="type === 'B'">B</span>
<span v-else>C</span>
</div>
</template>
2.8.4、在template元素上使用v-if条件渲染分组
v-if是一个指令,必须添加到一个元素上。若需切换多个元素呢?此时可把一个template元素当作不可见的包裹元素,并在上面使用v-if。最终的渲染结果将不包含template元素。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
type: "D",
msg: "西游记",
flag: true,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<template v-if="flag">
<div>这是div</div>
<span>这是span</span>
</template>
</template>
2.8.5、v-show
用于根条件展示元素的选项是v-show指令。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
flag: true,
}
},
methods: {
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<h5 v-if="flag">v-if显示</h5>
<h5 v-show="flag">v-show显示</h5>
</div>
</template>
2.8.6、v-if和v-show区别
v-if是dom操作,v-show只是css的显示隐藏,一般来说,v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁的切换,则使用v-show较好。
2.9、计算属性
2.9.1、计算属性入门
在模板中表达式非常便利,但实际上只能用于简单操作。
模板是为了描述视图的结构。在模板中放入太多的逻辑会让模板过重且难以维护。这就是Vue.js将绑定表达式限制为一个表达式。若需要多于一个表达式的逻辑,应当使用计算属性。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
flag: true,
msg: "三国演义",
type: "N"
}
},
methods: {
setMsg(){
this.msg = "大家好"
}
},
computed: {
reverseMsg(){
return this.msg.split("").reverse().join("");
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<span>{{reverseMsg}}</span>
<br>
<button @click="setMsg()">改变值</button>
</div>
</template>
2.9.2、计算属性实现数据筛选
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
list: ['apple','banana','orange','pear'],
keyword: ""
}
},
methods: {
},
computed: {
reverseMsg(){
return this.msg.split("").reverse().join("");
},
searchList(){
var temp = [];
this.list.forEach((value)=>{
if(value.indexOf(this.keyword) != -1 && this.keyword != ""){
temp.push(value);
}
})
console.log(temp);
return temp;
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<input type="text" v-model="keyword" placeholder="请输入关键词"/>
<ul>
<li v-for="(item,index) in searchList" :key="index">{{item}}</li>
</ul>
</div>
</template>
2.10、watch监听
Vue.js提供了一个方法watch,它用于观察Vue实例上的数据变动。当一些数据需要根据其它数据变化时,watch很诱人——特别是如果来自AngularJS。通常更好的办法时使用计算属性而不是命令式的watch回调。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
firstName: "",
lastName: "",
fullName: ""
}
},
methods: {
},
computed: {
fullNameFn(){
return this.firstName +" "+ this.lastName;
}
},
watch: {
firstName: function(val){
this.fullName = val + " " + this.lastName;
},
lastName: function(val){
this.fullName = this.firstName + " " + val;
}
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div>
<input type="text" v-model="firstName" placeholder="firsName"/>
<br><br>
<input type="text" v-model="lastName" placeholder="lastName">
<br>
{{firstName}} {{lastName}}
<br><br>
{{fullNameFn}}
</div>
</template>
3、Vue集成
3.1、集成sass和scss
3.1.1、安装sass-loader node-sass
npm install -D sass sass-loader node-sass
3.1.2、style中配置sass/scss
注:lang可以配置scss,scope表示这里写的css只有当前组件有效
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="todolist">
<h3>todolist</h3>
</div>
</template>
css
<style lang="scss" scoped>
/* sytle中写css样式 */
h2 {
text-align: center;
}
.todolist {
width: 500px;
border: 1px solid #eee;
margin: 0 auto;
padding: 20px;
h3 {
color: red;
font-size: 40px;
}
}
</style>
3.2、待办事项(案例)
实现完整toDoList(待办事项)以及类似京东App搜索缓存数据功能。
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
todo: "",
list: [],
}
},
methods: {
addData(){
this.list.push({
title: this.todo,
checked: false,
});
},
deleteData(idx){
// alert(idx);
this.list.splice(idx,1);
}
},
}
</script>
template模板(v-show)
<template>
<!-- 模板中写HTML页面 -->
<div class="todolist">
<input type="text" v-model="todo" @keyup.enter="addData()">
<hr>
<h4>正在进行</h4>
<ul>
<li v-for="(item,index) in list" :key="index" v-show="!item.checked">
<input type="checkbox" v-model="item.checked"/>{{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</ul>
<h4>已经完成</h4>
<ul>
<li v-for="(item,index) in list" :key="index" v-show="item.checked">
<input type="checkbox" v-model="item.checked" /> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</ul>
</div>
<br><br>
<div>
<pre>{{list}}</pre>
</div>
</template>
template模板(v-if)
<template>
<!-- 模板中写HTML页面 -->
<div class="todolist">
<input type="text" v-model="todo" @keyup.enter="addData()">
<hr>
<h4>正在进行</h4>
<ul>
<template v-for="(item,index) in list" :key="index">
<li v-if="!item.checked">
<input type="checkbox" v-model="item.checked"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
<h4>已经完成</h4>
<ul>
<template v-for="(item,index) in list" :key="index">
<li v-if="item.checked">
<input type="checkbox" v-model="item.checked"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
</div>
<br><br>
<div>
<pre>{{list}}</pre>
</div>
</template>
css
<style lang="scss" scoped>
/* sytle中写css样式 */
h2 {
text-align: center;
}
.todolist {
width: 500px;
border: 1px solid #eee;
margin: 0 auto;
padding: 20px;
h3 {
color: red;
font-size: 40px;
}
}
</style>
3.3、模块化及封装Storage
3.3.1、Storage
业务逻辑
<script>
// script中写业务逻辑
export default {
data(){
return {
todo: "",
list: [],
}
},
methods: {
//添加数据
addData(){
this.list.push({
title: this.todo,
checked: false,
});
this.todo = "";
//持久化:字符串类型
localStorage.setItem("todolist",JSON.stringify(this.list));
},
//删除数据
deleteData(idx){
// alert(idx);
this.list.splice(idx,1);
//持久化
localStorage.setItem("todolist",JSON.stringify(this.list));
},
//
setTodolist(){
//持久化
localStorage.setItem("todolist",JSON.stringify(this.list));
}
},
//页面加载时触发的方法
mounted(){
let storelist = JSON.parse(localStorage.getItem("todolist"));
if(storelist) {
this.list = storelist;
}
console.log("刷新时加载")
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="todolist">
<input type="text" v-model="todo" @keyup.enter="addData()" />
<hr>
<h4>正在进行</h4>
<ul>
<!-- <li v-for="(item,index) in list" :key="index" v-show="!item.checked">
<input type="checkbox" v-model="item.checked"/>{{item.title}}---<button @click="deleteData(index)">删除</button>
</li> -->
<template v-for="(item,index) in list" :key="index">
<li v-if="!item.checked">
<input type="checkbox" v-model="item.checked" @change="setTodolist()"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
<h4>已经完成</h4>
<ul>
<!-- <li v-for="(item,index) in list" :key="index" v-show="item.checked">
<input type="checkbox" v-model="item.checked" /> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li> -->
<template v-for="(item,index) in list" :key="index">
<li v-if="item.checked">
<input type="checkbox" v-model="item.checked" @change="setTodolist()"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
</div>
<br><br>
<div>
<pre>{{list}}</pre>
</div>
</template>
css
<style lang="scss" scoped>
/* sytle中写css样式 */
h2 {
text-align: center;
}
.todolist {
width: 500px;
border: 1px solid #eee;
margin: 0 auto;
padding: 20px;
h3 {
color: red;
font-size: 40px;
}
}
</style>
3.3.2、模块化
storage.js
注:创建src/modules/storage.js
const storage = {
set(key,value){
localStorage.setItem(key,JSON.stringify(value));
},
get(key){
return JSON.parse(localStorage.getItem(key));
},
remove(key){
localStorage.removeItem(key);
}
}
//暴露storage
export default storage;
业务逻辑
<script>
//引入storage模块
import storage from './modules/storage'
// script中写业务逻辑
export default {
data(){
return {
todo: "",
list: [],
}
},
methods: {
//添加数据
addData(){
this.list.push({
title: this.todo,
checked: false,
});
this.todo = "";
//持久化:字符串类型
storage.set("todolist",this.title);
},
//删除数据
deleteData(idx){
// alert(idx);
this.list.splice(idx,1);
//持久化
storage.set("todolist",this.list)
},
//
setTodolist(){
//持久化
storage.set("todolist",this.list);
}
},
//页面加载时触发的方法
mounted(){
let storelist = storage.get("todolist");
if(storelist) {
this.list = storelist;
}
console.log("刷新时加载")
}
}
</script>
template模板
<template>
<!-- 模板中写HTML页面 -->
<div class="todolist">
<input type="text" v-model="todo" @keyup.enter="addData()" />
<hr>
<h4>正在进行</h4>
<ul>
<template v-for="(item,index) in list" :key="index">
<li v-if="!item.checked">
<input type="checkbox" v-model="item.checked" @change="setTodolist()"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
<h4>已经完成</h4>
<ul>
<template v-for="(item,index) in list" :key="index">
<li v-if="item.checked">
<input type="checkbox" v-model="item.checked" @change="setTodolist()"/> {{item.title}}---<button @click="deleteData(index)">删除</button>
</li>
</template>
</ul>
</div>
<br><br>
<div>
<pre>{{list}}</pre>
</div>
</template>
css
<style lang="scss" scoped>
/* sytle中写css样式 */
h2 {
text-align: center;
}
.todolist {
width: 500px;
border: 1px solid #eee;
margin: 0 auto;
padding: 20px;
h3 {
color: red;
font-size: 40px;
}
}
</style>
4、组件
组件可以拓展HTML标签,解决HTML标签构建应用的不足,Vue项目由一个一个的组件组成。
4.1、Home组件
定义组件
创建src/components/Home.vue
<template>
<h5>{{title}}</h5>
<button @click="getTitle()">获取首页组件</button>
</template>
<script>
//暴露组件
export default {
data(){
return {
title: "首页组件"
}
},
methods: {
getTitle(){
alert(this.title);
}
}
}
</script>
<style lang="scss" scoped>
h5 {
text-align: center;
}
</style>
挂载组件
在src/App.vue中引入Home组件
<template>
<!-- 模板中写HTML页面 -->
<!-- 3、使用组件 -->
<Home/>
</template>
<script>
//1、引入Home组件
import Home from './components/Home.vue';
// script中写业务逻辑
export default {
data(){
return {
msg: "app根组件"
}
},
//2、挂载组件
components: {
Home
}
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
4.2、Header组件
定义组件
创建src/components/Header.vue
<template>
<header>头部组件</header>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
header {
width: 100%;
height: 44px;
text-align: center;
line-height: 44px;
background: #000;
color: #fff;
}
</style>
使用组件
<template>
<!-- 3、使用组件 -->
<v-header />
<h5>{{title}}</h5>
<button @click="getTitle()">获取首页组件</button>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
title: "首页组件"
}
},
methods: {
getTitle(){
alert(this.title);
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
h5 {
text-align: center;
}
</style>
4.3、用户组件
定义组件
<template>
用户组件
<div>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
}
},
mounted(){
for(var i=0; i < 10; i++){
this.list.push(`第${i}条数据`)
}
}
}
</script>
<style lang="scss" scoped>
ul {
list-style-type: none;
}
</style>
使用组件
<template>
<!-- 模板中写HTML页面 -->
<!-- 3、使用组件 -->
<Home/>
<br>
<User />
</template>
<script>
//1、引入Home组件
import Home from './components/Home.vue';
import User from './components/User.vue'
// script中写业务逻辑
export default {
data(){
return {
msg: "app根组件"
}
},
//2、挂载组件
components: {
Home,
User
}
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
5、父子组件
5.1、父组件给子组件传值
5.1.1、父组件调用子组件时传值
Header.vue
<template>
<h2>{{title}}</h2>
<br>
{{msg}}
<br>
{{count}}
<br>
{{user.name}}---{{user.age}}
</template>
<script>
export default {
//props
props: [
"title",
"msg",
"count",
"user",
],
data(){
return {
}
},
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
Home.vue
<template>
<!-- 3、使用子组件并传值 -->
<v-header :title="title" :msg="'父组件传值给子组件'" :count="xxx" :user="user"></v-header>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
title: "首页组件",
xxx: 10,
user: {
name: "张三",
age: 20
}
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
</style>
获取父组件数据
Header.vue
<template>
<h2>{{title}}</h2>
<br>
{{msg}}
<br>
{{count}}
<br>
{{user.name}}---{{user.age}}
<br>
<button @click="getParentTitle()">获取父组件数据</button>
</template>
<script>
export default {
//props
props: [
"title",
"msg",
"count",
"user",
],
data(){
return {
}
},
methods: {
// 获取父组件数据
getParentTitle(){
alert(this.title)
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
5.1.2、父组件实例传递给子组件
:home="this"表示将当前组件传递给子组件,子组件就可以获取父组件的数据和执行父组件的方法。
Home.vue
<template>
<!-- 3、使用子组件并传值 -->
<v-header :title="title" :msg="'父组件传值给子组件'" :count="xxx" :user="user" :home="this"></v-header>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
title: "首页组件",
xxx: 10,
user: {
name: "张三",
age: 20
},
list: ['马总','刘总'],
}
},
methods: {
run(){
alert("Home组件的run方法")
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
</style>
Header.vue
<template>
<!-- 使用父组件传值 -->
<h2>{{title}}</h2>
<br>
{{msg}}
<br>
{{count}}
<br>
{{user.name}}---{{user.age}}
<br>
<button @click="getParentTitle()">获取父组件数据</button>
<br>
{{home.list}}
<br>
<button @click="runParent()">执行父组件run方法</button>
</template>
<script>
export default {
//props:接收父组件传值
props: [
"title",
"msg",
"count",
"user",
"home",
],
data(){
return {
}
},
methods: {
// 获取父组件数据
getParentTitle(){
alert(this.title)
},
//执行父组件方法
runParent(){
this.home.run();
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
5.2、Props验证
可以为组件的props指定验证要求,如类型验证,若有需求没有被满足,则Vue会在浏览器控制台中警告。在开发一个会被别人用到的组件时尤其有帮助。
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'
}
}
}
5.3、单向数据流
所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外变更父级组件的状态,从而导致应用的数据流向难以理解。
另外,每次父组件发生变更时,子组件中所有的prop都将会刷新为最新的值。这意味着不应该在一个子组件内部改变prop。
若需这样做,Vue会在浏览器控制台中发出警告。
5.4、父组件主动获取子组件的数据和执行子组件方法
5.4.1、调用子组件时定义一个ref
#ref名称可自定义
<v-header ref="header"></v-header>
5.4.2、父组件主动获取子组件数据
this.$refs.header.属性
5.4.3、父组件主动执行子组将方法
this.$refs.header.方法
5.4.4、案例
App.vue
<template>
<!-- 模板中写HTML页面 -->
<!-- 3、使用组件 -->
<Home/>
</template>
<script>
//1、引入Home组件
import Home from './components/Home.vue';
// script中写业务逻辑
export default {
data(){
return {
msg: "app根组件"
}
},
//2、挂载组件
components: {
Home,
}
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
Home.vue
<template>
<!-- 3、使用子组件并传值 -->
<v-header ref="header"></v-header>
<br>
<hr>
<div>Home组件</div>
<br>
<button @click="getHeaderMsg()">获取Header子组件里面的数据</button>
<br>
<button @click="doHeaderFn()">执行Header子组件的方法</button>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
msg: "Home父组件",
}
},
methods: {
getHeaderMsg(){
//获取子组件实例
alert(this.$refs.header.msg);
},
doHeaderFn(){
this.$refs.header.run();
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
</style>
Header.vue
<template>
头部组件
</template>
<script>
export default {
data(){
return {
msg: "Header头部子组件"
}
},
methods: {
run(){
alert("Header组件里面的run方法");
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
5.5、子组件主动获取父组件的数据和执行父组件的方法
5.5.1、子组件主动获取父组件的数据
this.$parent.数据
5.5.2、子组件主动获取父组件的数据
this.$parent.方法
5.5.3、案例
Home.vue
<template>
<!-- 3、使用子组件并传值 -->
<v-header ref="header"></v-header>
<br>
<hr>
<div>Home组件</div>
<br>
父组件msg:{{msg}}
<br><br>
<button @click="getHeaderMsg()">获取Header子组件里面的数据</button>
<br>
<button @click="doHeaderFn()">执行Header子组件的方法</button>
<br>
<button @click="setChildMsg()">修改子组件数据</button>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
msg: "Home父组件",
}
},
methods: {
run(){
alert("Home组件的run方法");
},
//获取子组件数据
getHeaderMsg(){
//获取子组件实例
alert(this.$refs.header.msg);
},
//执行子组件方法
doHeaderFn(){
this.$refs.header.run();
},
//修改子组件数据
setChildMsg(){
this.$refs.header.msg="修改后的子组件数据";
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
</style>
Header.vue
<template>
<div>头部组件</div>
<br>
子组件msg:{{msg}}
<br><br>
<button @click="getParentMsg()">获取父组件里面的数据</button>
<br>
<button @click="doParentFn()">执行父组件中的方法</button>
<br>
<button @click="setterParentMsg()">修改父祖件的数据</button>
</template>
<script>
export default {
data(){
return {
msg: "Header头部子组件"
}
},
methods: {
run(){
alert("Header组件里面的run方法");
},
//获取父组件的数据
getParentMsg(){
alert(this.$parent.msg);
},
//执行父组件的方法
doParentFn(){
this.$parent.run();
},
//修改父组件数据
setterParentMsg(){
this.$parent.msg = "修改后的父组件数据";
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
}
</style>
5.6、自定义组件事件以及mitt实现非父子组件传值
5.6.1、组件自定义事件实现子组件给父组件传值
注:Vue官方推荐始终使用kebab-case的事件名。
子组件Header.vue
<template>
<h2>头部组件</h2>
<button @click="sendParent()">子组件执行父组件的自定义事件并传参给父组件</button>
</template>
<script>
export default {
// 建议定义所有发出的事件,以便更好的记录组件应该如何工作
emits:["send"],
data(){
return {
msg: "Header头部子组件"
}
},
methods: {
sendParent(){
// this.$emit("send")
// 子组件传参给父组件
this.$emit("send",this.msg);
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
background: #000;
height: 44px;
line-height: 44px;
color: white;
}
</style>
父组件Home.vue
<template>
<!-- 3、父组件自定义事件,使用子组件并传值给子组件 -->
<v-header @send="getChild"></v-header>
<hr>
<div>Home组件</div>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
//暴露组件
export default {
data(){
return {
msg: "Home父组件",
}
},
methods: {
// 父组件方法接收子组件传参
getChild(data){
alert("父组件的方法"+"--"+data);
}
},
//2、挂载组件
components: {
"v-header": Header
}
}
</script>
<style lang="scss" scoped>
</style>
5.6.2、组件自定义事件对传值进行验证
子组件Login.vue
<template>
<div class="login">
<input type="text" v-model="username" placeholder="用户名">
<br><br>
<input type="text" v-model="password" placeholder="密码">
<br><br>
<button @click="toLogin">执行登录</button>
</div>
</template>
<script>
export default {
// 2、监听自定义事件
emits:{
// 监听父组件的submit事件,对参数进行校验
submit:({username,password})=>{
if(username!="" && password!=""){
return true;
}else{
console.error("用户名密码不能为空");
return false;
}
}
},
data(){
return {
username:"",
password:"",
}
},
methods:{
toLogin(){
// alert("toLogin")
// 1、子组件执行父组件自定义事件并传值给父组件
this.$emit("submit",{
username: this.username,
password: this.password
})
}
}
}
</script>
<style lang="scss" scoped>
.login {
padding: 20px;
}
</style>
父组件Home.vue
<template>
<!-- 3、父组件自定义事件,使用子组件并传值给子组件 -->
<v-header @send="getChild"></v-header>
<br><br>
<v-login @submit="doLogin"></v-login>
</template>
<script>
//1、引入组件
import Header from './Header.vue'
import Login from './Login.vue'
//3、暴露组件
export default {
data(){
return {
msg: "Home父组件",
}
},
methods: {
// 4、父组件方法接收子组件传参
getChild(data){
alert("父组件的方法"+"--"+data);
},
doLogin(data){
console.log(data);
}
},
//2、注册/挂载组件
components: {
"v-header": Header,
"v-login": Login
}
}
</script>
<style lang="scss" scoped>
</style>
5.6.3、第三方插件mitt实现非父子组件传值
https://github.com/developit/mitt
Vue3.x以后从实例中移除了$on、$off和$once方法,仍然时现有API的一部分,只能实现子组件触发父组件的方法。
安装mitt模块
npm install --save mitt
新建model/event.js
// 引入mitt
import mitt from 'mitt'
// 创建mitt实例
const event = mitt();
// 暴露mitt
export default event;
组件Header.vue
<template>
<h2>头部组件</h2>
<button @click="sendParent()">子组件执行父组件的自定义事件并传参给父组件</button>
<br><br>
<button @click="sendLogin">非父子组件传值</button>
</template>
<script>
// 引入mitt
import event from "../model/event";
export default {
// 建议定义所有发出的事件,以便更好的记录组件应该如何工作
emits:["send"],
data(){
return {
msg: "Header头部子组件"
}
},
methods: {
sendParent(){
// this.$emit("send")
// 子组件传参给父组件
this.$emit("send",this.msg);
},
sendLogin(){
// alert("sendLogin")
// 广播事件,并传参
// event.emit("toLogin",this.msg);
event.emit("toLogin",{username:"张三",age:20})
}
}
}
</script>
<style lang="scss" scoped>
h2 {
text-align: center;
background: #000;
height: 44px;
line-height: 44px;
color: white;
}
</style>
组件Login.vue
<template>
<div class="login">
<input type="text" v-model="username" placeholder="用户名">
<br><br>
<input type="text" v-model="password" placeholder="密码">
<br><br>
<button @click="toLogin">执行登录</button>
</div>
</template>
<script>
// 引入mitt
import event from "../model/event";
export default {
// 2、监听自定义事件
emits:{
// 监听父组件的submit事件,对参数进行校验
submit:({username,password})=>{
if(username!="" && password!=""){
return true;
}else{
console.error("用户名密码不能为空");
return false;
}
}
},
data(){
return {
username:"",
password:"",
}
},
methods:{
toLogin(){
// alert("toLogin")
// 1、子组件执行父组件自定义事件并传值给父组件
this.$emit("submit",{
username: this.username,
password: this.password
})
}
},
// 生命周期函数
mounted(){
// 监听广播,并接收非子组件传值
event.on("toLogin",(data)=>{
console.log(data);
})
}
}
</script>
<style lang="scss" scoped>
.login {
padding: 20px;
}
</style>
6、自定义组件使用v-model实现双向数据绑定及slot、Prop的Attribute继承、禁用Attribute继承
6.1、自定义组件使用v-model实现双向数据绑定
v-model主要用于表单的双向数据绑定,v-model实现自定义组件的双向数据绑定。
6.1.1、单个v-model数据绑定
默认情况下,组件上的v-model使用modelValue作为prop和update:modelValue作为事件。可通过向v-model传递参数来修改这些名称
子组件Input.vue
<template>
<input type="text" :value="keyword" placeholder="请输入内容">
</template>
<script>
export default {
//接收父组件传递的keyword
props:["keyword"]
}
</script>
<style lang="scss" scoped>
input {
width: 400px;
height: 32px;
line-height: 32px;
}
</style>
父组件Home.vue
<template>
<div class="home">
{{msg}}
<br>
<!-- 3、使用自定义组件 -->
<v-input v-model:keyword="keyword"></v-input>
</div>
</template>
<script>
// 1、引入自定义Input组件
import Input from "./Input.vue"
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
keyword:"女装"
}
},
methods: {
},
//2、注册组件
components: {
"v-input":Input,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
App.vue
<template>
<!-- 模板中写HTML页面 -->
<!-- 3、使用组件 -->
<Home/>
</template>
<script>
//1、引入Home组件
import Home from './components/Home.vue';
// script中写业务逻辑
export default {
data(){
return {
msg: "app根组件"
}
},
//2、挂载组件
components: {
Home,
}
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
子组件将需要一个foo prop并发出update:foo要同步的事件
input.vue
<template>
<!-- 当自定义input框的值发生改变时触发自定义事件"$emit('update:womenswear',$event.target.value),
需要把input框的值传递给父组件。广播的名称是固定写法:update:womenswear(属性名称),获取当前值:$event.target.value -->
<input type="text"
:value="womenswear"
@input="$emit('update:womenswear',$event.target.value)"
placeholder="请输入内容">
</template>
<script>
export default {
//接收父组件传递的属性womenswear
props:["womenswear"]
}
</script>
<style lang="scss" scoped>
input {
width: 400px;
height: 32px;
line-height: 32px;
}
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<br>
<!-- 3、使用自定义组件 -->
<!-- v-model:womenswear才能实现双向数据绑定,womenswear为属性名称;:womenswear只能单向绑定数据-->
<!-- 调用v-input自定义组件,进行双向数据绑定 -->
<v-input v-model:womenswear="keyword"></v-input>
<br>
{{keyword}}
</div>
</template>
<script>
// 1、引入自定义Input组件
import Input from "./Input.vue"
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
keyword:"女装"
}
},
methods: {
},
//2、注册组件
components: {
"v-input":Input,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
6.1.2、多个v-model绑定
利用特定的prop和事件为目标的能力,v-model参数中,可以在单个组件实例上创建多个v-model绑定。
每个v-model将同步到不同的prop,而不需要在组件中添加额外的选项。
UserName.vue
<template>
<input type="text" :value="firstName" @input="$emit('update:firstName',$event.target.value)" placeholder="firstName">
<br><br>
<input type="text" :value="lastName" @input="$emit('update:lastName',$event.target.value)" placeholder="lastName">
</template>
<script>
export default {
props:[
"firstName",
"lastName",
]
}
</script>
<style>
input {
width: 100px;
height: 32px;
line-height: 32px;
}
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<br>
<!-- 3、使用自定义组件 -->
<!-- v-model:womenswear才能实现双向数据绑定,womenswear为属性名称;:womenswear只能单向绑定数据-->
<!-- 调用v-input自定义组件,进行双向数据绑定 -->
<v-input v-model:womenswear="keyword"></v-input>
<br>
{{keyword}}
<br>
<hr>
<h2>UserName组件</h2>
<user-name v-model:firstName="firstName" v-model:lastName="lastName"></user-name>
<br>
{{firstName}}--{{lastName}}
</div>
</template>
<script>
// 1、引入自定义Input组件
import Input from "./Input.vue"
import UserName from "./UserName.vue"
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
keyword:"女装",
firstName:"",
lastName:"",
}
},
methods: {
},
//2、注册组件
components: {
"v-input":Input,
UserName,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
6.2、自定义组件slots
Vue实现了一套内容分发的API,设计灵感来自于Web Components规范草案,将元素作为承载分发内容的出口。
6.2.1、自定义一个按钮组件
Button.vue
<template>
<button class="default">按钮</button>
</template>
<script>
export default {
}
</script>
<style>
.default {
padding: 5px 10px;
background: orange;
color: #fff;
border: none;
}
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<br>
<!-- 3、使用自定义组件 -->
<!-- v-model:womenswear才能实现双向数据绑定,womenswear为属性名称;:womenswear只能单向绑定数据-->
<!-- 调用v-input自定义组件,进行双向数据绑定 -->
<v-input v-model:womenswear="keyword"></v-input>
<br>
{{keyword}}
<br>
<hr>
<h2>UserName组件</h2>
<user-name v-model:firstName="firstName" v-model:lastName="lastName"></user-name>
<br>
{{firstName}}--{{lastName}}
<br><br>
<v-button></v-button>
</div>
</template>
<script>
// 1、引入自定义Input组件
import Input from "./Input.vue";
import UserName from "./UserName.vue";
import Button from "./Button.vue";
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
keyword:"女装",
firstName:"",
lastName:"",
}
},
methods: {
},
//2、注册组件
components: {
"v-input":Input,
UserName,
"v-button":Button,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
6.2.2、调用这个组件
slot还允许在自定义组件里面传入任意的HTML标签或其它组件
Button.vue
<button class="default"><slot></slot></button>
Home.vue
<v-button>提交</v-button>
<br><br>
<v-button>
<i>icon</i>
搜索
</v-button>
slot还可以绑定父组件的数据
Home.vue
<v-button>提交</v-button>
<br><br>
<v-button>
<i>icon</i>搜索
{{msg}}
</v-button>
6.2.3、slots默认值
<button class="default" type="submit"><slot>Submit</slot></button>
<button type="submit">Submit</button>
6.3、非Prop的Attribute继承
一个非prop的attribute是指传向一个组件,但该组件并没有相应props或emits定义的attribute。包括class、style和id属性。
Button.vue
<template>
<button><slot>default</slot></button>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.default {
padding: 5px 10px;
background: orange;
color: #fff;
border: none;
}
.primary {
padding: 5px 10px;
background: blue;
color: #fff;
border: none;
}
</style>
Home.vue
<v-button class="primary">取消</v-button>
6.3.1、当组件返回单个根节点时,非prop attribute将自动添加到根节点的attribute中
Home.vue
<v-button class="primary" data-aid="123">取消</v-button>
6.3.2、同样的规则适用于事件监听器
6.3.3、完整示例
6.4、自定义Attribute继承
不希望组件的根元素继承attribute,可在组件的选项中设置inheritAttrs:false。禁用attribute继承的情况是需要将attribute应用于根节点之外的其它元素。
通过将inheritAttes选项设置为false,可以在访问组件的$attris property,该property包括组件props和emits property中未包含的所有属性(如class、style、v-on监听器等)。
DatePicker.vue
<template>
<div class="date-picker">
<input type="date" v-bind="$attrs">
</div>
</template>
<script>
export default {
// 禁用默认继承
inheritAttrs: false,
data(){
return {
}
}
}
</script>
Home.vue
<template>
<div class="home">
<date-picker data-time="2022-06-11"></date-picker>
</div>
</template>
<script>
// 1、引入自定义组件
import DatePicker from "./DatePicker.vue";
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
}
},
methods: {
},
//2、注册组件
components: {
DatePicker,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
6.5、多个根节点上的Attribute继承
与单个根节点组件不同,具有多个根节点的组件不具有自动attribute回退行为。如果未显式绑定$attrs,将发出运行时警告。
<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
//这将发出警告
app.component('custom-layout',{
template:`
<header></header>
<main></main>
`
})
//没有警告,$attrs被传递到<main>元素
app.component('custom-layout',{
template:`
<header></header>
<main v-bind="$attrs"></main>
`
})
7、生命周期函数、动态组件keep-alive、this.$nextTick
7.1、生命周期函数
beforeCreate
在实例化之后,数据观测(data observer)和event/watcher事件配置之前被调用。
created
在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer)property和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$el property目前尚不可用。
beforeMount
在挂载开始之前被调用:相关的render函数首次被调用。
mounted
实例被挂载后调用,这时Vue.createApp({}).mount()被新创建的vm.
e
l
替
换
了
。
如
果
根
实
例
挂
载
到
了
一
个
文
档
内
的
元
素
上
,
当
m
o
u
n
t
e
d
被
调
用
时
v
m
.
el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时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
卸载组件实例后调用,调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载
LifeCycle.vue
<template>
<h2>生命周期函数</h2>
<br>
{{msg}}
<button @click="msg='改变后的值'">改变msg</button>
</template>
<script>
export default {
data(){
return {
msg:"生命周期"
}
},
beforeCreate(){
console.log("实例刚刚被创建1");
},
created(){
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(){
// 页面销毁的时候要保存一些数据,就可以监听这个销毁的生命周期函数
console.log("实例销毁之前")
},
unmounted(){
console.log("实例销毁完成")
}
}
</script>
Home.vue
<template>
<div class="home">
{{msg}}
<br>
<button @click="isShow=!isShow">挂载/卸载组件</button>
<life-cycle v-if="isShow"></life-cycle>
</div>
</template>
<script>
// 1、引入自定义组件
import LifeCycle from "./LifeCycle.vue";
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
isShow: true,
}
},
methods: {
},
//2、注册组件
components: {
LifeCycle,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
7.2、动态组件keep-alive
当在这些组件之间切换时,有时会想保持这些组件的状态,以避免反复重复渲染导致的性能问题,此时候就用keep-alive。
在不同路由切换时想保持组件的状态可以使用keep-alive
<keep-alive>
<life-cycle v-if="isShow"></life-cycle>
</keep-alive>
keep-alive结合Tab切换
LifeCycle.vue
<template>
<h2>生命周期函数</h2>
<br>
{{ msg }}
<button @click="msg = '改变后的值'">改变msg</button>
<br>
<div class="tab_info">
<ul class="tab_header">
<li v-for="(item, index) in posts" :key="index" :class="{ 'active': currentIndex === index }"
@click="currentIndex = index">{{ item.title }}</li>
</ul>
<div class="tab_content">
<!-- {{content}} -->
<span v-html="content"></span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: "生命周期",
currentIndex: 0,
posts: [
{
id: 1,
title: "Nodejs教程",
content: "<p>大地老师Nodejs系列教程</p>"
},
{
id: 2,
title: "Golang教程",
content: "<p>Golang零基础入门</p>"
},
{
id: 3,
title: "Ionic5.x教程",
content: "<p>Ionic4.x后在Angular、Vue、React基础上做了封装</p>"
}
]
}
},
computed: {
content() {
return this.posts[this.currentIndex].content;
}
},
beforeCreate() {
console.log("实例刚刚被创建1");
},
created() {
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() {
// 页面销毁的时候要保存一些数据,就可以监听这个销毁的生命周期函数
console.log("实例销毁之前")
},
unmounted() {
console.log("实例销毁完成")
}
}
</script>
<style lang="scss" scoped>
.h2 {
text-align: center;
}
ul {
list-style: none;
}
.tab_info {
width: 600px;
margin: 0 auto;
border: 1px solid #eee;
.tab_header {
width: 100%;
height: 40px;
line-height: 40px;
li {
display: inline-block;
margin: 0px 5px;
background: #eee;
text-align: center;
cursor: pointer;
&.active {
background: blue;
color: #fff;
}
}
}
.tab_content {
padding: 40px;
}
}
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<br>
<button @click="isShow=!isShow">挂载/卸载组件</button>
<!-- keep-alive缓存组件状态 -->
<keep-alive>
<life-cycle v-if="isShow"></life-cycle>
</keep-alive>
</div>
</template>
<script>
// 1、引入自定义组件
import LifeCycle from "./LifeCycle.vue";
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
isShow: true,
}
},
methods: {
},
//2、注册组件
components: {
LifeCycle,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
7.3、this.$nextTick
Vue可把获取DOM节点的代码放在mounted里面,如果要在渲染完成数据后获取DOM节点就需要用到this.$nextTick。
mounted(){
this.$nextTick(function(){
//仅在渲染整个视图后运行的代码
})
},
LifeCycle.vue
<template>
<h2>生命周期函数</h2>
<br>
<div id="msg">
{{ msg }}
</div>
<button @click="msg = '改变后的值'">改变msg</button>
<br>
<div class="tab_info">
<ul class="tab_header">
<li v-for="(item, index) in posts" :key="index" :class="{ 'active': currentIndex === index }"
@click="currentIndex = index">{{ item.title }}</li>
</ul>
<div class="tab_content">
<!-- {{content}} -->
<span v-html="content"></span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
msg: "生命周期",
currentIndex: 0,
posts: [
{
id: 1,
title: "Nodejs教程",
content: "<p>大地老师Nodejs系列教程</p>"
},
{
id: 2,
title: "Golang教程",
content: "<p>Golang零基础入门</p>"
},
{
id: 3,
title: "Ionic5.x教程",
content: "<p>Ionic4.x后在Angular、Vue、React基础上做了封装</p>"
}
]
}
},
computed: {
content() {
return this.posts[this.currentIndex].content;
}
},
beforeCreate() {
console.log("实例刚刚被创建1");
},
created() {
console.log("实例已经创建完成2");
},
beforeMount() {
console.log("模板编译之前3");
},
mounted() {
// 请求数据,操作dom,放在这个里面 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)
})
console.log("模板编译完成4");
},
beforeUpdate() {
console.log("数据更新之前")
},
updated() {
console.log("数据更新完毕");
},
activated() {
console.log("keep-alive 缓存的组件激活时调用")
},
deactivated() {
console.log("keep-alive 缓存的组件停用时调用")
},
beforeUnmount() {
// 页面销毁的时候要保存一些数据,就可以监听这个销毁的生命周期函数
console.log("实例销毁之前")
},
unmounted() {
console.log("实例销毁完成")
}
}
</script>
<style lang="scss" scoped>
.h2 {
text-align: center;
}
ul {
list-style: none;
}
.tab_info {
width: 600px;
margin: 0 auto;
border: 1px solid #eee;
.tab_header {
width: 100%;
height: 40px;
line-height: 40px;
li {
display: inline-block;
margin: 0px 5px;
background: #eee;
text-align: center;
cursor: pointer;
&.active {
background: blue;
color: #fff;
}
}
}
.tab_content {
padding: 40px;
}
}
</style>
8、全局绑定属性、使用Axios和fetchJsonp请求真实api接口数据、函数防抖实现百度搜索
8.1、使用Axios请求API接口数据
8.1.1源码
https://github.com/axios/axios
8.1.2、安装
npm install axios --save
yarn add axios
8.1.3、引入使用
News.vue
<template>
<div class="home">
<button @click="getData">Axios获取数据</button>
<br><br>
<hr>
<ul>
<li v-for="(item,index) in list" :key="index">{{item.title}}--{{item.aid}}</li>
</ul>
</div>
</template>
<script>
import axios from "axios";
export default {
data(){
return {
list: [],
}
},
methods:{
getData(){
var api = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
axios.get(api).then(
(response)=>{
console.log(response.data.result);
this.list = response.data.result;
}
).catch(
(err)=>{
console.log(err);
}
)
},
}
}
</script>
Home.vue
<template>
<div class="home">
<news></news>
</div>
</template>
<script>
// 1、引入自定义组件
import News from "./News.vue"
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
isShow: true,
}
},
methods: {
},
//2、注册组件
components: {
News,
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
8.1.4、全局绑定Axios
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import Axios from "axios";
const app = createApp(App)
app.config.globalProperties.axios = Axios;
app.mount('#app')
News.vue
<template>
<div class="home">
<button @click="getData">Axios获取数据</button>
<br><br>
<hr>
<ul>
<li v-for="(item,index) in list" :key="index">{{item.title}}--{{item.aid}}</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
list: [],
}
},
methods:{
getData(){
var api = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
this.Axios.get(api).then(
(response)=>{
console.log(response.data.result);
this.list = response.data.result;
}
).catch(
(err)=>{
console.log(err);
}
)
},
}
}
</script>
8.1.5、全局绑定Storage
新建src/modules/storage.js
storage.js
const storage = {
set(key,value){
localStorage.setItem(key,JSON.stringify(value));
},
get(key){
return JSON.parse(localStorage.getItem(key));
},
remove(key){
localStorage.removeItem(key);
}
}
//暴露storage
export default storage;
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
// 全局引入
import Axios from "axios";
import Storage from "./modules/storage"
const app = createApp(App)
// 全局绑定
app.config.globalProperties.axios = Axios; //this.Axios
app.config.globalProperties.Storage= Storage; //this.Storage
app.mount('#app')
News.vue
<template>
<div class="home">
<button @click="getData">Axios获取数据</button>
<br><br>
<hr>
<ul>
<li v-for="(item,index) in list" :key="index">{{item.title}}--{{item.aid}}</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
list: [],
}
},
methods:{
getData(){
var api = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
this.Axios.get(api).then(
(response)=>{
console.log(response.data.result);
this.list = response.data.result;
}
).catch(
(err)=>{
console.log(err);
}
)
},
},
mounted(){
//设置
this.Storage.set("username","zhangsan");
//获取
console.log(this.Storage.get("username"));
}
}
</script>
浏览器
8.2、使用fetch-jsonp请求jsonp接口
axios不支持jsonp请求,可用jsonp来请求数据可以使用fetch-jsonp这个模块。
8.2.1、源码
https://github.com/camsong/fetch-jsonp
8.2.2、安装
npm install fetch-jsonp --save
8.2.3、引入使用
main.js全局引入
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
// 全局引入
import Axios from "axios";
import Storage from "./modules/storage"
import fetchJsonp from 'fetch-jsonp';
const app = createApp(App)
// 全局绑定
app.config.globalProperties.axios = Axios; //this.Axios
app.config.globalProperties.Storage= Storage; //this.Storage
app.config.globalProperties.fetchJsonp=fetchJsonp; //this.fetchJsonp
app.mount('#app')
Baidu.vue
<template>
<div>
<button @click="getData">fetch-jsonp获取数据</button>
<br><br>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
list:[],
}
},
methods:{
getData(){
// 这里注意this的指向
// 有两种方法解决this指向问题
// 第一种方式:
// var that = this;
// 调用时用:that.list = data.s;
// 第二种方式用箭头函数(推荐使用)
var that = this;
// https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=golang&cb=xxxx
var api = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=php";
this.fetchJsonp(api,{
//回调函数:百度回调函数是cb
jsonpCallback:"cb",
})
.then(function (response){
return response.json();
})
// .then(function (json){
// console.log(json);
// })
.then(function (data){
console.log(data.s)
that.list = data.s;
})
.catch(function (error){
console.log(error);
})
}
},
}
</script>
<template>
<div>
<button @click="getData">fetch-jsonp获取数据</button>
<br><br>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
list: [],
}
},
methods: {
getData() {
// 这里注意this的指向
// 有两种方法解决this指向问题
// 第一种方式:
// var that = this;
// 调用时用:that.list = data.s;
// 第二种方式用箭头函数(推荐使用)
// https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=golang&cb=xxxx
var api = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=php";
this.fetchJsonp(api, {
//回调函数:百度回调函数是cb
jsonpCallback: "cb",
})
.then((response) => {
return response.json();
})
// .then((json)=>{
// console.log(json);
// })
.then((data) => {
console.log(data.s)
// 用到this一定要注意this指向,使用箭头函数解决
this.list = data.s;
})
.catch((error) => {
console.log(error);
})
}
},
}
</script>
8.2.4、防抖功能
Baidu.vue
<template>
<div>
<input type="text" v-model="keyword" @keyup="getData" placeholder="防抖功能">
<br><br>
<button @click="getData">fetch-jsonp获取数据</button>
<br><br>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
keyword: "",
list: [],
timer: "",
}
},
methods: {
getData() {
// 防抖功能
if (this.keyword != "") {
// 清除定时器
clearTimeout(this.timer);
this.timer = setTimeout(() => {
// https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=golang&cb=xxxx
var api = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + this.keyword;
this.fetchJsonp(api, {
//回调函数:百度回调函数是cb
jsonpCallback: "cb",
})
.then((response) => {
return response.json();
})
// .then((json)=>{
// console.log(json);
// })
.then((data) => {
console.log(data.s)
// 用到this一定要注意this指向,使用箭头函数解决
this.list = data.s;
})
.catch((error) => {
console.log(error);
})
}, 200)
} else {
this.list = [];
}
}
},
}
</script>
9、mixin实现组件功能的复用
9.1、mixin介绍
混入(mixin)提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被"混入"进入该组件本身的选项。
baseMixin.js
新建src/mixin/baseMixin.js
const baseMixn = {
data(){
return{
apiUrl:"http://www.itying.con",
msg:"baseMixin",
}
},
methods:{
success(){
console.log("成功");
}
}
}
export default baseMixin;
9.2、mixin的选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行合并。
9.2.1、数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
Home.vue
<template>
<div class="home">
<!-- 使用 -->
<span>首页模板--{{apiUrl}}</span>
<br>
<button @click="success">执行Mixin里面的方法</button>
<br>
<h3>关于mixin的选项合并</h3>
<br>
{{msg}}
</div>
</template>
<script>
// 1、引入自定义组件
import baseMixin from '../mixin/baseMixin';
// 暴露组件
export default {
//混入
mixins: [baseMixin],
data(){
return {
msg: "Home父组件",
isShow: true,
}
},
methods: {
},
//2、注册组件
components: {
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
9.3、全局配置mixin
为Vue应用程序全局应用mixin
baseMinix.js
const baseMixin = {
data(){
return{
apiUrl:"http://www.itying.con",
msg:"baseMixin",
}
},
methods:{
success(){
console.log("成功");
}
}
}
export default baseMixin;
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
// 全局引入
import Axios from "axios";
import Storage from "./modules/storage"
import fetchJsonp from 'fetch-jsonp';
import baseMixin from "./mixin/baseMixin"
const app = createApp(App)
// 全局绑定
app.config.globalProperties.axios = Axios; //this.Axios
app.config.globalProperties.Storage= Storage; //this.Storage
app.config.globalProperties.fetchJsonp=fetchJsonp; //this.fetchJsonp
app.mixin(baseMixin);
app.mount('#app')
Home.vue
<template>
<div class="home">
<!-- 使用 -->
<span>首页模板--{{apiUrl}}</span>
<br>
<button @click="success">执行Mixin里面的方法</button>
<br>
<h3>关于mixin的选项合并</h3>
<br>
{{msg}}
</div>
</template>
<script>
// 1、引入自定义组件
// 暴露组件
export default {
data(){
return {
msg: "Home父组件",
isShow: true,
}
},
methods: {
},
//2、注册组件
components: {
}
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
10、Teleport、使用Teleport实现一个模态对话框的组件
10.1、Teleport
Vue3.x中的组件模板属于该组件,需要把模板的内容移动到当前组件之外的DOM中,这时就可以使用Teleport。
表示teleport内包含的内容显示到body中
<teleport to="body">
内容
</teleport>
<teleport to="#app">
内容
</teleport>
10.2、使用Teleport实现一个模态对话框的组件
Modal.vue
<template>
<teleport to="body">
<!-- <teleport to="#app"> -->
<div class="modal-bg" v-if="visible">
<div class="modal-content">
<button class="close" @click="$emit('close-modal')"> X </button>
<div class="model-title">{{ title }}</div>
<div class="model-body">
<slot>模态对话框</slot>
</div>
</div>
</div>
</teleport>
</template>
<script>
export default {
// 定义父子传递事件
emits: ["close-modal"],
// 接收父子传值
props: ["visible", "title"],
}
</script>
<style lang="scss" scoped>
.modal-bg {
background: #000;
opacity: 0.7;
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
.modal-content {
width: 600px;
min-height: 300px;
border: 1px solid #eee;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
.model-title {
background: #eee;
color: #000;
height: 32px;
line-height: 32px;
text-align: center;
}
.model-body {
padding: 40px;
}
.close {
position: absolute;
right: 10px;
top: 5px;
display: inline-block;
width: 40px;
border: none;
cursor: pointer;
}
}
</style>
Home.vue
<template>
<div class="home">
<button @click="isVisible = true">弹出一个模态对话框</button>
<br>
<!-- 父子组件传值 -->
<modal :visible="isVisible" :title="title" @close-modal="isVisible = false"></modal>
</div>
</template>
<script>
// 1、引入自定义组件
import Modal from "./Modal.vue"
// 暴露组件
export default {
data() {
return {
title: "用户登录",
isVisible: false,
}
},
methods: {
},
//2、注册组件
components: {
Modal,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
11、Composition API
11.1、Composition API简介
Composition API也叫组合式API,是Vue3.x的新特性。
通过创建Vue组件,可以将接口的可重复部分及其功能提却到可宠用的代码段中。仅此一项就可以使应用程序在可维护性和灵活性方面走的更远。然而,经验已证明,光靠这一点是不够的,尤其是当应用程序变得非常大时——几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。
没有Composition API之前Vue相关业务代码需要配置到option的特定区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的。
composition-api提供了以下几个函数:
- setup
- ref
- reactive
- watchEffect
- watch
- computed
- toRefs
- 生命周期的hooks
11.2、setup组件选项
新的setup组件选项在创建组件之前执行,一旦props被解析,并充当合成API的入口。
提示
由于在执行setup时尚未创建组件实例,因此在setup选项中没有this。这意味着,除了props之外,将无法访问组件中声明的任何属性——本地状态、计算属性或方法。
使用setup函数时,将接受两个参数
11.2.1、Props
11.2.2、上下文
11.2.3、setup组件的property
11.2.4、ref reactive以及setup结合模板
Home.vue
<template>
<div class="home">
{{title}}
<br>
{{userinfo.username}}--{{userinfo.age}}
<br><br>
<button @click="getTitle">获取title</button>
<br><br>
<button @click="getUserName">获取username</button>
<br><br>
<button @click="setTitle">修改title</button>
<br><br>
<button @click="setUserName">修改username</button>
<br><br>
<input type="text" v-model="title" placeholder="双向数据绑定">
<br><br>
<input type="text" v-model="userinfo.username" placeholder="双向数据绑定">
</div>
</template>
<script>
// 1、引入自定义组件
import { ref,reactive } from 'vue'
// 暴露组件
export default {
msg: "Home组件",
setup(){
// ref 定义响应式数据,定义字符串、Num、bool、数组
// reactive定义响应式数据,定义对象
var title = ref("我是一个标题");
var userinfo = reactive({
username:"张三",
age:20,
});
// 获取reactive定义的数据
var getUserName = ()=>{
alert(userinfo.username);
}
// 获取ref定义的数据
var getTitle = ()=>{
alert(title.value);
}
//修改reactive定义的数据
var setUserName= ()=>{
userinfo.username="李四";
}
//修改ref定义的数据
var setTitle= ()=>{
title.value="修改后的ref里面的title";
}
return {
title,
userinfo,
getUserName,
getTitle,
setUserName,
setTitle,
}
},
methods: {
},
//2、注册组件
components: {
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
11.2.5、使用this
11.3、toRefs解构响应式对象数据
把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref,和响应式对象property一一对应。
Home.vue
<template>
<div class="home">
{{title}}
<br>
{{userinfo.username}}--{{userinfo.age}}
<br><br>
<button @click="getTitle">获取title</button>
<br><br>
<button @click="getUserName">获取username</button>
<br><br>
<button @click="setTitle">修改title</button>
<br><br>
<button @click="setUserName">修改username</button>
<br><br>
<input type="text" v-model="title" placeholder="双向数据绑定">
<br><br>
<input type="text" v-model="userinfo.username" placeholder="双向数据绑定">
<br><br>
{{desc}}---{{clicks}}
</div>
</template>
<script>
// 1、引入自定义组件
import { ref,reactive,toRefs } from 'vue'
// 暴露组件
export default {
msg: "Home组件",
setup(){
// ref 定义响应式数据,定义字符串、Num、bool、数组
// reactive定义响应式数据,定义对象
var title = ref("我是一个标题");
var userinfo = reactive({
username:"张三",
age:20,
});
var article = reactive({
desc: "我是一个新闻",
clicks: 201,
})
// 获取reactive定义的数据
var getUserName = ()=>{
alert(userinfo.username);
}
// 获取ref定义的数据
var getTitle = ()=>{
alert(title.value);
}
//修改reactive定义的数据
var setUserName= ()=>{
userinfo.username="李四";
}
//修改ref定义的数据
var setTitle= ()=>{
title.value="修改后的ref里面的title";
}
return {
title,
userinfo,
getUserName,
getTitle,
setUserName,
setTitle,
// 三点运算符进行合并
// ...article, //错误写法,会导致article失去响应式特性
...toRefs(article)
}
},
methods: {
},
//2、注册组件
components: {
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
11.4、computed计算属性
Login.vue
<template>
<div class="login">
<h2>获取用户信息</h2>
<input type="text" v-model="firstName" placeholder="firstName">
<br><br>
<input type="text" v-model="lastName" placeholder="lastName">
<br>
{{ firstName }}---{{ lastName }}
<br>
{{ fullName }}
<br><br>
<input type="text" v-model="score" placeholder="判断是否及格">
{{pass}}
</div>
</template>
<script>
import { computed, reactive, toRefs } from 'vue'
export default {
setup() {
// 定义响应式对象
let userinfo = reactive({
firstName: "",
lastName: "",
score: 78,
})
let fullName = computed(() => {
return userinfo.firstName + " " + userinfo.lastName;
})
let pass = computed(() => {
if (userinfo.score > 60) {
return "及格"
} else {
return "不及格"
}
})
return {
// 解构userinfo
...toRefs(userinfo),
fullName,
pass,
}
},
// computed: {
// fullName: function () {
// return userinfo.firstName + " " + userinfo.lastName;
// }
// }
}
</script>
<style lang="scss" scoped>
.login {
padding: 20px;
}
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<br><br>
<login></login>
</div>
</template>
<script>
import Login from "./Login.vue"
// 暴露组件
export default {
data() {
return {
msg: "Home组件",
}
},
methods: {
},
//2、注册组件
components: {
Login,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
11.5、readonly"深层"的只读代理
传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理。一个只读代理是"深层的",对象内部任何嵌套的属性也都是只读的。
Login.vue
<template>
<div class="login">
<h2>获取用户信息</h2>
<input type="text" v-model="firstName" placeholder="firstName">
<br><br>
<input type="text" v-model="lastName" placeholder="lastName">
<br>
{{ firstName }}---{{ lastName }}
<br>
{{ fullName }}
<br><br>
<input type="text" v-model="score" placeholder="判断是否及格">
{{pass}}
</div>
</template>
<script>
import { computed, reactive, readonly, toRefs } from 'vue'
export default {
setup() {
// 非响应式数据 原始对象
let obj = {
username: "张山",
age: "",
}
// 定义响应式对象
let userinfo = reactive({
firstName: "",
lastName: "",
score: 78,
})
// 将响应式对象转为非响应式对象
userinfo = readonly(userinfo);
let fullName = computed(() => {
return userinfo.firstName + " " + userinfo.lastName;
})
let pass = computed(() => {
if (userinfo.score > 60) {
return "及格"
} else {
return "不及格"
}
})
return {
// 解构userinfo
...toRefs(userinfo),
fullName,
pass,
}
},
// computed: {
// fullName: function () {
// return userinfo.firstName + " " + userinfo.lastName;
// }
// }
}
</script>
<style lang="scss" scoped>
.login {
padding: 20px;
}
</style>
11.6、watch与watchEffect区别
对比watchEffect,watch允许
- 懒执行,就是说仅在侦听的源变更时才执行回调;
- 更明确哪些状态的改变会触发侦听器重新运行;
- 访问侦听状态变化前后的值;
11.6.1、watchEffect
在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
Search.vue
<template>
Search组件---{{num}}
</template>
<script>
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)
}
}
}
</script>
Home.vue
<template>
<div class="home">
{{msg}}
<br><br>
<login></login>
<br>
<hr>
<search></search>
</div>
</template>
<script>
import Login from "./Login.vue"
import Search from "./Search.vue"
// 暴露组件
export default {
data() {
return {
msg: "Home组件",
}
},
methods: {
},
//2、注册组件
components: {
Login,
Search,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
11.6.2、watch
Search.vue
<template>
<h2>Search组件</h2>
<input type="text" v-model="keyword">
{{keyword}}
</template>
<script>
import { ref , watch } from 'vue'
export default {
setup(){
let keyword = ref("")
watch(keyword,(newData,oldData)=>{
console.log(newData,oldData);
})
return {
keyword
}
}
}
</script>
11.7、组合式api生命周期钩子
可以通过在生命周期钩子前面加上"on"来访问组件的生命周期钩子。
下表包含如何在 setup () 内部调用生命周期钩子:
选项式 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 |
activated | onActivated |
deactivated | onDeactivated |
11.8、props
Home.vue
<template>
<div class="home">
{{msg}}
<br><br>
<search :msg="msg"/>
</div>
</template>
<script>
import Search from "./Search.vue"
// 暴露组件
export default {
data() {
return {
msg: "Home组件",
}
},
methods: {
},
//2、注册组件
components: {
Search,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
Search.vue
<template>
<h2>Search组件</h2>
</template>
<script>
export default {
props: ['msg'],
setup(props){
console.log(props);
}
}
</script>
12、Provider Inject
当需要将数据从父组件传递到子组件时,使用props。有一些深嵌套的组件,只需要来自深嵌套子组件中父组件的某些内容。在这种情况下,仍需要将props传递到整个组件链中,这可能会很烦人。
对于这种情况,可以使用provide和inject对父组件可作为其所有子组件的依赖项提供程序,而不管组件层次结构有多深。这个特性有两个部分:父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这个数据。
12.1、组合式api写法
12.1.1、Provide
在setup()中使用provide时,首先从的那个Vue显示导入provide方法。能够调用provide时来定义每个property。
provide函数允许通过两个参数定义property
- property的name(类型)
- property的value
使用自定义组件,提供的值可以按如下方式重构,provide、inject实现父子组件传值时,父子组件数据改变会相互影响。
App.vue
<template>
<!-- 模板中写HTML页面 -->
<!-- 3、使用组件 -->
<Home/>
</template>
<script>
//1、引入Home组件
import { provide } from 'vue';
import Home from './components/Home.vue';
// script中写业务逻辑
export default {
name: "App",
data(){
return {
msg: "app根组件",
}
},
//2、挂载组件
components: {
Home,
},
// provide方式一
// provide: {
// title: "app组件里面的标题",
// }
// provide方式二
provide(){
return {
msg: this.msg,
title:"app组件里面的标题",
userinfo:{
username:"zhangsan",
age:20
}
}
}
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
Home.vue
<template>
<div class="home">
{{msg}}
<hr>
<location></location>
</div>
</template>
<script>
import Location from "./Location.vue"
// 暴露组件
export default {
data() {
return {
msg: "Home组件",
}
},
methods: {
},
//2、注册组件
components: {
Location,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
Location.vue
<template>
<h3>Location组件---{{title}}</h3>
<br>
{{userinfo.username}}---{{userinfo.age}}
<br>
{{msg}}
</template>
<script>
export default {
// 注入
inject: ["title","userinfo","msg"]
}
</script>
<style lang="scss" scoped>
</style>
12.1.2、Inject
App.vue
<template>
<!-- 模板中写HTML页面 -->
<h3>{{msg}}</h3>
<!-- 数据双向绑定:父组件值改变,子组件同样会发生变化 -->
<button @click="setTitle">执行方法改变父组件的title</button>
<br>
<input type="text" v-model="username"/>
<hr>
<!-- 3、使用组件 -->
<Home/>
</template>
<script>
//1、引入Home组件
import { provide, reactive, ref, toRefs } from 'vue';
import Home from './components/Home.vue';
// script中写业务逻辑
export default {
name: "App",
setup(){
let msg = ref("App组件");
let userinfo = reactive({
username: "张三",
age: 20,
})
let title = ref("app根组件里面的title");
// 将provide传递给子组件
provide("title",title);
provide("userinfo",userinfo);
let setTitle=()=>{
title.value = "改变后的app根组件的title"
}
return {
msg,
title,
setTitle,
userinfo,
...toRefs(userinfo),
}
},
//2、挂载组件
components: {
Home,
},
}
</script>
<style lang="scss" scoped>
/* sytle中写css样式 */
</style>
Home.vue
<template>
<div class="home">
<h3>{{msg}}</h3>
<hr>
<location></location>
</div>
</template>
<script>
import Location from "./Location.vue";
// 暴露组件
export default {
data() {
return {
msg: "Home组件",
}
},
methods: {
},
//2、注册组件
components: {
Location,
}
}
</script>
<style lang="scss" scoped>
.home {
position: relative;
}
</style>
Location.vue
<template>
<h3>Location组件</h3>
{{title}}
<br>
<!-- 数据双向绑定:子组件值改变,父组件同样会发生变化 -->
<input type="text" v-model="userinfo.username"/>
<br>
{{userinfo.username}}---{{userinfo.age}}
</template>
<script>
import { inject } from "vue";
export default {
setup(){
// 注入
let title = inject("title");
let userinfo = inject("userinfo")
// 返回
return {
title,
userinfo,
}
}
}
</script>
<style lang="scss" scoped>
</style>
13、集成Typescript
13.1、项目创建
https://v3.cn.vuejs.org/guide/typescript-support.html#项目创建
Vue CLI 可以生成使用 TypeScript 的新项目
# 1. Install Vue CLI, 如果尚未安装
npm install --global @vue/cli@next
# 2. 创建一个新项目, 选择 "Manually select features" 选项
vue create vue-demo
# 3. 如果已经有一个不存在TypeScript的 Vue CLI项目,请添加适当的 Vue CLI插件
vue add typescript
13.2、集成scss
npm install -D sass-loader node-sass
13.3、添加组件
Home.vue
<template>
<div>
Home组件---{{title}}
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
title: "我是一个Home组件"
}
}
})
</script>
<style lang="scss" scoped>
</style>
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<Home/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Home from "./components/Home.vue";
export default defineComponent({
name: 'App',
components: {
Home,
}
});
</script>
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
})
News.vue
<template>
新闻组件---{{title}}
<br>
<button @click="setCount">改变count值</button>{{count}}
<br>
{{reverseTitle}}
</template>
<script lang="ts">
import { defineComponent } from "vue";
// data数据的接口
interface News{
title:string,
description:string,
count:number|string,//可以指定多个类型
content?:string,
}
// let newsData:News={
// title:"我是一个新闻",
// description:"新闻描述",
// count:10,
// };
let newsData={
title:"我是一个新闻",
description:"新闻描述",
count:12
} as News;
export default defineComponent({
data(){
return newsData;
},
methods:{
setCount():void{
this.count = 123;
}
},
computed:{
reverseTitle():string{
return this.title.split("").reverse().join("");
}
},
})
</script>
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<Home/>
<hr>
<News/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Home from "./components/Home.vue";
import News from "./components/News.vue";
export default defineComponent({
name: 'App',
components: {
Home,
News,
}
});
</script>
13.4、Typescript与组合式API使用
User.vue
<template>
<div>
User组件
<br>
<button @click="setUsername('旺旺')">修改username</button>{{ username }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "vue";
interface User {
username: string,
age: number | string,
setUsername(username: string): void,
getUsername(): string,
}
export default defineComponent({
setup() {
let user = reactive({
username: "张三",
age: "20",
setUsername(username: string) {
this.username = username;
},
getUsername() {
return this.username;
}
});
let count = ref<number | string>("20");
return {
...toRefs(user),
count,
}
}
})
</script>
User.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<Home/>
<hr>
<News/>
<hr>
<User/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
export default defineComponent({
name: 'App',
components: {
Home,
News,
User,
}
});
</script>
14、路由、路由配置
14.1、Vue3.x中的路由
路由可以让应用程序根据用户输入的不同地址动态挂载不同的组件。
https://router.vuejs.org/
-
router-link
如何使用自定义组件来创建链接,而不是使用常规a标签。router-link这允许 Vue Router 在不重新加载页面的情况下更改 URL,处理 URL 生成及其编码。
-
router-view
router-view将显示与 url 对应的组件。可以把它放在任何地方以适应布局。
14.2、路由的基本配置
14.2.1、安装路由模块
npm install vue-router@4
npm install vue-router@next --save
yarn add vue-router@4
14.2.2、新建组件
Home组件、User组件、News组件
14.2.3、配置路由
新建src/routes.ts配置路由
配置路由routes.ts
import { createRouter, createWebHashHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
// 配置路由
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: "/", component: Home },
{ path: "/news", component: News },
{ path: "/user", component: User },
]
})
export default router
挂载路由main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import route from "./routes"
const app = createApp(App)
// 挂载路由
app.use(route)
app.mount('#app')
渲染组件App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 渲染组件:组件映射到路由并让 Vue Router 知道在哪里渲染 -->
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
访问
http://localhost:8080/#/
http://localhost:8080/#/user
http://localhost:8080/#/news
router-link
App.vue
<template>
<ul class="header">
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<router-link to="/user">用户</router-link>
</li>
<li>
<router-link to="/news">新闻</router-link>
</li>
</ul>
<!-- 渲染组件:组件映射到路由并让 Vue Router 知道在哪里渲染 -->
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
<style lang="scss">
* {
margin: 0px;
padding: 0px;
}
.header {
width: 100%;
height: 44px;
line-height: 44px;
list-style: none;
background: #000;
color: #fff;
li {
display: inline-block;
margin-right: 20px;
color: #fff;
a {
color: #fff;
}
}
}
</style>
14.3、路由、路由配置、动态路由、get传值、路由跳转
14.3.1、动态路由
NewsContent.vue
<template>
<div>
NewsContent组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
}
}
})
</script>
<style lang="scss">
</style>
路由跳转
import { createRouter, createWebHashHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
// 配置路由
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: "/", component: Home },
{ path: "/news", component: News },
{ path: "/user", component: User },
// 配置动态路由
{path: "/newcontent/:aid", component: NewsContent},
]
})
// 暴露组件
export default router
News.vue
<template>
News组件
<br>
<ul>
<li v-for="(item, index) in list" :key="index">
<!-- 配置动态路由 -->
<router-link :to="`/newscontent/${index}`">{{ item }}</router-link>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// data数据的接口
interface News {
list: string[];
}
export default defineComponent({
data() {
return {
list: []
} as News;
},
mounted() {
for (let i = 0; i < 10; i++) {
this.list.push(`我是第${i}个新闻`);
}
},
})
</script>
获取动态路由的传值
NewsContent.vue
<template>
<div>
NewsContent组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
}
},
mounted(){
// 获取动态路由的传值
console.log(this.$route.params);
}
})
</script>
<style lang="scss">
</style>
14.3.2、Get传值
route.ts
import { createRouter, createWebHashHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
// 配置路由
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: "/", component: Home, alias: "/home" },
{ path: "/news", component: News },
{ path: "/user", component: User },
// 配置动态路由
{ path: "/newscontent", component: NewsContent },
]
})
// 暴露组件
export default router
News.vue
<template>
News组件
<br>
<ul>
<li v-for="(item, index) in list" :key="index">
<!-- 配置动态路由 -->
<router-link :to="`/newscontent?aid=${index}`">{{ item }}</router-link>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// data数据的接口
interface News {
list: string[];
}
export default defineComponent({
data() {
return {
list: []
} as News;
},
mounted() {
for (let i = 0; i < 10; i++) {
this.list.push(`我是第${i}个新闻`);
}
},
})
</script>
NewContent.vue
<template>
<div>
NewsContent组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
}
},
mounted(){
// 获取get传值
console.log(this.$route.query);
}
})
</script>
<style lang="scss">
</style>
14.4、路由编程式导航(Js跳转路由)
NewsContent.vue
<template>
<div>
NewsContent组件
<br>
<button @click="toHome">js跳转路由</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
}
},
methods:{
toHome(){
// js跳转路由
this.$router.push({
path: "/home"
})
}
},
mounted(){
// 获取get传值
console.log(this.$route.query);
}
})
</script>
<style lang="scss">
</style>
Home.vue
<template>
<div>
Home组件---{{title}}
<br>
<button @click="this.$router.push({path:'/news'})">跳转到新闻</button>
<br>
<button @click="this.$router.push({path:'/newscontent',query:{aid:14}})">跳转到新闻详情get传值</button>
</div>
</template>
<script lang="ts">
let title: string = "我是一个Home组件";
import { defineComponent } from "vue";
export default defineComponent({
data(){
return {
title,
}
}
})
</script>
<style lang="scss" scoped>
</style>
14.5、路由、路由模式、命名路由、路由重定向、路由别名
14.5.1、路由HTML5 History模式和hash模式
hash模式
src/routes.ts
import { createRouter, createWebHashHistory } from "vue-router";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
routes: [
...
]
})
HTML5 History
import { createRouter, createWebHistory } from "vue-router";
// 配置路由
const router = createRouter({
// HTML History模式
history: createWebHistory(),
routes: [
...
]
})
注意:开启HTML5 History模式后,发布到服务器需要配置伪静态
https://router.vuejs.org/guide/essentials/history-mode.html
14.6、命名路由
通过一个名称来标识一个路由显得方便一些,特别是在链接一个路由,或者是执行一些跳转时。可以创建Router实例时,在routes配置中给某个路由设置名称。
const router = new VueRouter({
routes: [{
path: '/user/:userId',
name: 'user',
component: User
}]
})
要链接到一个命名路由,可以给route-link的to属性传递一个对象
<router-link :to="{name:'user',params:{userId:123}}">User</router-link>
跟代码调用router.push()是一回事
this.$router.push({name:'user',params:{userId:123}})
这两种方式都会把路由导航到/user/123路径。
this.$router.push({name:'newscontent',query:{aid:567}})
命名路由跳转
routes.ts
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
// HTML History模式
// history: createWebHistory(),
routes: [
{ path: "/", component: Home, alias: "/home" },
{ path: "/news", component: News , name:"news"},
{ path: "/user", component: User },
// 配置动态路由
{ path: "/newscontent",name:"newscontent", component: NewsContent },
]
})
// 暴露组件
export default router
App.vue
<template>
<ul class="header">
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<router-link to="/user">用户</router-link>
</li>
<li>
<!-- <router-link to="/news">新闻</router-link> -->
<!-- 使用命名路由 -->
<router-link :to="{ name: 'news' }">新闻</router-link>
</li>
</ul>
<!-- 渲染组件:组件映射到路由并让 Vue Router 知道在哪里渲染 -->
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
<style lang="scss">
* {
margin: 0px;
padding: 0px;
}
.header {
width: 100%;
height: 44px;
line-height: 44px;
list-style: none;
background: #000;
color: #fff;
li {
display: inline-block;
margin-right: 20px;
color: #fff;
a {
color: #fff;
}
}
}
</style>
News.vue
<template>
News组件
<br>
<ul>
<li v-for="(item, index) in list" :key="index">
<!-- 配置动态路由 -->
<!-- <router-link :to="`/newscontent?aid=${index}`">{{ item }}</router-link> -->
<!-- 使用命名路由 -->
<router-link :to="{name:'newscontent',query:{aid:index}}">{{item}}</router-link>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// data数据的接口
interface News {
list: string[];
}
export default defineComponent({
data() {
return {
list: []
} as News;
},
mounted() {
for (let i = 0; i < 10; i++) {
this.list.push(`我是第${i}个新闻`);
}
},
})
</script>
命名路由传值
UserInfo.vue
<template>
<div>
UserInfo组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
})
</script>
routes.ts
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
import UserInfo from "./components/UserInfo.vue";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
// HTML History模式
// history: createWebHistory(),
routes: [
{ path: "/", component: Home, alias: "/home" },
{ path: "/news", component: News, name: "news" },
{ path: "/user", component: User },
// 配置动态路由
{ path: "/newscontent", name: "newscontent", component: NewsContent },
{ path: "/userinfo/:id", name: "userinfo", component: UserInfo },
]
})
// 暴露组件
export default router
User.vue
<template>
<div>
User组件
<br>
<button @click="setUsername('旺旺')">修改username</button>{{ username }}
<br>
<router-link :to="{ name: 'userinfo', params: { id: 123 } }">跳转到用户详情</router-link>
<br>
<button @click="this.$router.push({ name: 'userinfo', params: { id: 456 } })">js跳转路由到用户详情</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "vue";
interface User {
username: string,
age: number | string,
setUsername(username: string): void,
getUsername(): string,
}
export default defineComponent({
setup() {
let user = reactive({
username: "张三",
age: "20",
setUsername(username: string) {
this.username = username;
},
getUsername() {
return this.username;
}
});
let count = ref<number | string>("20");
return {
...toRefs(user),
count,
}
}
})
</script>
News.vue
<template>
News组件
<br>
<ul>
<li v-for="(item, index) in list" :key="index">
<!-- 配置动态路由 -->
<!-- <router-link :to="`/newscontent?aid=${index}`">{{ item }}</router-link> -->
<!-- 使用命名路由 -->
<router-link :to="{name:'newscontent',query:{aid:index}}">{{item}}</router-link>
</li>
<br>
<button @click="this.$router.push({name:'newscontent',query:{aid:567}})">js跳转路由到新闻详情</button>
</ul>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// data数据的接口
interface News {
list: string[];
}
export default defineComponent({
data() {
return {
list: []
} as News;
},
mounted() {
for (let i = 0; i < 10; i++) {
this.list.push(`我是第${i}个新闻`);
}
},
})
</script>
14.7、路由重定向
重定向在routes配置中完成,要从重定向/a到/b
const routes = [{path:'/home',redirect: {name:'homepage'} }]
重定向也可以针对命名路由
const routes = [{path:'/home',redirect:{name:'homepage'}}]
甚至使用函数进行动态重定向
rotues.ts
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
import UserInfo from "./components/UserInfo.vue";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
// HTML History模式
// history: createWebHistory(),
routes: [
{path:'',redirect:"/home"}, //路由重定向
{ path: "/", component: Home, alias: "/home" },
{ path: "/news", component: News, name: "news" },
{ path: "/user", component: User },
// 配置动态路由
{ path: "/newscontent", name: "newscontent", component: NewsContent },
{ path: "/userinfo/:id", name: "userinfo", component: UserInfo },
]
})
// 暴露组件
export default router
访问
http://localhost:8080
14.8、路由别名
重定向是指用户访问时/home,URL将被替换/,然后与匹配/。
别名/as/home表示用户访问时/home,URL保持不变/home,但将被匹配,就像用户正在访问时一样。
以上内容可以在路由配置中表示为
const routes = [{path:'/',component:Homepage,alias:'/home'}]
别名可以自由地将UI结构映射到任意URL,而不受配置的嵌套结构约束。使别名以a开头,/以使路径在嵌套路由中使绝对的。甚至可以将两者结合起来,并为数组提供多个别名
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
//- /users
//- /users/list
//- /people
{path:'',component:UsersList,alias:['/people','list']},
],
},
]
如果路径包含参数,请确保将其包含在任何绝对别名中
rotues.ts
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import User from "./components/User.vue";
import NewsContent from "./components/NewsContent.vue";
import UserInfo from "./components/UserInfo.vue";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
// HTML History模式
// history: createWebHistory(),
routes: [
{ path: '', redirect: "/home" }, //路由重定向
{ path: "/", component: Home, alias: "/home" },
{ path: "/news", component: News, name: "news", alias: ["/n", "/c"] },
{ path: "/user", component: User, alias: "/people" },
// 配置动态路由
{ path: "/newscontent", name: "newscontent", component: NewsContent },
{ path: "/userinfo/:id", name: "userinfo", alias: "/u/:id", component: UserInfo },
]
})
// 暴露组件
export default router
访问
http://localhost:8080/#/people
http://localhost:8080/#/n
http://localhost:8080/#/c
http://localhost:8080/#/u/123
14.9、嵌套路由
User.vue
<template>
<div class="content">
<div class="left">
<ul>
<!-- 静态路由 -->
<li>
<router-link to="/user/userlist">用户列表</router-link>
</li>
<li>
<router-link to="/user/useradd">新增用户</router-link>
</li>
</ul>
</div>
<div class="right">
<!-- 动态挂载路由 -->
<router-view></router-view>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
}
})
</script>
<style lang="scss">
.content {
display: flex;
.left {
width: 200px;
min-height: 400px;
border-right: 1px solid #eee;
padding: 20px;
ul {
list-style: none;
li {
margin-bottom: 20px;
text-align: center;
}
}
}
.right {
flex: 1;
}
}
</style>
UserList.vue
src/component/User/UserList.vue
<template>
<div>
UserList组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
})
</script>
UserAdd.vue
src/component/User/UserAdd.vue
<template>
<div>
UserAdd组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
})
</script>
News.vue
<template>
<div class="content">
<div class="left">
<ul>
<li>
<router-link to="/news/newslist">新闻列表</router-link>
</li>
<li>
<router-link to="/news/newsadd">新增新闻</router-link>
</li>
</ul>
</div>
<div class="right">
<router-view></router-view>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
}
})
</script>
<style lang="scss">
.content {
display: flex;
.left {
width: 200px;
min-height: 400px;
border-right: 1px solid #eee;
padding: 20px;
ul {
list-style: none;
li {
margin-bottom: 20px;
text-align: center;
}
}
}
.right {
flex: 1;
}
}
</style>
NewsList.vue
src/component/News/NewsList.vue
<template>
<div>
NewsList组件
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
})
</script>
NewsAdd.vue
src/component/News/NewsAdd.vue
<template>
<div>
NewsAdd组件
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
})
</script>
routes.ts
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
// 引入组件
import Home from "./components/Home.vue";
import News from "./components/News.vue";
import NewsList from "./components/News/NewsList.vue";
import NewsAdd from "./components/News/NewsAdd.vue";
import User from "./components/User.vue";
import UserAdd from "./components/User/UserAdd.vue";
import UserList from "./components/User/UserList.vue";
// 配置路由
const router = createRouter({
// hash模式
history: createWebHashHistory(),
// HTML History模式
// history: createWebHistory(),
routes: [
{ path: '', redirect: "/home" },
{ path: '/home', component: Home },
{
path: '/news', component: News, name: "news",
children: [
{ path: '', redirect: "/news/newslist" },
{ path: 'newslist', component: NewsList },
{ path: 'newsadd', component: NewsAdd },
]
},
{
path: '/user', component: User,
children: [
{ path: '', redirect: "/user/userlist" },
{ path: 'userlist', component: UserList },
{ path: 'useradd', component: UserAdd }
]
},
]
})
// 暴露组件
export default router
App.vue
<template>
<ul class="header">
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<!-- <router-link to="/news">新闻</router-link> -->
<!-- 使用命名路由 -->
<router-link :to="{ name: 'news' }">新闻</router-link>
</li>
<li>
<router-link to="/user">用户</router-link>
</li>
</ul>
<!-- 渲染组件:组件映射到路由并让 Vue Router 知道在哪里渲染 -->
<router-view></router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
});
</script>
<style lang="scss">
* {
margin: 0px;
padding: 0px;
}
.header {
width: 100%;
height: 44px;
line-height: 44px;
list-style: none;
background: #000;
color: #fff;
li {
display: inline-block;
margin-right: 20px;
color: #fff;
a {
color: #fff;
}
}
}
</style>
15、状态管理模式Vuex
15.1、Vuex
Vuex是一个转为Vue.js应用程序开发的状态管理模式。
官网:https://vuex.vuejs.org/
主要功能:
- Vuex可实现Vue中不同组件之间的状态共享(解决了不同组件之间的数据共享)
- 可实现组件里面数据的持久化
Vuex的几个核心概念
- State
- Getters
- Mutations
- Actions
- Modules
15.2、Vuex基本使用
15.2.1、安装依赖
npm install vuex@next --save
yarn add vuex@next --save
15.2.2、store.js
src目录下新建一个vuex的文件夹,vuex文件夹里面新建一个store.js
import { createStore } from "vuex";
const store = createStore({
state(){
return {
count:1,
}
}
})
export default store
15.2.3、main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import route from "./routes"
// 引入状态
import store from "./vuex/store";
const app = createApp(App)
// 挂载路由
app.use(route)
// 挂载状态
app.use(store)
app.mount('#app')
15.3、State
15.3.1、获取State的方法
1、第一种获取State的方法(不推荐)
用到组件里面引入的store,然后计算属性里面获取
computed: {
count(){
return store.state.count
}
}
2、第二种获取State的方法
由于全局配置了Vuex app.use(store),所以直接可以通过下面方法获取store里面的值
computed:{
count(){
return this.$store.state.count
}
}
3、第三种获取State的方法,通过mapState助手
computed:{
...mapState({
count:(state)=>state.count,
list:(state)=>state.title,
})
}
示例
store.js
import { createStore } from "vuex";
const store = createStore({
// 定义数据
state() {
return {
count: 1,
title:['马总','李总','刘总'],
}
},
// 定义方法
mutations: {
incCount(state) {
state.count++;
},
setCount(state, num) {
state.count = num;
}
},
})
export default store
Home.vue
<template>
<div>
Home组件---{{ title }}
<br>
<button @click="addCount">执行Vuex里面的count方法</button>---{{ num }}
<br><br>
<button @click="setCount">执行Vuex里面的方法传值</button>---{{ $store.state.count }}
<br><br>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}---{{count}}</li>
</ul>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { mapState } from "vuex";
export default defineComponent({
data() {
return {
title: "我是一个Home组件",
}
},
methods: {
// 方法调用
addCount() {
this.$store.commit('incCount')
},
// 传值
setCount() {
this.$store.commit('setCount', 15);
}
},
computed: {
num() {
return this.$store.state.count;
},
// 三点运算符,合并对象;映射store中要使用的数据
...mapState(["count","list"])
}
})
</script>
<style lang="scss" scoped>
</style>
15.4、Getter
15.4.1、定义Getter
15.4.2、访问Getter方法
第一种访问Getter方法
Getter会暴露为store.getters对象,可以以属性的形式访问这些值
store.getters.doneTodos
第二种访问Getter方法
computed:{
doneTodosCount(){
return this.$store.getters.doneTodoCount
}
}
第三种访问Getter方法,通过mapGetters辅助函数
computed:{
//使用对象展开运算符讲getter混入computed对象中
...mapGetters([
'doneTodoCount',
'anotherGetter',
])
}
如果想将一个getter属性另取一个名字,使用对象形式
...mapGetters([ "reverseMsg","num"])
...mapGetters({
num:"num",
reverseMsg:"reverseMsg"
})
示例
store.js
import { createStore } from "vuex";
const store = createStore({
// 定义数据
state() {
return {
count: 1,
title: ['马总', '李总', '刘总'],
msg: "你好vue",
}
},
// 定义方法
mutations: {
incCount(state) {
state.count++;
},
setCount(state, num) {
state.count = num;
},
setMsg(state, msg) {
state.msg = msg;
},
},
// 计算属性
getters: {
reverseMsg(state) {
return state.msg.split("").reverse().join("");
},
num(state) {
return state.count + 10;
}
}
})
export default store
Home.vue
<template>
<div>
Home组件---{{ title }}
<br>
<button @click="addCount">执行Vuex里面的count方法</button>---{{ num }}
<br><br>
<button @click="setCount">执行Vuex里面的方法传值</button>---{{ $store.state.count }}
<br><br>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}---{{ count }}</li>
</ul>
<br>
<span>获取Vuex中getters里面的数据</span>---{{ revMsg }}
<br>
<span>执行Vuex中getters里面的方法</span>---{{ count }}
<br>
<span>mapGetters</span>---{{ num }}---{{ reverseMsg }}
<br>
<button @click="setMsg">改变state里面的msg</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { mapGetters, mapState } from "vuex";
export default defineComponent({
data() {
return {
title: "我是一个Home组件",
}
},
methods: {
// 方法调用
addCount() {
this.$store.commit('incCount')
},
// 传值
setCount() {
this.$store.commit('setCount', 15);
},
setMsg() {
this.$store.commit('setMsg', '改变后的数据');
}
},
computed: {
num() {
return this.$store.state.count;
},
// 三点运算符,合并对象;映射store中要使用的数据
...mapState({
count: (state) => state.count,
list: (state) => state.title,
}),
// 获取getters里面的数据
revMsg() {
return this.$store.getters.reverseMsg;
},
// 执行getters里面的方法
count() {
return this.$store.getters.num;
},
// ...mapGetters(["reverseMsg","num"])
...mapGetters({
num: "num",
reverseMsg: "reverseMsg"
})
}
})
</script>
<style lang="scss" scoped>
</style>
15.5、Mutations
更改Vuex的store中的状态的唯一方法是提交mutation。Vuex中的mutation非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数实际进行状态更改的地方,并且会接受state作为第一个参数。
15.5.1、定义Mutations触发Mutations里面的方法
const store = createStore({
state:{
count:1
},
mutations:{
increment(state){
state.count++
}
}
})
触发mutations里面的方法
store.commit('increment')
15.6、Actions
Action类似于mutation,不同在于
- Action提交的是mutation,而不是直接变更状态
- Action可以包含任意异步操作
15.6.1、定义Action
import { createStore } from "vuex";
const store = createStore({
// 定义数据
state() {
return {
count: 1,
}
},
// 定义方法
mutations: {
incCount(state) {
state.count++;
},
},
//执行mutations里面的方法,异步操作放在actions
actions:{
addCount(context){
// 执行mutations的addCount方法
context.commit("incCount")
},
}
})
export default store
另一种写法
actions:{
addCount({commit}){
// 执行mutations的addCount方法
context.commit("incCount")
},
}
15.6.2、分发Action(触发Action中的方法)
store.dispatch("addCount")
mutation必须同步执行,Action就不受约束,可以在actions内部执行异步操作
store.js
import { createStore } from "vuex";
const store = createStore({
// 定义数据
state() {
return {
count: 1,
title: ['马总', '李总', '刘总'],
msg: "你好vue",
}
},
// 定义方法
mutations: {
incCount(state) {
state.count++;
},
setCount(state, num) {
state.count = num;
},
setMsg(state, msg) {
state.msg = msg;
},
},
// 计算属性
getters: {
reverseMsg(state) {
return state.msg.split("").reverse().join("");
},
num(state) {
return state.count + 10;
}
},
//执行mutations里面的方法,异步操作放在actions
actions:{
addCount(context){
// 执行mutations的addCount方法
context.commit("incCount")
},
// setMsg(context,msg){
// setTimeout(() => {
// 执行mutations的setMsg方法
// context.commit("setMsg",mgs);
// }, 1000);
// }
// 结构解析
setMsg({commit},msg){
setTimeout(() => {
commit("setMsg",msg);
}, 1000);
}
}
})
export default store
Home.vue
<template>
<div>
Home组件---{{ title }}
<br>
<button @click="addCount">执行Vuex里面的count方法---改变数量</button>---{{ num }}
<br><br>
<button @click="setCount">执行Vuex里面的方法传值</button>---{{ $store.state.count }}
<br><br>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}---{{ count }}</li>
</ul>
<br>
<span>获取Vuex中getters里面的数据</span>---{{ revMsg }}
<br>
<span>执行Vuex中getters里面的方法</span>---{{ count }}
<br>
<span>mapGetters</span>---{{ num }}---{{ reverseMsg }}
<br>
<button @click="setMsg">改变state里面的msg</button>
<hr>
<button @click="$store.dispatch('addCount')">触发action里面的方法</button>
<br><br>
<button @click="setActionMsg">触发action里面的方法-异步</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
import { mapGetters, mapState } from "vuex";
export default defineComponent({
data() {
return {
title: "我是一个Home组件",
}
},
methods: {
// 方法调用
addCount() {
this.$store.commit('incCount')
},
// 传值
setCount() {
this.$store.commit('setCount', 15);
},
setMsg() {
this.$store.commit('setMsg', '改变后的数据');
},
setActionMsg(){
this.$store.dispatch("setMsg","改变后的数据-action");
}
},
computed: {
num() {
return this.$store.state.count;
},
// 三点运算符,合并对象;映射store中要使用的数据
...mapState({
count: (state) => state.count,
list: (state) => state.title,
}),
// 获取getters里面的数据
revMsg() {
return this.$store.getters.reverseMsg;
},
// 执行getters里面的方法
count() {
return this.$store.getters.num;
},
// ...mapGetters(["reverseMsg","num"])
...mapGetters({
num: "num",
reverseMsg: "reverseMsg"
})
}
})
</script>
<style lang="scss" scoped>
</style>
15.7、Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决这个问题,Vuex允许将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块——从上至下进行同样方式的分割。
const moduleA={
state:()=>({...}),
mutations:{...},
actions:{...},
getters:{...}
}
const moduleB={
state:()=>({...}),
mutations:{...},
actions:{...},
}
const store=createStore({
modules:{
a:moduleA,
b:moduleB
}
})
store.state.a //->moduleA
store.state.b //->moduleB
示例
userStore.js
let userStore = {
// 定义数据
state() {
return {
count: 1,
title: ['马总', '李总', '刘总'],
msg: "你好vue",
}
},
// 定义方法
mutations: {
incCount(state) {
state.count++;
},
setCount(state, num) {
state.count = num;
},
setMsg(state, msg) {
state.msg = msg;
},
},
// 计算属性
getters: {
reverseMsg(state) {
return state.msg.split("").reverse().join("");
},
num(state) {
return state.count + 10;
}
},
//执行mutations里面的方法,异步操作放在actions
actions:{
addCount(context){
// 执行mutations的addCount方法
context.commit("incCount")
},
// setMsg(context,msg){
// setTimeout(() => {
// 执行mutations的setMsg方法
// context.commit("setMsg",mgs);
// }, 1000);
// }
// 结构解析
setMsg({commit},msg){
setTimeout(() => {
commit("setMsg",msg);
}, 1000);
}
}
}
export default userStore
newsStore.js
let newsStore={
// 定义数据
state() {
return {
list:["民生","娱乐"],
}
},
}
export default newsStore
store.js
import { createStore } from "vuex";
import newsStore from "./newsStore";
import userStore from "./userStore";
const store = createStore({
modules:{
"user":userStore,
"news":newsStore,
}
})
export default store
Home.vue
<template>
<div>
Home组件---{{ title }}
<br>
获取user模块里面的state:{{ $store.state.user.count }}
<br>
获取news模块里面的state:{{ $store.state.news.list }}
<br><br>
<button @click="addCount">改变user模块里面的数据</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
title: "我是一个Home组件",
}
},
methods: {
addCount() {
this.$store.commit("incCount");
}
},
computed: {
list() {
return this.$store.state.news.list;
}
}
})
</script>
<style lang="scss" scoped>
</style>
15.8、结合Composition api
组合式api中没有this.$store,可以使用userStore来替代
export default {
setup(){
const store = useStore()
}
}
15.8.1、组合式api中访问state和getters
export default {
setup(){
const store = useStore()
return {
count: computed(()=>store.state.count),
double:computed(()=>store.getters.double)
}
}
}
15.8.2、组合式api中访问Mutations and Actions
export default{
setup(){
const store = useStore()
return {
addCount:()=>store.commit('increment'),
asyncIncrement:()=>store.dispatch('asyncIncrement')
}
}
}
Home.vue
<template>
<div>
Home组件
<br>
<button @click="addCount">执行mutations方法</button>---{{ count }}---{{ num }}
<br><br>
<button @click="addActionCount">执行actions方法</button>---{{ count }}
<br><br>
<button @click="setMsg('玩玩呢')">执行action方法传值</button>---{{ msg }}
</div>
</template>
<script>
import { computed, defineComponent } from "vue";
import { useStore } from "vuex";
export default defineComponent({
setup() {
const store = useStore();
return {
msg: computed(() => {
return store.state.user.msg;
}),
count: computed(() => {
return store.state.user.count;
}),
addCount: () => {
store.commit("incCount");
},
num: computed(() => {
return store.getters.num;
}),
addActionCount: () => {
store.dispatch("addCount");
},
setMsg: (msg) => {
store.dispatch("setMsg", msg);
}
}
}
})
</script>
<style lang="scss" scoped>
</style>
15.9、Typescript中使用Vuex
https://vuex.vuejs.org/guide/typescript-support.html
首先需要在Vue项目中集成typescript
vue add typescript
提示:如果配置完ts后调用this.$store有警告信息,重启vscode,或者安装Vue3的插件后重启vscode重试。
15.9.1、修改store.js为store.ts
15.9.2、配置store.ts中的代码
Vuex与Typescript一起使用时,必须声明自己的模块扩充
import { ComponentCustomProperties } from "vue";
import { Store, createStore } from 'vuex';
declare module '@vue/runtime-core' {
interface State {
count: number,
title: string[],
msg: string,
}
interface ComponentCustomProperties {
$store: Store<State>
}
}
const store = createStore({
// 定义数据
state() {
return {
count: 1,
title: ['马总', '李总', '刘总'],
msg: "你好vue",
}
},
// 定义方法
mutations: {
incCount(state: any) {
state.count++;
},
setCount(state: any, num: number) {
state.count = num;
},
setMsg(state: any, msg: string) {
state.msg = msg;
},
},
// 计算属性
getters: {
reverseMsg(state: any) {
return state.msg.split("").reverse().join("");
},
num(state: any) {
return state.count + 10;
}
},
//执行mutations里面的方法,异步操作放在actions
actions: {
addCount(context) {
// 执行mutations的addCount方法
context.commit("incCount")
},
// setMsg(context,msg){
// setTimeout(() => {
// 执行mutations的setMsg方法
// context.commit("setMsg",mgs);
// }, 1000);
// }
// 结构解析
setMsg({ commit }, msg: string) {
setTimeout(() => {
commit("setMsg", msg);
}, 1000);
}
}
})
export default store
Home.vue
<template>
<div>
Home组件
<br>
<button @click="addCount">执行mutations方法</button>---{{ count }}---{{ num }}
<br><br>
<button @click="addActionCount">执行actions方法</button>---{{ count }}
<br><br>
<button @click="setMsg('玩玩呢')">执行action方法传值</button>---{{ msg }}
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {};
},
methods: {
addCount(): void {
this.$store.commit("incCount");
},
addActionCount(): void {
this.$store.dispatch("addCount");
},
setMsg(msg: string): void {
this.$store.dispatch("setMsg", msg);
}
},
computed: {
msg() {
return this.$store.state.msg;
},
count() {
return this.$store.state.count;
},
num() {
return this.$store.getters.num;
}
}
})
</script>
<style lang="scss" scoped>
</style>
16、Serverless
16.1、Serverless架构介绍
Serverless又名无服务器,所谓无服务器并非是说不需要依赖和依靠服务器等资源,而是开发者再也不用过多考虑服务器的问题,可以更专注在产品代码上,狭义的Serverless是Fass和Bass组成。
无人点餐、无人收银系统是一个颠覆传统行业(中西餐厅、奶茶店、酒吧、火锅店、食堂、KTV)的智能点单系统。无人点单,手机支付,免费收银员,免点餐员,免硬件,免布线的一套智能管理系统。
16.1.1、概念
Serverless是一种软件系统架构的思想和方法,它不是软件框架、类库或者工具。与传统框架的不同之处在于,完全由第三方管理,由事件触发,存在于无状态(Stateless)、暂存(可能只存在于一次调用的过程中)计算容器内。构建无服务应用程序意味着开发者可以专注在产品代码上,而无须管理和操作云端或本地的服务器或运行时(运行时通俗讲就是运行环境,如nodejs环境、java环境、php环境)。Serverless真正做到部署应用无需涉及基础设施的建设,自动构建、部署和启动服务。
通俗的讲:Serverless是构建和运行软件时不需要关心服务器的一种架构思想。老程序员都用过虚拟机,刚开始学Serverless时可以把它理解为虚拟机的升级版本。
虚拟主机已经是快被淘汰的上一代产物。云计算涌现出了很多改变传统IT架构和运维方式的新技术,比如虚拟机、容器、微服务,无论这些技术应用在哪些场景,降低成本、提升效率是云服务永恒的主题。Serverless的出现真正的解决了降低成本、提升效率的问题。它真正做到了弹性伸缩、高并发、按需收费、备份容灾、日志监控等。
16.1.2、传统模式和Serverless模式下项目开发上线流程
16.1.3、Serverless和ServerFul架构的区别
传统的ServerFul架构模式
ServerFul架构是n台Server通过网络通信的方式协作一起,可以说ServerFul架构是基于Server和网络通信(分布式计算)的软件实现架构,Server可以是虚拟机、物理机,以及基于硬件实现的云服务器。
Serverless架构模式
Serverless的核心特点就是实现自动弹性伸缩和按量付费。
Serverless相比ServerFul的特点
-
资源分配
在Serverless架构中,不用关心应用应用运行的资源(服务配置、磁盘大小)只提供一份代码就行。
-
计费方式
在Serverless架构中,计费方式按实际使用量计费(如函数调用次数、运行时长),不按传统的执行代码所需的资源计费(如固定CPU)。计费粒度也精确到了毫秒级,而不是传统的小时级。个别云厂商推出了每个月的免费额度,如腾讯云提供了每个月40万GBs的资源使用额度和100万次调用次数的免费额度。中小企业的网站访问量不是特别大的话完全可以免费使用。
-
弹性伸缩
Serverless架构的弹性伸缩更自动化、更精确,可以快速根据业务并发扩容更多的实例,甚至允许缩容到零实例状态来实现零费用,对用户来说是完全无感知的。而传统架构对服务器(虚拟机)进行扩容,虚拟机的启动速度比较慢,需要几分钟甚至更久。
16.1.4、Serverless的能力
-
计算能力
资源按需分配,无需申请资源;
Mwm:租户级别强隔离;
Docker:进程级别隔离;
Mwm+Docker轻量级资源毫秒级启动;
实时扩容,阶梯缩容;
按需收费; -
系统运维能力
1、性能保障
整个链路耗时毫秒级内,并支持VPC内网访问
2、安全保障
资源对用户不可见,安全由腾讯云提供专业的保障
提供进程级和用户级安全隔离
访问控制管理3、自动性扩缩容
根据CPU内容网络IO自动扩容底层资源
根据请求自动扩容函数实例,业务高峰期扩容,满足业务高并发需求,业务低峰期缩容,释放资源,降低成本4、自愈能力
每一次请求都是一个健康的实例。
Serverless中云函数被第一次调用会执行冷启动,Serverless中云函数被多次连续调用会执行热启动。
冷启动是指在服务器中新开辟一块空间提供一个函数实例运行,这个过程有点像把这个函数放到虚拟机里去运行,每次运行前都需要先启动虚拟机加载这个函数,以前冷启动非常耗时,但是目前云厂商已经能做到毫秒级别的冷启动,这个过程不需要关心,但是需要注意的是使用Session时可能会导致Session丢失,所以建议将Session保存到数据库。
热启动则是说如果一个云函数被持续触发,那就先不释放这个云函数实例,下次请求让然由之前已经创建的云函数实例来运行,就好比打开虚拟机运行完这个函数之后没有关闭虚拟机,而是让它待机,等待下一次重新触发调用运行,这样的好处就是省去了开机的过程。
-
业务运维能力
16.1.5、Serverless厂商
云厂商
亚马逊 AWS Lambda https://aws.amazon.com/cn/lambda/
谷歌 Google Cloud Functions https://cloud.goole.com/functions
微软Microsoft Azure https://www.azure.cn
阿里云函数计算 https://www.aliyun.com/product/fc
腾讯云 云函数 SCF(Serverless Cloud Function) https://cloud.tencent.com/product/scf
华为云 FunctionGraph https://www.huaweicloud.com/product/functiongraph.html
大家关心的问题
1、为什么不使用亚马逊、谷歌、微软、IBM的server less
在国内阿里云、腾讯云使用的更多一些。就像购买域名、服务器一样,首先想到的肯定不是国外的运营商,英语好的开源尝试一下。
2、阿里云、腾讯云、华为云为什么选择腾讯云
微信小程序的云开发就是基于腾讯云,选择腾云更方便和小程序对接
腾讯云在serverless方面相比其它厂商支持更好一些
腾讯云的技术在线客服非常棒
腾讯云和serverless合作在腾讯云中集成了serverless Framework可以用喜欢的框架开发serverless应用。可以快速部署老项目。
价格更便宜
3、会使用腾讯云的serverless后,其它服务商的serverless也会了吗
是的
16.1.6、部署serverless应用
通过Serverless Framework提供的云函数SCF组件快速创建与部署一个云函数项目。
Serverless Framework将项目快速部署到腾讯云Serverless平台,部署前,请确认已经注册腾讯云账号并完成实名认证。
安装serverless
npm install -g serverless
serverless -v
创建项目
在项目的目录上面运行serverless
serverless
部署
在serverless.yml文件所在的项目根目录,运行以下指令进行部署
serverless deploy
16.1.7、Serverless组成
广义的Serverless更多是指一种技术理念:Serverless是构建和运行软件是不需要关心服务器的一种架构思想。
侠义的Serverless是指现阶段主流的技术实现:侠义的Serverless是Fass和Bass组成。
16.2、项目
Serverless架构
Egg.js基础+Egg.js无人点餐后台管理系统以及API接口
Vue3.x基础+Vue无人点餐无人收银系统
项目部署到Serverless
count: computed(()=>store.state.count),
double:computed(()=>store.getters.double)
}
}
}
#### 15.8.2、组合式api中访问Mutations and Actions
export default{
setup(){
const store = useStore()
return {
addCount:()=>store.commit(‘increment’),
asyncIncrement:()=>store.dispatch(‘asyncIncrement’)
}
}
}
##### Home.vue
### 15.9、Typescript中使用Vuex
https://vuex.vuejs.org/guide/typescript-support.html
首先需要在Vue项目中集成typescript
vue add typescript
**提示**:如果配置完ts后调用this.$store有警告信息,重启vscode,或者安装Vue3的插件后重启vscode重试。
#### 15.9.1、修改store.js为store.ts
#### 15.9.2、配置store.ts中的代码
Vuex与Typescript一起使用时,必须声明自己的模块扩充
import { ComponentCustomProperties } from “vue”;
import { Store, createStore } from ‘vuex’;
declare module ‘@vue/runtime-core’ {
interface State {
count: number,
title: string[],
msg: string,
}
interface ComponentCustomProperties {
$store: Store<State>
}
}
const store = createStore({
// 定义数据
state() {
return {
count: 1,
title: [‘马总’, ‘李总’, ‘刘总’],
msg: “你好vue”,
}
},
// 定义方法
mutations: {
incCount(state: any) {
state.count++;
},
setCount(state: any, num: number) {
state.count = num;
},
setMsg(state: any, msg: string) {
state.msg = msg;
},
},
// 计算属性
getters: {
reverseMsg(state: any) {
return state.msg.split(“”).reverse().join(“”);
},
num(state: any) {
return state.count + 10;
}
},
//执行mutations里面的方法,异步操作放在actions
actions: {
addCount(context) {
// 执行mutations的addCount方法
context.commit(“incCount”)
},
// setMsg(context,msg){
// setTimeout(() => {
// 执行mutations的setMsg方法
// context.commit(“setMsg”,mgs);
// }, 1000);
// }
// 结构解析
setMsg({ commit }, msg: string) {
setTimeout(() => {
commit("setMsg", msg);
}, 1000);
}
}
})
export default store
##### Home.vue
## 16、Serverless
### 16.1、Serverless架构介绍
Serverless又名无服务器,所谓无服务器并非是说不需要依赖和依靠服务器等资源,而是开发者再也不用过多考虑服务器的问题,可以更专注在产品代码上,狭义的Serverless是Fass和Bass组成。
无人点餐、无人收银系统是一个颠覆传统行业(中西餐厅、奶茶店、酒吧、火锅店、食堂、KTV)的智能点单系统。无人点单,手机支付,免费收银员,免点餐员,免硬件,免布线的一套智能管理系统。
#### 16.1.1、概念
Serverless是一种软件系统架构的思想和方法,它不是软件框架、类库或者工具。与传统框架的不同之处在于,完全由第三方管理,由事件触发,存在于无状态(Stateless)、暂存(可能只存在于一次调用的过程中)计算容器内。构建无服务应用程序意味着开发者可以专注在产品代码上,而无须管理和操作云端或本地的服务器或运行时(运行时通俗讲就是运行环境,如nodejs环境、java环境、php环境)。Serverless真正做到部署应用无需涉及基础设施的建设,自动构建、部署和启动服务。
通俗的讲:Serverless是构建和运行软件时不需要关心服务器的一种架构思想。老程序员都用过虚拟机,刚开始学Serverless时可以把它理解为虚拟机的升级版本。
虚拟主机已经是快被淘汰的上一代产物。云计算涌现出了很多改变传统IT架构和运维方式的新技术,比如虚拟机、容器、微服务,无论这些技术应用在哪些场景,降低成本、提升效率是云服务永恒的主题。Serverless的出现真正的解决了降低成本、提升效率的问题。它真正做到了弹性伸缩、高并发、按需收费、备份容灾、日志监控等。
#### 16.1.2、传统模式和Serverless模式下项目开发上线流程
#### 16.1.3、Serverless和ServerFul架构的区别
##### **传统的ServerFul架构模式**
ServerFul架构是n台Server通过网络通信的方式协作一起,可以说ServerFul架构是基于Server和网络通信(分布式计算)的软件实现架构,Server可以是虚拟机、物理机,以及基于硬件实现的云服务器。
[外链图片转存中...(img-KJ29ArV1-1668475744416)]
##### Serverless架构模式
Serverless的核心特点就是实现自动弹性伸缩和按量付费。
##### Serverless相比ServerFul的特点
- 资源分配
在Serverless架构中,不用关心应用应用运行的资源(服务配置、磁盘大小)只提供一份代码就行。
- 计费方式
在Serverless架构中,计费方式按实际使用量计费(如函数调用次数、运行时长),不按传统的执行代码所需的资源计费(如固定CPU)。计费粒度也精确到了毫秒级,而不是传统的小时级。个别云厂商推出了每个月的免费额度,如腾讯云提供了每个月40万GBs的资源使用额度和100万次调用次数的免费额度。中小企业的网站访问量不是特别大的话完全可以免费使用。
- 弹性伸缩
Serverless架构的弹性伸缩更自动化、更精确,可以快速根据业务并发扩容更多的实例,甚至允许缩容到零实例状态来实现零费用,对用户来说是完全无感知的。而传统架构对服务器(虚拟机)进行扩容,虚拟机的启动速度比较慢,需要几分钟甚至更久。
#### 16.1.4、Serverless的能力
- 计算能力
资源按需分配,无需申请资源;
Mwm:租户级别强隔离;
Docker:进程级别隔离;
Mwm+Docker轻量级资源毫秒级启动;
实时扩容,阶梯缩容;
按需收费;
- 系统运维能力
1、性能保障
整个链路耗时毫秒级内,并支持VPC内网访问
2、安全保障
资源对用户不可见,安全由腾讯云提供专业的保障
提供进程级和用户级安全隔离
访问控制管理
3、自动性扩缩容
根据CPU内容网络IO自动扩容底层资源
根据请求自动扩容函数实例,业务高峰期扩容,满足业务高并发需求,业务低峰期缩容,释放资源,降低成本
4、自愈能力
每一次请求都是一个健康的实例。
Serverless中云函数被第一次调用会执行冷启动,Serverless中云函数被多次连续调用会执行热启动。
**冷启动**是指在服务器中新开辟一块空间提供一个函数实例运行,这个过程有点像把这个函数放到虚拟机里去运行,每次运行前都需要先启动虚拟机加载这个函数,以前冷启动非常耗时,但是目前云厂商已经能做到毫秒级别的冷启动,这个过程不需要关心,但是需要注意的是使用Session时可能会导致Session丢失,所以建议将Session保存到数据库。
**热启动**则是说如果一个云函数被持续触发,那就先不释放这个云函数实例,下次请求让然由之前已经创建的云函数实例来运行,就好比打开虚拟机运行完这个函数之后没有关闭虚拟机,而是让它待机,等待下一次重新触发调用运行,这样的好处就是省去了开机的过程。
- 业务运维能力
#### 16.1.5、Serverless厂商
##### 云厂商
亚马逊 AWS Lambda https://aws.amazon.com/cn/lambda/
谷歌 Google Cloud Functions https://cloud.goole.com/functions
微软Microsoft Azure https://www.azure.cn
阿里云函数计算 https://www.aliyun.com/product/fc
腾讯云 云函数 SCF(Serverless Cloud Function) https://cloud.tencent.com/product/scf
华为云 FunctionGraph https://www.huaweicloud.com/product/functiongraph.html
##### 大家关心的问题
1、为什么不使用亚马逊、谷歌、微软、IBM的server less
在国内阿里云、腾讯云使用的更多一些。就像购买域名、服务器一样,首先想到的肯定不是国外的运营商,英语好的开源尝试一下。
2、阿里云、腾讯云、华为云为什么选择腾讯云
微信小程序的云开发就是基于腾讯云,选择腾云更方便和小程序对接
腾讯云在serverless方面相比其它厂商支持更好一些
腾讯云的技术在线客服非常棒
腾讯云和serverless合作在腾讯云中集成了serverless Framework可以用喜欢的框架开发serverless应用。可以快速部署老项目。
价格更便宜
3、会使用腾讯云的serverless后,其它服务商的serverless也会了吗
是的
#### 16.1.6、部署serverless应用
通过Serverless Framework提供的云函数SCF组件快速创建与部署一个云函数项目。
Serverless Framework将项目快速部署到腾讯云Serverless平台,部署前,请确认已经注册腾讯云账号并完成实名认证。
##### 安装serverless
npm install -g serverless
serverless -v
##### 创建项目
在项目的目录上面运行serverless
serverless
##### 部署
在serverless.yml文件所在的项目根目录,运行以下指令进行部署
serverless deploy
#### 16.1.7、Serverless组成
广义的Serverless更多是指一种技术理念:Serverless是构建和运行软件是不需要关心服务器的一种架构思想。
侠义的Serverless是指现阶段主流的技术实现:侠义的Serverless是Fass和Bass组成。
### 16.2、项目
Serverless架构
Egg.js基础+Egg.js无人点餐后台管理系统以及API接口
Vue3.x基础+Vue无人点餐无人收银系统
项目部署到Serverless