vue3-02

属性绑定
<script setup lang="ts">
import { ref } from 'vue'
const path = ref('/src/assets/vue.svg')

</script>

<template>
  <img :src="path" alt="">
</template>

  • 【:属性名】用来将标签属性与【响应式】变量绑定

  • <script setup lang="ts">
    import { ref } from 'vue'
    const count = ref(0)
    function dec() {
      count.value--
    }
    function inc() {
      count.value++
    }
    </script>
    
    <template>
      <input type="button" value="-" @click="dec">
      <h2>{{count}}</h2>
      <input type="button" value="+" @click="inc">

  • 【@事件名】用来将标签属性与函数绑定,事件发生后执行函数内代码

  • 表单绑定
    <script setup lang="ts">
    import { ref } from "vue";
    const user = ref({
      name:'张三',
      age:18,
      sex:'男',
      fav:['游泳','打球']
    })
    
    function saveUser() {
      console.log(user.value)
    }
    </script>
    
    <template>
      <div class="outer">
        <div>
          <label for="">请输入姓名</label>
          <input type="text" v-model="user.name"/>
        </div>
        <div>
          <label for="">请输入年龄</label>
          <input type="text" v-model="user.age"/>
        </div>
        <div>
          <label for="">请选择性别</label>
          男 <input type="radio" value="男" v-model="user.sex"/> 
          女 <input type="radio" value="女" v-model="user.sex"/>
        </div>
        <div>
          <label for="">请选择爱好</label>
          游泳 <input type="checkbox" value="游泳" v-model="user.fav"/> 
          打球 <input type="checkbox" value="打球" v-model="user.fav"/> 
          健身 <input type="checkbox" value="健身" v-model="user.fav"/>
        </div>
        <div>
          <input type="button" value="保存" @click="saveUser">
        </div>
      </div>
    </template>
    
    <style scoped>
      div {
        margin-bottom: 8px;
      }
      .outer {
        width: 100%;
        position: relative;
        padding-left: 80px;
      }
      label {
        text-align: left;
        width: 100px;
        display: inline-block;
        position: absolute;
        left :0;
      }
    </style>

    页面效果

  • 用 v-model 实现双向绑定,即

    • JavaScript 数据可以同步到表单标签

    • 反过来用户在表单标签输入的新值也会同步到 JavaScript 这边

  • 双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了

  • 复选框这种标签,双向绑定的 JavaScript 数据类型一般用数组

  • 计算属性
  • 有时在数据展示时要做简单的计算
  • <script setup lang="ts">
    import { ref } from 'vue'
    const firstName = ref('三')
    const lastName = ref('张')
    
    </script>
    
    <template>
      <h2>{{lastName + firstName}}</h2>
      <h3>{{lastName + firstName}}</h3>
      <h4>{{lastName + firstName}}</h4>
    </template>

    看起来较为繁琐,可以用计算属性改进

  • <script setup lang="ts">
    import { ref, computed } from 'vue'
    const firstName = ref('三')
    const lastName = ref('张')
    const fullName = computed(() => {
      console.log('enter')
      return lastName.value + firstName.value
    })
    </script>
    
    <template>
      <h2>{{fullName}}</h2>
      <h3>{{fullName}}</h3>
      <h4>{{fullName}}</h4>
    </template>

  • fullName 即为计算属性,它具备缓存功能,即 firstName 和 lastName 的值发生了变化,才会重新计算

  • 如果用函数实现相同功能,则没有缓存功能

    <script setup lang="ts">
    import { ref } from 'vue'
    const firstName = ref('三')
    const lastName = ref('张')
    function fullName() {
      console.log('enter')
      return lastName.value + firstName.value
    }
    </script>
    
    <template>
      <h2>{{fullName()}}</h2>
      <h3>{{fullName()}}</h3>
      <h4>{{fullName()}}</h4>
    </template>
    xhr

    浏览器中有两套 API 可以和后端交互,发送请求、接收响应,fetch API 前面我们已经介绍过了,另一套 API 是 xhr,基本用法如下

    const xhr = new XMLHttpRequest()
    xhr.onload = function() {
        console.log(xhr.response)
    }
    xhr.open('GET', 'http://localhost:8080/api/students')
    xhr.responseType = "json"
    xhr.send()

    但这套 API 虽然功能强大,但比较老,不直接支持 Promise,因此有必要对其进行改造

  • function get(url: string) {
      return new Promise((resolve, reject)=>{
        const xhr = new XMLHttpRequest()
        xhr.onload = function() {
          if(xhr.status === 200){
            resolve(xhr.response)
          } else if(xhr.status === 404) {
            reject(xhr.response)
          } // 其它情况也需考虑,这里简化处理
        }
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.send()
      })
    }

  • Promise 对象适合用来封装异步操作,并可以配合 await 一齐使用

  • Promise 在构造时,需要一个箭头函数,箭头函数有两个参数 resolve 和 reject

    • resolve 是异步操作成功时被调用,把成功的结果传递给它,最后会作为 await 的结果返回

    • reject 在异步操作失败时被调用,把失败的结果传递给它,最后在 catch 块被捉住

  • await 会一直等到 Promise 内调用了 resolve 或 reject 才会继续向下运行

  • 调用示例1:同步接收结果,不走代理

  • try {
      const resp = await get("http://localhost:8080/api/students")
      console.log(resp)
    } catch (e) {
      console.error(e)
    }

    调用示例2:走代理

  • try {
      const resp = await get('/api/students')
      console.log(resp)  
    } catch(e) {
      console.log(e)
    }

  • 走代理明显慢不少

axios
基本用法

axios 就是对 xhr API 的封装,手法与前面例子类似

安装

npm install axios

一个简单的例子

<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";

let count = ref(0);

async function getStudents() {
  try {
    const resp = await axios.get("/api/students");
    count.value = resp.data.data.length;
  } catch (e) {
    console.log(e);
  }
}

onMounted(() => {
  getStudents()
})
</script>

<template>
  <h2>学生人数为:{{ count }}</h2>
</template>

onMounted 指 vue 组件生成的 HTML 代码片段,挂载完毕后被执行

再来看一个 post 例子

<script setup lang="ts">
import { ref } from "vue";
import axios from "axios";

const student = ref({
  name: '',
  sex: '男',
  age: 18
})

async function addStudent() {
  console.log(student.value)
  const resp = await axios.post('/api/students', student.value)
  console.log(resp.data.data)
}
</script>

<template>
  <div>
    <div>
      <input type="text" placeholder="请输入姓名" v-model="student.name"/>
    </div>
    <div>
      <label for="">请选择性别</label>
      男 <input type="radio" value="男" v-model="student.sex"/> 
      女 <input type="radio" value="女" v-model="student.sex"/>
    </div>
    <div>
      <input type="number" placeholder="请输入年龄" v-model="student.age"/>
    </div>
    <div>
      <input type="button" value="添加" @click="addStudent"/>
    </div>
  </div>
</template>
<style scoped>
div {
  font-size: 14px;
}
</style>
环境变量
  • 开发环境下,联调的后端服务器地址是 http://localhost:8080

  • 上线改为生产环境后,后端服务器地址为 http://lalala.com

这就要求我们区分开发环境和生产环境,这件事交给构建工具 vite 来做

默认情况下,vite 支持上面两种环境,分别对应根目录下两个配置文件

  • .env.development - 开发环境

  • .env.production - 生产环境

针对以上需求,分别在两个文件中加入

//VITE_BACKEND_API_BASE_URL是自定义的名字,但前面的VITE_是不能变的
VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'

VITE_BACKEND_API_BASE_URL = 'http://lalala.com'

然后在代码中使用 vite 给我们提供的特殊对象 import.meta.env,就可以获取到 VITE_BACKEND_API_BASE_URL 在不同环境下的值

import.meta.env.VITE_BACKEND_API_BASE_URL

在main.ts文件中添加代码console.log(import.meta.env.VITE_BACKEND_API_BASE_URL)

在控制台运行开发环境npm run dev

在控制台运行生产环境1,先打包 npm run build2,然后在把项目部署到服务器

执行打包命令会在在项目的/dist/assets目录下生成一个以index开头的js文件,查找console.log,会找到生产环境下的BASE_URL的值

默认情况下,不能智能提示自定义的环境变量,做如下配置:新增文件 src/env.d.ts 并添加如下内容

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_BACKEND_API_BASE_URL: string
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

参考文档地址 环境变量和模式 | Vite 官方中文文档 (vitejs.dev)

baseURL

可以自己创建一个 axios 对象,方便添加默认设置,新建文件 /src/api/request.ts

// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})

export default _axios

然后在其它组件中引用这个 ts 文件,例如 /src/views/E8.vue,就不用自己拼接路径前缀了

<script setup lang="ts">
import axios from '../api/request'
// ...
await axios.post('/api/students', ...)  
</script>
拦截器
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})

// 请求拦截器
_axios.interceptors.request.use(
    //如果请求成功做的统一处理
  (config)=>{ // 统一添加请求头
    config.headers = {
      Authorization: 'aaa.bbb.ccc'
    }
    return config
  },
  (error)=>{ // 请求出错时的处理
    return Promise.reject(error)
  }
)

// 响应拦截器
_axios.interceptors.response.use(
  (response)=>{ // 状态码  2xx
    // 这里的code是自定义的状态码
    if(response.data.code === 200) {
      return response
    }   
    else if(response.data.code === 401) {   
      // 情况1
      return Promise.resolve({})
    }
    // ... 
  },
  (error)=>{ // 状态码 > 2xx, 400,401,403,404,500
    console.error(error) // 处理了异常
    if(error.response.status === 400) {
      // 情况1
    } else if(error.response.status === 401) {
      // 情况2
    } 
    // ...
    return Promise.resolve({})
  }
)

export default _axios

处理响应时,又分成两种情况

  1. 后端返回的是标准响应状态码,这时会走响应拦截器第二个箭头函数,用 error.response.status 做分支判断

  2. 后端返回的响应状态码总是200,用自定义错误码表示出错,这时会走响应拦截器第一个箭头函数,用 response.data.code 做分支判断

另外

  • Promise.reject(error) 类似于将异常继续向上抛出,异常由调用者(Vue组件)来配合 try ... catch 来处理

  • Promise.resolve({}) 表示错误已解决,返回一个空对象,调用者中接到这个空对象时,需要配合 ?. 来避免访问不存在的属性

条件与列表

首先,新增模型数据 src/model/Model8080.ts

export interface Student {
  id: number;
  name: string;
  sex: string;
  age: number;
}

// 如果 spring 错误,返回的对象格式
export interface SpringError {
  timestamp: string,
  status: number,
  error: string,
  message: string,
  path: string
}

// 如果 spring 成功,返回 list 情况
export interface SpringList<T> {
  data: T[],
  message?: string,
  code: number
}

// 如果 spring 成功,返回 page 情况
export interface SpringPage<T> {
  data: { list: T[], total: number },
  message?: string,
  code: number
}

// 如果 spring 成功,返回 string 情况
export interface SpringString {
  data: string,
  message?: string,
  code: number
}

import { AxiosResponse } from 'axios'
export interface AxiosRespError extends AxiosResponse<SpringError> { }
export interface AxiosRespList<T> extends AxiosResponse<SpringList<T>> { }
export interface AxiosRespPage<T> extends AxiosResponse<SpringPage<T>> { }
export interface AxiosRespString extends AxiosResponse<SpringString> { }

其中

  • AxiosRespPage 代表分页时的响应类型

  • AxiosRespList 代表返回集合时的响应类型

  • AxiosRespString 代表返回字符串时的响应类型

  • AxiosRespError 代表 Spring 出错时时的响应类型

  • <script lang="ts" setup>
    import { ref, onMounted } from "vue";
    import axios from "../api/request";
    import { Student, SpringList } from "../model/Model8080";
    
    // 说明 students 数组类型为 Student[]
    const students = ref<Student[]>([]);
    
    async function getStudents() {
      // 说明 resp.data 类型是 SpringList<Student>
      const resp = await axios.get<SpringList<Student>>("/api/students");  
      console.log(resp.data.data);
      students.value = resp.data.data;
    }
    
    onMounted(() => getStudents());
    </script>
    <template>
      <div class="outer">
        <div class="title">学生列表</div>
        <div class="thead">
          <div class="row bold">
            <div class="col">编号</div>
            <div class="col">姓名</div>
            <div class="col">性别</div>
            <div class="col">年龄</div>
          </div>
        </div>
        <div class="tbody">
          <div v-if="students.length === 0">暂无数据</div>
          <template v-else>
            <div class="row" v-for="s of students" :key="s.id">
              <div class="col">{{ s.id }}</div>
              <div class="col">{{ s.name }}</div>
              <div class="col">{{ s.sex }}</div>
              <div class="col">{{ s.age }}</div>
            </div>
          </template>
        </div>
      </div>
    </template>
    <style scoped>
    .outer {
      font-family: 华文行楷;
      font-size: 20px;
      width: 500px;
    }
    
    .title {
      margin-bottom: 10px;
      font-size: 30px;
      color: #333;
      text-align: center;
    }
    
    .row {
      background-color: #fff;
      display: flex;
      justify-content: center;
    }
    
    .col {
      border: 1px solid #f0f0f0;
      width: 15%;
      height: 35px;
      text-align: center;
      line-height: 35px;
    }
    
    .bold .col {
      background-color: #f1f1f1;
    }
    </style>
  • 加入泛型是为了更好的提示

  • v-if 与 v-else 不能和 v-for 处于同一标签

  • template 标签还有一个用途,就是用它少生成一层真正 HTML 代码

  • 可以看到将结果封装为响应式数据还是比较繁琐的,后面会使用 useRequest 改进

  • 监听器(参考代码E10.vue)

    利用监听器,可以在【响应式】的基础上添加一些副作用,把更多的东西变成【响应式的】

  • 原本只是数据变化 => 页面更新

  • watch 可以在数据变化时 => 其它更新

    <template>
      <input type="text" v-model="name" />
    </template>
    
    <script setup lang="ts">
    import { ref, watch } from "vue";
    function useStorage(name: string) {
      const data = ref(sessionStorage.getItem(name) ?? "");
      watch(data, (newValue) => {
        sessionStorage.setItem(name, newValue);
      });
      return data;
    }
    const name = useStorage("name");
    </script>
  • 名称为 useXXXX 的函数,作用是返回带扩展功能的【响应式】数据

  • localStorage 即使浏览器关闭,数据还在

  • sessionStorage 数据工作在浏览器活动期间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码的翠花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值