前端学习第九站——Vue3基础篇

news2024/11/29 0:33:44

目录

一、环境搭建

创建项目

 编码 IDE

修改端口

配置代理

项目架构 

二、Vue组件 

main.ts

属性绑定

事件绑定

表单绑定

计算属性

 xhr

axios

环境变量

baseURL

 拦截器

条件和列表 

监听器

 vueuse

 useRequest

usePagination(分页)

子组件


一、环境搭建

创建项目

采用 vite 作为前端项目的打包,构建工具

npm init vite@latest

 按照提示操作

cd 项目目录
npm install
npm run dev 

 编码 IDE

推荐采用微软的 VSCode 作为开发工具,到它的官网 Visual Studio Code - Code Editing. Redefined 下载安装即可。

要对 *.vue 做语法支持,还要安装一个 Volar 插件  

修改端口

 打开项目根目录下 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 7070
  }
})
  • 文档地址:配置 Vite {#configuring-vite} | Vite中文网 (vitejs.cn)

配置代理

为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理,同样是修改项目根目录下 vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 7070,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
})

项目架构 

  • index.html 为主页面

  • package.json npm 配置文件

  • tsconfig.json typescript 配置文件

  • vite.config.ts vite 配置文件

  • public 静态资源

  • src/components 可重用组件

  • src/model 模型定义

  • src/router 路由

  • src/store 共享存储

  • src/views 视图组件

二、Vue组件 

ue 的组件文件以 .vue 结尾,每个组件由三部分组成  

<script setup lang="ts"></script>

<template></template>

<style scoped></style>
  • script 代码部分,控制模板的数据来源和行为

  • template 模板部分,由它生成 html 代码

  • style 样式部分

根组件是 src/App.vue,先来个 Hello,world 例子

<script setup lang="ts">
import { ref } from "vue";
let msg = ref("hello"); // 把数据变成响应式的

function change() {
  msg.value = "world";
  console.log(msg);
}
</script>
<template>
  <h1>{{ msg }}</h1>
  <input type="button" value="修改msg" @click="change" />
</template>
  • {{msg}} 用来把一个变量绑定到页面上某个位置

  • 绑定的变量必须用 ref 函数来封装

    • ref 返回的是【响应式】数据,即数据一旦变化,页面展示也跟着变化

main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App)
  .mount('#app')
  • createApp 是创建一个 Vue 应用程序,它接收的参数 App 即之前我们看到的根组件

  • mount 就是把根组件生成的 html 代码片段【挂载】到 index.html 中 id 为 app 的 html 元素上

可以修改自己的组件文件,挂载到主页面。

新建 src/views/E0.vue,内容如下

<script setup lang="ts">
import { ref } from 'vue'
const msg = ref('Hello, World!!')
</script>
<template>
  <h1>{{ msg }}</h1>
</template>

修改 main.ts 将自己的组件文件挂载  

import { createApp } from 'vue'
import './style.css'
// import App from './App.vue'
import E0 from './views/E0.vue'

createApp(E0).mount('#app')

打开浏览器控制台,进入 Vue 的开发工具,尝试做如下修改

当把 msg 的值由 "Hello, World" 改为 "你好" 时,会发现页面展示同步发生了变化 ,这个就是响应式。

属性绑定

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

<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">
</template>

表单绑定

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

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

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

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

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

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

计算属性

 有时在数据展示时要做简单的计算

<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()

xhr.onload函数会在xhr接收到响应后才执行方法。 

但这套 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://itheima.com

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

默认情况下,vite 支持上面两种环境,需要我们分别在对应根目录下创建两个配置文件

  • .env.development - 开发环境

  • .env.production - 生产环境

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

VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'

VITE_BACKEND_API_BASE_URL = 'http://itheima.com'

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

import.meta.env.VITE_BACKEND_API_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 改进

监听器

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

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

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

下述代码就可以通过watch监听事件和sessionStorage 来实现响应式数据。

watch监听当数据发生变化是,存入sessionStorage,然后在通过sessionStorage获取新值

<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 数据工作在浏览器活动期间

 vueuse

 安装

npm install @vueuse/core

一些函数的用法

useMouse:鼠标移动、useCount:数字运算、useStorage: 存取数据

<template>
  <h3>X: {{x}}</h3>
  <h3>Y: {{y}}</h3>

  <h3>{{count}}</h3>
  <input type="button" @click="inc()" value="+">
  <input type="button" @click="dec()" value="-">

  <input type="text" v-model="name">
</template>
<script setup lang="ts">
import { useMouse, useCounter, useStorage } from '@vueuse/core'

const {x, y} = useMouse()

const {count, inc, dec} = useCounter()

const name = useStorage("name", "")
</script>

 useRequest

响应式的 axios 封装,官网地址 一个 Vue 请求库 | VueRequest (attojs.org)  

首先安装 vue-request  

npm install vue-request@next

 组件

<template>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
</template>
<script setup lang="ts">
import axios from "../api/request"
import { useRequest } from 'vue-request'
import { computed } from 'vue'
import { AxiosRespList, Student } from '../model/Model8080'

// data 代表就是 axios 的响应对象
const { data } = useRequest<AxiosRespList<Student>>(() => axios.get('/api/students'))

const students = computed(()=>{
  return data?.value?.data.data || []
})
</script>
<style scoped>
ul li {
  list-style: none;
  font-family: "华文行楷";
}

li span:nth-child(1) {
  font-size: 24px;
}
li span:nth-child(2) {
  font-size: 12px;
  color: crimson;
  vertical-align: bottom;
}
li span:nth-child(3) {
  font-size: 12px;
  color: darkblue;
  vertical-align: top;
}
</style>
  • data.value 的取值一开始是 undefined,随着响应返回变成 axios 的响应对象

  • 用 computed 进行适配

usePagination(分页)

 在 src/model/Model8080.ts 中补充类型说明

export interface StudentQueryDto {
  name?: string,
  sex?: string,
  age?: string, // 18,20
  page: number,
  size: number
}
  • js 中类似于 18,20 这样以逗号分隔字符串,会在 get 传参时转换为 java 中的整数数组

编写组件

<template>
  <input type="text" placeholder="请输入姓名" v-model="dto.name">
  <select v-model="dto.sex">
    <option value="" selected>请选择性别</option>
    <option value="男">男</option>
    <option value="女">女</option>
  </select>
  <input type="text" placeholder="请输入年龄范围" v-model="dto.age">
  <br>
  <input type="text" placeholder="请输入页码" v-model="dto.page">
  <input type="text" placeholder="请输入页大小" v-model="dto.size">
  <input type="button" value="搜索" @click="search">
  <hr>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
  <hr>
  总记录数{{total}} 总页数{{totalPage}}
</template>
<script setup lang="ts">
import axios from "../api/request"
import { usePagination } from 'vue-request'
import { computed, ref } from 'vue'
import { AxiosRespPage, Student, StudentQueryDto } from '../model/Model8080'

const dto = ref<StudentQueryDto>({name:'', sex:'', age:'', page:1, size:5})

// data 代表就是 axios 的响应对象
// 泛型参数1: 响应类型
// 泛型参数2: 请求类型
const { data, total, totalPage, run } = usePagination<AxiosRespPage<Student>, StudentQueryDto[]>(
  (d) => axios.get('/api/students/q', {params: d}), // 箭头函数
  {
    defaultParams: [ dto.value ], // 默认参数, 会作为参数传递给上面的箭头函数
    pagination: {
      currentKey: 'page', // 指明当前页属性
      pageSizeKey: 'size', // 指明页大小属性
      totalKey: 'data.data.total' // 指明总记录数属性
    } 
  } // 选项
)

const students = computed(()=>{
  return data?.value?.data.data.list || []
})

function search() {
  run(dto.value) // 会作为参数传递给usePagination的箭头函数
}
</script>
<style scoped>
ul li {
  list-style: none;
  font-family: "华文行楷";
}

li span:nth-child(1) {
  font-size: 24px;
}
li span:nth-child(2) {
  font-size: 12px;
  color: crimson;
  vertical-align: bottom;
}
li span:nth-child(3) {
  font-size: 12px;
  color: darkblue;
  vertical-align: top;
}
input,select {
  width: 100px;
}
</style>
usePagination 只需要定义一次,后续还想用它内部的 axios 发请求,只需调用 run 函数

子组件

 例1

定义子组件 Child1

<template>
  <div class="container">
    <div class="card">
      <div>
        <p class="name">{{name}}</p>
        <p class="location">{{country}}</p>
      </div>
      <img :src="avatar || '/src/assets/vue.svg'"/>
    </div>
  </div>
</template>
<script setup lang="ts">
// 定义属性,  编译宏
defineProps<{name:string,country:string,avatar?:string}>()
</script>
<style scoped>
.container {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
  flex-direction: row-reverse;
}
.name {
  font-weight: bold;
}
.location {
  font-size: 0.8em;
  color: #6d597a;
}
.card {
  display: flex;
  justify-content: space-evenly;
  padding: 1em;
  margin: 1rem;
  border-radius: 5px;
  background: #fff;
  width: 200px;
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}

.card:hover {
  transform: rotate(-5deg);
}

.card img {
  margin-left: 1em;
  border-radius: 50%;
  max-width: 55px;
  max-height: 55px;
}
</style>

 父组件引用

<template>
  <Child1 name="张三" country="中国" avatar="/src/assets/vue.svg"></Child1>
  <Child1 name="李四" country="印度" avatar="/vite.svg"></Child1>
  <Child1 name="王五" country="韩国" ></Child1>
</template>
<script lang="ts" setup>
import Child1 from '../components/Child1.vue';
</script>

例2

首先添加类型说明 model/ModelRandomUser.ts  

import { AxiosResponse } from "axios";
export interface AxiosRespResults extends AxiosResponse<Results>{}

export interface Results {
  info: {
    page: number,
    results: number
  },
  results: Result[]
}

export interface Result {
  gender: 'male' | 'female',
  name: {
    first: string,
    last: string
  },
  location: {
    country: string
  },
  picture: {
    medium: string
  },
  login: {
    username: string
  }
}

 子组件不变,父组件使用子组件

<!-- 父组件 -->
<template>
  <Child1 v-for="u of users" 
    :name="u.name.first" 
    :country="u.location.country" 
    :avatar="u.picture.medium"
    :key="u.login.username"></Child1>
</template>
<script setup lang="ts">
import axios from "axios";
import { useRequest } from "vue-request";
import { computed } from "vue";
import { AxiosRespResults } from '../model/ModelRandomUser'
import Child1 from "../components/Child1.vue";

const { data } = useRequest<AxiosRespResults>(
  ()=>axios.get('https://randomuser.me/api/?results=3')
)

const users = computed(()=>{
  return data.value?.data.results || []
})
</script>

 如果觉得 Result 数据结构嵌套太复杂,还可以做一个类型映射  

<!-- 父组件 -->
<template>
  <Child1 v-for="u of users" 
    :name="u.name" 
    :country="u.country" 
    :avatar="u.avatar"
    :key="u.username"></Child1>
</template>
<script setup lang="ts">
import axios from "axios";
import { useRequest } from "vue-request";
import { computed } from "vue";
import { AxiosRespResults, Result } from '../model/ModelRandomUser'
import Child1 from "../components/Child1.vue";

const { data } = useRequest<AxiosRespResults>(
  ()=>axios.get('https://randomuser.me/api/?results=3')
)

const users = computed(()=>{
  return data.value?.data.results.map(resultToUser) || []
})

interface User {
  name: string,
  country: string,
  avatar: string,
  username: string
}
function resultToUser(r:Result):User {
  return {
    name: r.name.first,
    country: r.location.country,
    avatar: r.picture.medium,
    username: r.login.username
  }
}
</script>
  • resultToUser 将 Result 类型映射为 User 类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/364027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你什么档次?敢和我用一样的即时通讯平台WorkPlus?

现今&#xff0c;很多企业越来越青睐私有化部署&#xff0c;尤其是在选择组织内部即时通讯平台的时候&#xff0c;更是会提出私有化部署的需求。究其原因&#xff0c;企业选择私有化部署即时通讯软件完全是出于安全方面考虑。因此&#xff0c;越来越多的企业将眼光望向了本地化…

深入讲解CFS组调度!(上)

注&#xff1a;本文缩写说明 一、CFS组调度简介 1.1. 存在的原因 总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢&#xff0c;比如多用户计算机系统每个用户的所有任务划分到一个分组中&#xff0c;A用户90个相同任务&#xff0c;而B…

NIO蔚来 面试——IP地址你了解多少?

目录 前言 1、IP地址 1.1、什么是IP地址 1.2、IP地址的格式 1.2.1、32位二进制数表示IP地址&#xff0c;够用吗&#xff1f; 1.3、IP地址的组成 1.4、为什么会出现IPv6 1.4.1、为什么IPv6还没有大量普及呢&#xff1f; 1.5、子网掩码 1.6、特殊的IP地址 2、路由选择 …

微信小程序 之 云开发

一、概念1. 传统开发模式2. 新开发模式 ( 云开发模式 )3. 传统、云开发的模式对比4. 传统、云开发的项目流程对比5. 云开发的定位1. 个人的项目或者想法&#xff0c;不想开发服务器&#xff0c;直接使用云开发2. 某些公司的小程序项目是使用云开发的&#xff0c;但是不多&#…

Python自动化测试之登录脚本

登录脚本环境准备1、安装selenium模块2、安装浏览器驱动器代码1、登录代码2、xpath定位元素标签环境准备 前提已经安装好python、pycharm&#xff0c;配置了对应的环境变量。 1、安装selenium模块 文件–>设置—>项目&#xff1a;script---->python解释器---->s…

Spring自动装配的底层逻辑

Spring是如何自动装配Bean的&#xff1f;看源码一些自己的理解&#xff0c;如有错漏&#xff0c;请指正 使用Spring之前我们要先去web.xml中设置一下Spring的配置文件&#xff0c;在Spring的配置文件中&#xff0c;是通过component-scan扫描器去扫描base-package底下所有的类装…

【基础算法】哈希表(拉链法)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

【C++的OpenCV】第四课-OpenCV图像常用操作(一):Mat对象深化学习、灰度、ROI

我们开始图像处理的基本操作的了解一、图像对象本身的加深学习1.1 Mat对象和ROI1.1.1 创建一个明确的Mat对象1.1.2 感兴趣的区域ROI二、图像的灰度处理2.1 概念2.2 cvtColor()函数2.3 示例一、图像对象本身的加深学习 1.1 Mat对象和ROI 这是一个技术经验的浅尝&#xff0c;所以…

什么是 CSAT?这份客户满意度流程指南请查收

什么是 CSAT&#xff1f;如何计算我的客户满意度分数&#xff1f;大中型公司应该熟悉这些术语。以下文章旨在教您有关客户满意度流程的所有内容 - 基本的CSAT概念、创建CSAT调查的好处、如何创建CSAT调查。配图来源&#xff1a; SaleSmartly(ss客服) 一、什么是 CSAT&#xff1…

算法笔记(十二)—— Manacher算法(回文子串)

计算字符串内的最大回文子串&#xff0c;常用的暴力扩散在应对长度为偶数的回文时会遇到一些问题。 Manacher基础&#xff1a;对字符串进行填充&#xff0c;在字符串开头结尾以及字符间填充‘#’&#xff0c;以来应对偶数回文时的问题。&#xff08;这是采用暴力扩再除2&#x…

[黑马程序员SSM框架教程]03 spring核心概念

IOC/DI 书写现状&#xff1a;耦合度偏高 如图&#xff1a;传统书写代码左边业务层需要new一个对象进行业务实现。当数据层优化代码BookDaoImpl2就需要动业务层代码重新修改new的对象。导致代码耦合度偏高。 解决办法&#xff1a;使用对象&#xff0c;不要主动new对象&#xff…

kubernetes traefik ingress 安装部署以及使用和注意点

1、简介 Traefik 是一款 open-source 边缘路由器&#xff0c;可让您轻松地发布服务. 它接收来自您的系统请求&#xff0c;并找出负责处理它们的后端服务组件。 traefik 与众不同在于它能够自动发现适合您服务的配置。 当 Traefik 检查您的基础设施时&#xff0c;它会发现相关信…

Redisson实现分布式锁

目录Redisson简介Redisson实现分布式锁步骤引入依赖application.ymlRedisson 配置类Redisson分布式锁实现Redisson简介 Redis 是最流行的 NoSQL 数据库解决方案之一&#xff0c;而 Java 是世界上最流行&#xff08;注意&#xff0c;没有说“最好”&#xff09;的编程语言之一。…

Matthew Ball:十多年后AR/VR为何依然发展缓慢?

2010年&#xff0c;Magic Leap和微软就开始研发AR技术&#xff0c;直到2012年Oculus才成立&#xff0c;AR/VR经过了13年左右的时间&#xff0c;虽然受到越来越多人关注&#xff0c;但发展依然缓慢。VR的主要应用场景还是游戏&#xff0c;但VR游戏只是游戏市场的一个分支&#x…

第七章.深度学习

第七章.深度学习 7.1 深度学习 深度学习是加深了层的深度神经网络。 1.加深层的好处 1).可以减少网络的参数数量 5*5的卷积运算示例&#xff1a; 重复两次3*3的卷积层示例&#xff1a; 图像说明&#xff1a; ①.一次5 * 5的卷积运算的区域可以由两次3 * 3的卷积运算抵消&a…

服务端开发Java之备战秋招面试篇1

在这个面试造火箭工作拧螺丝的时代背景下&#xff0c;感觉不是很好&#xff0c;不过还好也是拿到了还行的offer&#xff0c;准备去实习了&#xff0c;接下来就是边实习边准备秋招了&#xff0c;这半年把&#xff08;技术栈八股文面经算法题项目&#xff09;吃透&#xff0c;希望…

打破数据孤岛,Apache Doris 助力纵腾集团快速构建流批一体数仓架构|最佳实践

福建纵腾网络有限公司&#xff08;简称“纵腾集团”&#xff09;成立于 2009 年&#xff0c; 以“全球跨境电商基础设施服务商”为企业定位&#xff0c;聚焦跨境仓储与物流&#xff0c; 为全球跨境电商商户、出口贸易企业、出海品牌商提供海外仓储、商业专线物流、定制化物流等…

【C++】vector 模拟实现

vectorvector 容器vector 基本使用vector 定义库中各类接口的使用迭代器容量相关接口元素访问相关接口元素修改相关接口模拟实现 vector前期准备构造与析构赋值运算符重载迭代器相关容量相关元素访问相关元素的修改相关二维数组的创建对于自定义类型数据的测试vector 容器 C S…

Python实战之小说下载神器(二)整本小说下载:看小说不用这个程序,我实在替你感到可惜*(小说爱好者必备)

前言 这次的是一个系列内容给大家讲解一下何一步一步实现一个完整的实战项目案例系列之小说下载神器&#xff08;二&#xff09;&#xff08;GUI界面化程序&#xff09; 单章小说下载保存数据——整本小说下载 你有看小说“中毒”的经历嘛&#xff1f;小编多多少少还是爱看小说…

基于react+nodejs+mysql开发用户中心,用于项管理加入的项目的用户认证

基于reactnodejsmysql开发用户中心&#xff0c;用于项管理加入的项目的用户认证用户中心功能介绍页面截图后端采用架构user表projects表project_user表仓库地址用户中心功能介绍 用户中心项目&#xff0c;用于统一管理用户信息、登录、注册、鉴权等 功能如下&#xff1a; 用…