Vue3基础

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. elmountedvm.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 () 内部调用生命周期钩子:

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

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

Home组件

### 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

Home组件

## 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值