VUE3

Ajax

仅对原生ajax作了解。

<script>
    function test(){
        // 实例化XMLHttpRequest
        var request = new XMLHttpRequest();
        // 设置回调函数处理响应结果
        request.onreadystatechange = function() {
            if (xmlhttp.readyState==4 && xmlhttp.status==200) {
                // 响应成功的逻辑
                document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
            }
        }
        // 设置请求方式和请求的资源路径
        request.open("GET","/try/ajax/ajax_info.txt",true);
        // 发送请求
        request.send();
    }
</script>

ES6

ECMAScript 6,JavaScript语言的版本。

let和const

letvar的差别:

  • let不能重复声明;

  • let有块级作用域,只能在花括号里面访问;

  • let不会预解析进行变量提升(必须先声明,再使用);

  • let 定义的全局变量不会作为window的属性。

const就是常量,变量的值不能修改,对于数组和对象来说指向的地址不能修改(比如给数组添加元素是可以的)。

所以推荐使用letconst

模板字符串

类似python的f"",里面可以换行,变量用${变量名}

let str =`${name}被评为本年级优秀学员`;

解构表达式

  • 数组解构赋值

    let [a, b, c] = [1, 2, 3];
    let [a, b, c, d = 4] = [1, 2, 3];	// 赋默认值

  • 对象解构赋值

    let {a, b} = {a: 1, b: 2};		// 新增变量名必须和属性名相同
    let {a: x, b: y} = {a: 1, b: 2};	// 使用:操作符指定新的变量名
    console.log(x, y);

  • 函数参数解构赋值

    function add([x, y]) {
      return x + y;
    }
    add([1, 2]); // 3

箭头函数

函数声明

let fn1 = function(){}	// 常规
let fn2 = ()=>{} // 箭头函数,此处不需要书写function关键字
let fn3 = x =>{} // 单参数可以省略(),多参数/无参数不可以!
let fn4 = x => console.log(x) // 只有一行方法体可以省略{};
let fun5 = x => x + 1 // 当函数体只有一句返回值时,可以省略花括号和 return 语句

this关键字

  • 常规函数中this表示该函数所在的对象;
  • 箭头函数中this表示外层上下文环境的this(即所在对象的所在对象)。
let person ={
    name:"张三",
    showName:function (){
        console.log(this) //  这里的this是person
    },
    viewName: () =>{
        console.log(this) //  这里的this是window
    }
}

rest和spread

rest相当于可变参数。

let fun1 = function (...args){console.log(args)}
let fun2 = (...args) =>{console.log(args)}
// args会被当成数组
fun1(1,2,3)
fun2(1,2,3,4)

spread,类似python中的*,解包。

// 在实参上使用
let arr = [1,2,3]
let fun1 = (a,b,c) => {
    console.log(a,b,c)
}
fun1(...arr)	// 调用方法时,对arr进行转换 转换为1,2,3 

// 合并数组
let arr2 = [4,5,6]
let arr3 = [...arr,...arr2]
console.log(arr3)

// 合并对象属性
let p1 = {name:"张三"}
let p2 = {age:10}
let p3 = {gender:"boy"}
let person = {...p1,...p2,...p3}
console.log(person)

类与对象

class Person {
    // 属性
    #n;	// 带#表示私有,注意这个变量就叫#n
    age;
    get name(){
        return this.#n;
    }
    set name(n){
        this.#n = n;
    }
    // 实例方法
    eat(food){
        console.log(this.age+"岁的"+this.#n+"用筷子吃"+food)
    }
    // 静态方法
    static sum(a,b){
        return a + b;
    }
    // 构造器
    constructor(name,age){
        this.#n = name;
        this.age = age;
    }
}
let person =new Person("张三",10)
console.log(person.name)
console.log(person.#n)	// 报错
person.eat("火锅")
console.log(Person.sum(1,2))

class Student extends Person{
    score;
    study() {
    }
    constructor(name,age,score) {
        super(name,age);
        this.score = score;
    }
}
let stu =new Student("学生小李",18,99)
stu.eat("面条")

深拷贝和浅拷贝

let arr  =['java','c','python']
let person ={
    name:'张三',
    language:arr
}

// 浅拷贝,person2和person指向相同的内存
let arr2 = arr
let person2 = person;

// 深拷贝,通过json转换/用解构表达式
let arr2 = [...arr]
let person2 = JSON.parse(JSON.stringify(person))
let person3 = {...person}

模块化

类似java的import。

  • 分别导出

    // 模块想对外导出,添加export关键字即可
    // 导出一个变量
    export const PI = 3.14
    // 导出一个函数
    export function sum(a, b) {
        return a + b;
    }
    // 导出一个类
    export class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
        }
    }

    /* 
        *代表module.js中的所有成员
        m1代表所有成员所属的对象
    */
    import * as m1 from './module.js'
    // 使用暴露的属性
    console.log(m1.PI)
    // 调用暴露的方法
    let result =m1.sum(10,20)
    console.log(result)
    // 使用暴露的Person类
    let person =new m1.Person('张三',10)
    person.sayHello()

    <!-- 导入JS文件 添加type='module' 属性,否则不支持ES6的模块化 -->
    <script src="./app.js" type="module" />

  • 统一导出

    // 模块想对外导出,export统一暴露想暴露的内容
    // 定义一个常量
    const PI = 3.14
    // 定义一个函数
    function sum(a, b) {
        return a + b;
    }
    // 定义一个类
    class Person {
    	......
    }
    // 统一对外导出(暴露)
    export {PI, sum, Person}

    /* 
        {}中导入要使用的来自于module.js中的成员
        {}中导入的名称要和module.js中导出的一致,也可以在此处起别名
        {}中如果定义了别名,那么在当前模块中就只能使用别名
        {}中导入成员的顺序可以不是暴露的顺序
        一个模块中可以同时有多个import
        多个import可以导入多个不同的模块,也可以是同一个模块
    */
    //import {PI ,Person ,sum}  from './module.js'
    //import {PI as pi,Person as People,sum as add}  from './module.js'
    import {PI, Person, sum ,PI as pi, Person as People, sum as add}  from './module.js'
    // 使用暴露的属性
    console.log(PI)
    console.log(pi)
    // 调用暴露的方法
    let result1 =sum(10,20)
    let result2 =add(10,20)
    // 使用暴露的Person类
    let person1 =new Person('张三',10)
    person1.sayHello()
    let person2 =new People('李四',11)
    person2.sayHello()

  • 默认导出

    /* 
        默认暴露语法  export default sum
        默认暴露相当于是在暴露的对象中增加了一个名字为default的属性
        三种暴露方式可以在一个module中混合使用
    */
    export const PI = 3.14	// 分别导出
    function sum(a, b) {
        return a + b;
    }
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
        }
    }
    // 导出默认
    export default sum
    // 统一导出
    export {Person}

    /* 
        *代表module.js中的所有成员
        m1代表所有成员所属的对象
    */
    import * as m1 from './module.js'
    import {default as add} from './module.js' // 用的少
    import add2 from './module.js' // 等价于上面的
    
    // 调用暴露的方法
    let result = m1.default(10,20)
    console.log(result)
    let result2 = add(10,20)
    console.log(result2)
    let result3 = add2(10,20)
    console.log(result3)
    
    // 引入其他方式暴露的内容
    import {PI,Person} from './module.js'
    // 使用暴露的Person类
    let person = new Person('张三',10)
    person.sayHello()
    // 使用暴露的属性
    console.log(PI)

npm

  • 项目初始化

    npm init		# 创建package.json文件,包含项目基本信息
    npm init -y		# 使用默认信息创建

  • 安装依赖 :查看所有依赖地址https://www.npmjs.com

    npm install 包名			# 安装最新版本到当前项目
    npm install 包名@版本号	  # 安装指定版本到当前项目
    npm install -g 包名	    # 安装全局依赖包
    npm install				 # 安装package.json中的所有记录的依赖

  • 卸载依赖

    npm uninstall 包名

  • 升级依赖

    npm update 包名		# 将依赖升级到最新版本

  • 查看依赖

    npm ls			# 查看当前项目依赖
    npm list -g		# 查看全局依赖

  • 运行命令

    运行package.jsonscripts下的命令。

    // package.json
    {
    	......
        "scripts": {
            "start": "node index.js",
            "test": "jest",
            "build": "webpack"
        },
        ......
    }

    npm run test
    npm run build

Vite

创建、运行

npm create vite@latest	# 创建项目(或省略@latest)
npm create vite
npm install				# 安装依赖
npm run dev				# 启动

目录结构

  • public/:用于存放一些公共资源,如 HTML 文件、图像、字体等,这些资源会被直接复制到构建出的目标目录中。

  • src/:存放项目的源代码,包括 JavaScript、CSS、Vue 组件、图像和字体等资源。在开发过程中,这些文件会被 Vite 实时编译和处理,并在浏览器中进行实时预览和调试。以下是src内部划分建议:

    • assets/:用于存放一些项目中用到的静态资源,如图片、字体、样式文件等。

    • components/:用于存放组件相关的文件。组件是代码复用的一种方式,用于抽象出一个可复用的 UI 部件,方便在不同的场景中进行重复使用。

    • layouts/:用于存放布局组件的文件。布局组件通常负责整个应用程序的整体布局,如头部、底部、导航菜单等。

    • pages/:用于存放页面级别的组件文件,通常是路由对应的组件文件。在这个目录下,可以创建对应的文件夹,用于存储不同的页面组件。

    • plugins/:用于存放 Vite 插件相关的文件,可以按需加载不同的插件来实现不同的功能,如自动化测试、代码压缩等。

    • router/:用于存放 Vue.js 的路由配置文件,负责管理视图和 URL 之间的映射关系,方便实现页面之间的跳转和数据传递。

    • store/:用于存放 Vuex 状态管理相关的文件,负责管理应用程序中的数据和状态,方便统一管理和共享数据,提高开发效率。

    • utils/:用于存放一些通用的工具函数,如日期处理函数、字符串操作函数等。

    • main.js:这是 Vue.js 应用程序的启动文件,也是整个前端应用程序的入口文件。在该文件中,通常会引入 Vue.js 及其相关插件和组件,同时会创建 Vue 实例,挂载到 HTML 页面上指定的 DOM 元素中。

  • vite.config.js:Vite 的配置文件,可以通过该文件配置项目的参数、插件、打包优化等。该文件可以使用 CommonJS 或 ES6 模块的语法进行配置。

  • package.json:标准的 Node.js 项目配置文件,包含了项目的基本信息和依赖关系。

项目组件

传统的页面由html、css和js三个文件组成(多文件组件),vue将这文件合并成一个.vue文件(Single-File Component,简称SFC,单文件组件)。

  • <template>:代表组件的html部分代码,代替传统的.html文件;
  • <script>:代表组件的js代码,代替传统的.js文件;
  • <style>:代表组件的css样式代码,代替传统的.css文件。

样式的导入方式

只有当前vue文件使用的样式直接写在style标签中。

  • 全局引入main.js

    import './style/reset.css'

  • vue文件script代码引入

    import './style/reset.css'

  • vue文件style代码引入

    @import './style/reset.css'

响应式和setup函数

VUE3中数据要经过ref或者reactive处理后才是响应式的。ref是VUE3框架提供的一个函数,需要导入。ref处理的响应式数据:

  • 在js编码修改的时候需要.value
  • 在绑定到html上时不需要.value

setup:原始的写法,需要export defaultreturn

<script type="module">
    import {ref} from 'vue'
    export default {
        setup(){
            let counter = ref(1)
            function increase(){
                counter.value++
            }
            function decrease(){
                counter.value--
            }
            return {
                counter,
                increase,
                decrease
            }
        }
    }
</script>

<script>中添加setup可省略上述内容。

<script type="module" setup>
    import {ref} from 'vue'
    
    let counter = ref(1)
    function increase(){
        counter.value++
    }
    function decrease(){
        counter.value--
    }
</script>

<template>
    <div>
      <button @click="decrease()">-</button>
      {{ counter }}
      <button @click="increase()">+</button>
    </div>
</template>

VUE3视图渲染

模版语法

文本渲染

插值表达式

插值表达式能实现文本插值,使用的是“Mustache”语法,即双大括号{{}}

<script setup type="module">
    let msg ="hello vue3"
    let getMsg = ()=>{
        return 'hello vue3 message'
    }
    let age = 19
    let bee = '蜜 蜂'
    // 购物车
    const carts = [{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}];
    //计算购物车总金额
    function compute(){
        let count = 0;
        for(let index in carts){
            count += carts[index].price*carts[index].number;
        }
        return count;
    }
</script>

<template>
<div>
    <!-- 绑定数据 -->
    <h1>{{ msg }}</h1>
    msg的值为: {{ msg }} <br>
    <!-- 调用函数 -->
    getMsg返回的值为:{{ getMsg() }}  <br>
    购物车总金额: {{ compute() }} <br/>
    <!-- 使用运算符 -->
    是否成年: {{ age>=18?'true':'false' }} <br>
    <!-- 支持对象的API -->
    反转: {{ bee.split(' ').reverse().join('-') }} <br>
</div>
</template>
v-text和v-html
  • v-text将数据渲染成双标签中间的文本,但是不识别html元素结构的文本;
  • v-html将数据渲染成双标签中间的文本,识别html元素结构的文本。
<template>
<div>
    <span v-text='msg'></span> <br>
    <span v-text='redMsg'></span> <br>
    <span v-text='getMsg()'></span> <br>
    <span v-text='age>18?"成年":"未成年"'></span> <br>
    <span v-text='bee.split(" ").reverse().join("-")'></span> <br>
    <span v-html="`<font color='green'>${msg}</font>`"></span> <br>
</div>
</template>

属性渲染

v-bind可以用于渲染任何元素的属性,语法为 v-bind:属性名='数据名', 可以简写为 :属性名='数据名'

<script setup type="module">
    const data = {
        name:'尚硅谷',
        url:"http://www.atguigu.com",
      	logo:"http://xxx/logo.png"
    }
</script>

<template>
<div>
    <a v-bind:href='data.url'>
        <img :src="data.logo" :title="data.name">
        <input type="button" :value="`点击访问${data.name}`">
    </a>
</div>
</template>

事件绑定

  • 使用v-on:click="handler" ,简写为 @click="handler"

  • vue中的事件名=原生事件名去掉on 前缀,如:onClick --> click

  • handler的值可以是方法事件处理器,也可以是内联事件处理器(直接写处理事件的代码);

  • 绑定事件时,可以使用修饰符,常见的事件修饰符如下:

    • .once:只触发一次事件;
    • .prevent:阻止默认事件。
<script setup type="module">
    import {ref} from 'vue'
    let count=ref(0)
    let addCount= ()=>{
        count.value++
    }
    let fun= (event)=>{
        let flag = confirm("确定访问吗")
        if (!flag) {
            // 通过事件对象阻止组件的默认行为
        	event.preventDefault();
        }
    }
</script>

<template>
<div>
    <h1>count的值是:{{ count }}</h1>
    <!-- 方法事件处理器 -->
    <button v-on:click="addCount()">addCount</button> <br>
    <!-- 内联事件处理器 -->
    <button @click="count++">incrCount</button> <br>
    <!-- 事件修饰符 once 只绑定事件一次 -->
    <button @click.once="count++">addOnce</button> <br>
    <!-- 事件修饰符 prevent 阻止组件的默认行为 -->
    <a href="http://www.atguigu.com" target="_blank" @click.prevent="count++">prevent</a> <br>
    <!-- 原生js方式阻止组件默认行为 (推荐) -->
    <a href="http://www.atguigu.com" target="_blank" @click="fun($event)">prevent</a> <br>
</div>
</template>

响应式基础

  • ref:更适合单个变量
    • <script>中操作需要.value
    • <template>中无需.value
  • reactive:更适合对象,都无需.value
  • toRef:将reactive响应式数据的某个属性转化为ref响应式数据;
  • toRefs:将reactive响应式数据的多个属性转化为ref响应式数据;
<script type="module" setup>
    import {ref,reactive,toRef,toRefs} from 'vue'
    let counter = ref(0);
    let data = reactive({
        name: "test",
        counter:0
    })
    function show(){
        alert(counter.value);
    }
    let incr = () =>{
        counter.value++;
    }
    let incrData = () =>{
        data.counter++;
    }
    
    let ct =toRef(data,'counter');
    let {counter,name} = toRefs(data);
</script>

<template>
<div>
    <button @click="counter++">+</button>
    <button @click="incr()">+</button> 

    <button @click="data.counter++">+</button>
    <button @click="incrData()">+</button> 
</div>
</template>

条件渲染

  • v-if='表达式'只会在指令的表达式返回真值时才被渲染,数据为false时,元素不在DOM树中;
  • 使用 v-elsev-if 添加一个“else 区块”,一个 v-else 元素必须跟在一个 v-if 元素后面,否则它将不会被识别。
  • v-show类似v-if。数据为false时,元素仍在DOM树中,只是将CSS属性display改为none;不能和 v-else 搭配使用。
<template>
<div>
    <h1 v-if="awesome">Vue is awesome!</h1>
    <h1 v-else>Oh no 😢</h1>
    <h1 id="ha" v-show="awesome">Vue is awesome!</h1>
</div>
</template>

总结v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

列表渲染

v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

<script type="module" setup>
    import {ref,reactive} from 'vue'
    let parentMessage= ref('产品')
    let items =reactive([
        {
            id:'item1',
            message:"薯片"
        },
        {
            id:'item2',
            message:"可乐"
        }
    ])
</script>

<template>
<div>
    <ul>
        <!-- :key不写也可以 -->
        <li v-for='item in items' :key='item.id'>
            {{ item.message }}
    </li>
    </ul>

    <ul>
        <!-- index表示索引,当然不是非得使用index这个单词 -->
        <li v-for="(item, index) in items" :key="index">
            {{ parentMessage }} - {{ index }} - {{ item.message }}
    </li>
    </ul>
</div>
</template>

双向绑定

  • 响应式数据的变化会更新dom树,dom树上用户的操作造成的数据改变也会同步更新到响应式数据;
  • 一般用于表单标签;
  • 语法:v-model=''
<script type="module" setup>
    import { reactive,ref } from 'vue' 
    let hbs = ref([]); //爱好
    let user = reactive({
        username:null,
        password:null,
        introduce:null,
        pro:null
    })   
    function clearx(){
        //user = {};// 这种写法会将数据变成非响应的
        user.username=''
        user.password=''
        user.introduce=''
        user.pro=''
        hbs.value.splice(0,hbs.value.length);
    }
</script>

<template>
<div>
    账号: <input type="text" v-model="user.username"> <br>
    密码: <input type="text" v-model="user.password"> <br>
    爱好: 
    吃 <input type="checkbox" name="hbs" v-model="hbs" value="吃"><input type="checkbox" name="hbs" v-model="hbs" value="喝"><input type="checkbox" name="hbs" v-model="hbs" value="玩">
    <br>
    简介:<textarea v-model="user.introduce"></textarea>
    <br>
    籍贯:
    <select v-model="user.pro">
        <option value="1"></option>
        <option value="2"></option>
        <option value="3"></option>
    </select> 
    <br>
    <button @click="clearx()">重置</button>
    <hr>
    显示爱好:{{ hbs }}
    <hr>
    显示用户信息:{{ user }}
</div> 
</template>

计算属性

  • 计算属性:直接当成属性调用(不加括号)。如果数据没有变化,则使用之前的数据。
  • 普通函数:调用有括号。每使用一次就执行一次。
<script type="module" setup>
    import { reactive,computed } from 'vue'
    const author = reactive({
        name: 'John Doe',
        books: [
            'Vue 2 - Advanced Guide',
            'Vue 3 - Basic Guide',
            'Vue 4 - The Mystery'
        ]
    })
    // 计算属性
    const publishedBooksMessage = computed(() => {
        console.log("publishedBooksMessage")
        return author.books.length > 0 ? 'Yes' : 'No'
    })
    // 函数
    let hasBooks = ()=>{
        console.log("hasBooks")
        return author.books.length > 0?'Yes':'no'
    }
</script>

<template>
<div>
    <!-- 调用函数,每个标签都会调用一次,所以console.log两次 -->
    <span>{{ hasBooks() }}</span>
    <span>{{ hasBooks() }}</span>
    <!-- 属性计算,属性值不变时,多个标签只会调用一次,所以console.log一次 -->
    <span>{{ publishedBooksMessage }}</span>
    <span>{{ publishedBooksMessage }}</span>
</div>
</template>

数据监听器

  • watch:过于复杂。
  • watchEffect:监听回调函数里出现的响应式数据。
<script type="module" setup>
    import { ref,reactive,watchEffect } from 'vue'

    let firstname=ref('')
    let lastname=reactive({name:''})
    let fullname=ref('')

    watchEffect(() => {
        fullname.value=`${firstname.value}${lastname.name}`
    })
</script>

<template>
<div>
    全名:{{fullname}} <br>
    姓氏:<input type="text" v-model="firstname"> <br>
    名字:<input type="text" v-model="lastname.name" > <br>
    </div>
</template>

VUE生命周期

import {ref,onUpdated,onMounted,onBeforeUpdate} from 'vue'
let message =ref('hello')

// 挂载完毕生命周期
onMounted(()=>{
    console.log('-----------onMounted---------')
    let span1 =document.getElementById("span1")
    console.log(span1.innerText)
})
// 更新前生命周期
onBeforeUpdate(()=>{
    console.log('-----------onBeforeUpdate---------')
    let span1 =document.getElementById("span1")
    console.log(span1.innerText)
})
// 更新完成生命周期
onUpdated(()=>{
    console.log('-----------onUpdated---------')
    let span1 =document.getElementById("span1")
    console.log(span1.innerText)
})

VUE组件

拼接页面

为实现上图效果,在components/下创建Header.vueNavigator.vueContent.vue并编写相应代码,在App.vue中编写如下代码。

<script setup>
    import Header  from './components/Header.vue'
    import Navigator  from './components/Navigator.vue'
    import Content  from './components/Content.vue'
</script>

<template>
<div>
    <Header class="header"></Header>
    <Navigator class="navigator"></Navigator>
    <Content class="content"></Content>
</div>
</template>

<style scoped>
    .header{
        height: 80px;
        border: 1px solid red;
    }
    .navigator{
        width: 15%;
        height: 800px;
        display: inline-block;
        border: 1px blue solid;
        float: left;
    }
    .content{
        width: 83%;
        height: 800px;
        display: inline-block;
        border: 1px goldenrod solid;
        float: right;
    }
</style>

传递参数

分为:父传子、子传父、兄弟传参(子传父+父传子)

以兄弟传参为例:

  • Navigator.vue代码:
<script setup type="module">
    import {defineEmits} from 'vue'
    const emits = defineEmits(['sendMenu']);
    //触发事件,向父容器发送数据
    function send(data){
        emits('sendMenu',data);
    }
</script>

<template>
<div>
    <ul>
        <li @click="send('学员管理')">学员管理</li>
        <li @click="send('图书管理')">图书管理</li>
        <li @click="send('请假管理')">请假管理</li>
        <li @click="send('考试管理')">考试管理</li>
        <li @click="send('讲师管理')">讲师管理</li>
    </ul>
</div>
</template>
  • App.vue代码:
<script setup>
    import Header  from './components/Header.vue'
    import Navigator  from './components/Navigator.vue'
    import Content  from './components/Content.vue'

    import {ref} from "vue"
    //定义接受navigator传递参数
    var navigator_menu = ref('ceshi');

    const receiver = (data) =>{
        navigator_menu.value = data;
    }
</script>

<template>
<div>
    <hr>
    {{ navigator_menu }}
    <hr>
    <Header class="header"></Header>
    <Navigator @sendMenu="receiver" class="navigator"></Navigator>
    <!-- 向子组件传递数据-->
    <Content class="content" :message="navigator_menu"></Content>
</div>
</template>
  • Content.vue代码:
<script setup type="module">
    import {defineProps} from "vue"
    
    defineProps({
        message:String
    })
</script>

<template>
<div>
    展示的主要内容!
    <hr>
    {{ message }}
</div>
</template>

VUE3路由Router

npm install vue-router@4 //安装vue-router 4版本

路由入门

  • src/routers/router.js
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件
import Home from '../components/Home.vue'
import List from '../components/List.vue'
import Add from '../components/Add.vue'
import Update from '../components/Update.vue'

// 创建路由对象,声明路由规则
const router = createRouter({
    history: createWebHashHistory(),	// 创建路由的历史记录对象
    routes:[
        {
            path:'/',
            components:{
                default:Home,	// 对应默认的<router-view>
                homeView:Home	// 对应name为homeView的<router-view>
            }
            // redirect: "/list" 	// 重定向
        },
        {
            path:'/list',
            components:List
        },
        {
            path:'/add',
            components:Add
        },
        {
            path:'/update',
            components:Update
        }
    ]
})

// 对外暴露路由对象
export default router;
  • main.js
import { createApp } from 'vue'
import App from './App.vue'
// 导入router模块
import router from './routers/router.js'
let app = createApp(App)
// 绑定路由对象
app.use(router)
// 挂载
app.mount("#app")
  • App.vue<router-view>会被替换为组件。
<template>
    <div>
        <!-- 路由的连接 -->
        <router-link to="/">home页</router-link> <br>
        <router-link to="/list">list页</router-link> <br>
        <router-link to="/add">add页</router-link> <br>
        <router-link to="/update">update页</router-link> <br>
        <!-- 路由连接对应视图的展示位置 -->
        <!-- 默认展示位置(一般用这个就够了) -->
        <router-view></router-view>
        <!-- 单独展示某个组件,在router.js中对应 -->
        <router-view name="homeView"></router-view>
    </div>
</template>

编程式路由

上面的<router-link>是写死的,下面通过代码来跳转。

App.vue:用的是useRouter,和下一节区分。

<script setup type="module">
    import {useRouter} from 'vue-router'
    //创建动态路由对象
    let router = useRouter()
    let showList = () => {
        // 方式一:直接push一个路径
        router.push('/list')
        // 方式二:push一个带有path属性的对象
        router.push({path:'/list'})
    }
</script>

<template>
    <div>
        <h1>App页面</h1>
        <!-- 通过编程实现路由切换 -->
        <button @click="showList()">showList</button> <br>
        <hr/>
        <!-- 路由连接对应视图的展示位置 -->
        <router-view></router-view>
    </div>
</template>

路由传参

  • 路径参数:如/showDetail/1/java,中的1java就是参数;
  • 键值对参数:即/showDetail?id=1&language=java

router.js

const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        {
            // 路径参数写法
            path:'/showDetail/:id/:language',
            components:ShowDetail
        },
        {
            // 键值对参数写法(和之前一样)
            path:'/showDetail2',
            components:ShowDetail2
        },
    ]
})

App.vue:传参

<script setup type="module">
    import {useRouter} from 'vue-router'
    //创建动态路由对象
    let router = useRouter()

    // 路径参数
    let showDetail= (id,language)=>{
        // 方式一:使用拼接字符串方式传递
        router.push(`showDetail/${id}/${languange}`)
        // 方式二:传对象,使用params 
        router.push({name:"showDetail",params:{id:id,language:language}})
    }

    // 键值对参数
    let showDetail2= (id,language)=>{
        // 方式一:使用拼接字符串方式传递
        router.push(`/showDetail?id=${id}&language=${language}`)
        // 方式二:传对象,使用query
        router.push({path:"/showDetail2",query:{id:id,language:language}})
    }
</script>

<template>
    <div>
        <!-- 路径参数 -->
        <router-link to="/showDetail/1/JAVA">普通路由</router-link> 
        <button @click="showDetail(1,'JAVA')">编程路由</button>
        <!-- 键值对参数 -->
        <router-link to="/showDetail?id=1&language=JAVA">普通路由1</router-link> 
        <router-link v-bind:to="{path:'/showDetail2',query:{id:1,language:'Java'}}">普通路由2,同上</router-link> 
        <button @click="showDetail2(1,'JAVA')">编程式路由</button>
        <hr>
        <router-view></router-view>
    </div>
</template>

ShowDetail.vue:接收参数。注意是useRoute

<script setup type="module">
    import{useRoute} from 'vue-router'
    import { onUpdated,ref } from 'vue';
    // 获取当前的route对象
    let route =useRoute()
    let languageId = ref(0)
    let languageName = ref('')
    //  借助更新时生命周期,将数据更新进入响应式对象
    onUpdated (()=>{
        // 路径参数用params
        languageId.value=route.params.id
        languageName.value=route.params.language
        
        // 键值对参数用query
        languageId.value=route.query.id
        languageName.value=route.query.language
    })
</script>

路由守卫

  • 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。
  • 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。

router.js

//全局前置路由守卫
router.beforeEach((to,from,next) => {
    // to 是目标地包装对象  .path属性可以获取地址
    // from 是来源地包装对象 .path属性可以获取地址
    // next是方法,不调用默认拦截! next() 放行,直接到达目标组件
    // next('/地址')重定向到其他地址,客户端重定向时会再次经过前置路由守卫,注意避免无限重定向
    console.log(to.path,from.path,next)
    
    //需要判断,注意避免无限重定向
    if(to.path == '/index'){
        next()
    }else{
        next('/index')
    }
} )

//全局后置路由守卫
router.afterEach((to, from) => {
    console.log(`Navigate from ${from.path} to ${to.path}`);
});

VUE3数据交互Axios

Promise

  • Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

基本用法

  • resolvereject是两个函数,如果调用resolve,状态会由pending转换为resolved;如果调用reject,状态会由pending转换为reject。可以写参数,thencatch里的回调函数可以接收到这些参数。
  • then中的函数:状态转换为resolved时会执行;
  • catch中的函数:状态转换为reject时或异常时会执行。
let   =new Promise(function(resolve,reject){
    console.log("promise do some code ... ...")
    resolve("promise success")
    // reject("promise fail")
    // throw new Error("error message")
})

promise.then(
    function(resolveValue){
        console.log(`promise中执行了resolve:${resolveValue}`)
    }
).catch(
    function(error){
        console.log(error)
    }
)

async和await

  • async标识函数后,async函数的返回值会变成一个promise对象;

  • 如果返回的是一个非promise对象(如整数、字符串等),async函数的结果会返回一个成功状态的promise对象;

  • 如果返回的是一个promise对象,则async函数返回的状态与结果由该对象决定(该promise对象可以resolve或reject);

  • 如果函数抛出的是一个异常,则async函数返回的是一个失败的promise对象。

async function fun1(){
    // return 10	// 返回成功的promise对象
    // throw new Error("something wrong")	// 返回失败的promise对象
    let promise = Promise.reject("oh on")	// 返回失败的promise对象
    let promise = Promise.resolve("heihei")	// 返回成功的promise对象
    return promise
}
let promise = fun1()

promise.then(
    function(value){
        console.log("success:"+value)
    }
).catch(
    function(value){
        console.log("fail:"+value)
    }
)
  • await是帮助获取promise成功状态返回值的关键字;
  • await右侧如果是普通值则直接返回;如果是promise对象则返回promise成功状态的结果;如果是失败状态的promise则抛异常;
  • await必须在async函数中,但是async函数中可以没有await;
  • await后边的代码会等待await执行完毕后再继续执行。
async function fun1(){
    return 10
}
async function fun2(){
    try{
        let res = await fun1();
        // let res = await Promise.reject("something wrong")
    }catch(e){
        console.log("catch got:"+e);
    }
    console.log("await got:"+res);
}
fun2()

Axios入门

npm install axios

axios返回一个promise对象。

<script setup type="module">
    import axios from 'axios'
    import { reactive } from 'vue';

    let jsonData =reactive({
        code:1,
        content:"xxx"
    })

    function getLoveMessage() {
        axios({
            // 请求方式
            method:"post",
            // 请求的url
            url:"https://api.uomg.com/api/rand.qinghua?format=json",
            // 请求方式为post时,data下的数据会以JSON串放入请求体
            data:{ 
                username:"123456"
            },
            // params中的数据会以key=value形式放在url后
            params: {
                format: 'json'
            }
        }).then(	// 响应成功时要执行的函数
            function (response){
                console.log(response);
                Object.assign(jsonData,response.data);
            }
        ).catch(	// 响应失败时要执行的函数
            function (error){
                console.log(error);
            }
        )
    }
</script>

<template>
    <div>
        <h1>今日土味情话:{{jsonData.content}}</h1>
        <button  @click="getLoveMessage()">获取今日土味情话</button>
    </div>
</template>

响应的数据:

{
    // `data` 由服务器提供的响应
    data: {},
    // `status` 来自服务器响应的 HTTP 状态码
    status: 200,
    // `statusText` 来自服务器响应的 HTTP 状态信息
    statusText: 'OK',
    // `headers` 是服务器响应头
    // 所有的 header 名称都是小写,而且可以使用方括号语法访问
    // 例如: `response.headers['content-type']`
    headers: {},
    // `config` 是 `axios` 请求的配置信息
    config: {},
    // `request` 是生成此响应的请求,在浏览器中是 XMLHttpRequest 实例
    request: {}
}

get和post方法

axios.get(url[, config])

function getLoveWords() {
    return axios.get(
        'https://api.uomg.com/api/rand.qinghua',
        {
            params:{	// 向url后添加的键值对参数
                format:'json',
                username:'zhangsan'
            },
            headers:{	// 设置请求头
                'Accept' : 'application/json'
            }
        }
    )
}

async function getLoveMessage() {
    let {data} = await getLoveWords()
    Object.assign(message,data)
}

axios.post(url[, data[, config]])

return axios.post(
    'https://api.uomg.com/api/rand.qinghua',
    {	//请求体中的JSON数据
        username:'zhangsan',
        password:'123456'
    },
    {	// 其他参数
        params:{	// url上拼接的键值对参数
            format:'json',
        },
        headers:{	// 请求头
            'Accept' : 'application/json'
        }
    }
)

拦截器

src/axios.js中配置拦截器。

import axios from 'axios'

//  创建instance实例,发送请求都用这个实例
const instance = axios.create({
    baseURL:'https://api.uomg.com',
    timeout:10000
})

//  添加请求拦截器,参数为两个函数
instance.interceptors.request.use(
    // 函数1:发送请求之前拦截(比如可以处理请求头)
    config => {
        console.log("before request")
        config.headers.Accept = 'application/json'
        return config	// 一定要return
    },
    // 函数2:请求错误都处理函数
    error => {
        console.log("request error")
        return Promise.reject(error)	// 一定要返回错误
    }
)

// 添加响应拦截器,参数为两个函数
instance.interceptors.response.use(
    // 函数1:设置响应正确时(返回200)的处理函数
    response => {
        console.log("after success response")
        console.log(response)
        return response		// 记得return
    },
    // 函数2:设置响应异常时的处理函数(如404)
    error => {
        console.log("after fail response")
        console.log(error)
        return Promise.reject(error)	// 返回错误
    }
)
// 默认导出
export default instance

其他js中使用上面定义的instance:

import axios from './axios.js'

function getLoveWords() {
    return axios.get('https://api.uomg.com/api/rand.qinghua')
}

VUE3状态管理Pinia

npm install pinia
  • src/store/store.js:定义共享数据。
import { defineStore } from 'pinia'

export const definedPerson = defineStore(
    {
        // 数据id,必须全局唯一
        id: 'personPinia',
        // 状态,其实就是响应式数据,用函数返回
        state:()=>{ 
            return {
                username:'张三',
                age:0,
                hobbies:['唱歌','跳舞']
            }
        },
        // 定义一些通过计算而得到结果的一些方法,不修改数据
        getters:{
            // getters中的方法可以当做属性值方式使用!
            getHobbiesCount(){
                return this.hobbies.length
            },
            // 推荐用箭头函数
            getAge: (state) => {
                return state.age
            }
        },
        // 定义一些对数据修改的方法,可以是异步的
        actions:{ 
            doubleAge(){
                this.age=this.age*2
            }
        }
    }
)

id也可以放前面

export const useStore = defineStore('main', {
    state: () => ({
        count: 0,
    }),
    getters: {
        doubleCount: (state) => state.count * 2,
    },
})
  • main.js:app中开启pinia功能。
import { createApp } from 'vue'
import App from './App.vue'
import router from './routers/router.js'
// 导入pinia
import { createPinia } from 'pinia'

let pinia = createPinia()	// 创建pinia对象
let app =createApp(App)
app.use(router)
app.use(pinia)		// app中使用pinia功能
app.mount('#app')
  • conponents/Operate.vue:操作pinia数据。
<script setup type="module">
    import { definedPerson} from '../store/store';
    // 读取存储的数据
    let person= definedPerson()
</script>

<template>
    <div>
        <h1>operate视图,用户操作Pinia中的数据</h1>
        请输入姓名:<input type="text" v-model="person.username"> <br>
        请输入年龄:<input type="text" v-model="person.age"> <br>
        请增加爱好:
        <input type="checkbox" value="吃饭"  v-model="person.hobbies"> 吃饭
        <input type="checkbox" value="睡觉"  v-model="person.hobbies"> 睡觉
        <input type="checkbox" value="打豆豆"  v-model="person.hobbies"> 打豆豆 <br>

        <!-- 事件中调用person的doubleAge()方法 -->
        <button @click="person.doubleAge()">年龄加倍</button> <br>
        <!-- 事件中调用pinia提供的$reset()方法恢复数据的默认值 -->
        <button @click="person.$reset()">恢复默认值</button> <br>
        <!-- 事件中调用$patch方法一次性修改多个属性值 -->
        <button @click="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})">变身奥特曼</button> <br>
        显示pinia中的person数据:{{person}}
    </div>
</template>
  • conponents/List.vue:展示pinia数据。
<script setup type="module">
    import { definedPerson} from '../store/store';
    // 读取存储的数据
    let person= definedPerson()
</script>

<template>
    <div>
        <h1>List页面,展示Pinia中的数据</h1>
        读取姓名:{{person.username}} <br>
        读取年龄:{{person.age}} <br>
        通过get年龄:{{person.getAge}} <br>
        爱好数量:{{person.getHobbiesCount}} <br>
    </div>
</template>

VUE3
https://shuusui.site/blog/2024/09/22/vue3/
作者
Shuusui
发布于
2024年9月22日
更新于
2024年9月25日
许可协议