当前位置: 首页 > news >正文

小兔鲜儿_第一周综合笔记

小兔鲜儿 UniApp 项目 - 第一周综合笔记

本笔记结合老师官方笔记重点理论 + 实践中遇到的疑问与解答,适合复习和查阅。


一、开发环境搭建

1.1 UniApp 项目创建方式

企业中最常见的三种方式:

方式适用场景
HBuilderX 可视化创建快速原型、小团队
Vue CLI 脚手架中大型项目、需要工程化
Vite 脚手架(官方推荐)新项目、追求构建速度
# 当前企业最推荐方式 npm create uni-app@latest

1.2 HBuilderX 安装

  • Windows:下载正式版.zip
  • Mac Intel(2020年前):下载正式版.dmg
  • Mac M1/M2/M3:下载 Mac Arm 正式版
  • 正式版 vs Alpha版:日常开发选正式版,Alpha版有新功能但不稳定

1.3 开发工作流(重要)

用 VSCode 写代码时,必须先启动编译命令,否则微信开发者工具看到的是旧代码。

# 只需执行一次,保持运行 pnpm dev:mp-weixin

完整流程:

1. VSCode 打开项目 2. 终端执行 pnpm dev:mp-weixin(保持运行,不要关闭) 3. 微信开发者工具打开 dist/dev/mp-weixin 目录 4. 在 VSCode 写代码、保存 5. 自动编译,微信开发者工具自动刷新

实践疑问:为什么需要这个命令?因为写的是 UniApp + Vue3 代码,微信开发者工具看不懂.vue文件,需要先编译成微信小程序认识的.wxml.js.wxss格式。pnpm dev:mp-weixin是一个实时编译器,一直在后台把 Vue 代码翻译成小程序代码。

1.4 常用包管理器 pnpm

pnpm 比 npm 速度更快、占用磁盘空间更少,很多新项目使用它。

# 安装 pnpm(如未安装) npm install -g pnpm

二、项目配置

2.1 引入 uni-ui 组件库

pnpm i @dcloudio/uni-ui

配置自动导入:

// pages.json { "easycom": { "autoscan": true, "custom": { "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue", "^Xtx(.*)": "@/components/Xtx$1.vue" } } }

安装类型声明文件:

pnpm i -D @uni-helper/uni-app-types@latest @uni-helper/uni-ui-types@latest

2.2 tsconfig.json 配置

{ "compilerOptions": { "types": [ "@dcloudio/types", "miniprogram-api-typings", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types" ] }, "vueCompilerOptions": { "plugins": ["@uni-helper/uni-app-types/volar-plugin"] } }

实践疑问:vueCompilerOptions飘红提示"未知编译器选项",这是 VSCode 对非标准 tsconfig 字段的误报,不影响实际运行,忽略即可。确保安装了 Vue - Official 插件,并禁用旧版 Vetur 插件。

2.3 全局组件类型声明

// src/types/components.d.ts import XtxSwiper from '@/components/XtxSwiper.vue' import XtxGuess from '@/components/XtxGuess.vue' declare module 'vue' { export interface GlobalComponents { XtxSwiper: typeof XtxSwiper XtxGuess: typeof XtxGuess } } // 组件实例类型 export type XtxGuessInstance = InstanceType<typeof XtxGuess>

实践疑问:配置全局自动导入后组件能正常运行,但编辑器类型提示不完整。原因是自动导入(编译时)和编辑器类型检查(实时)是独立的两套机制,需要在components.d.ts中手动声明类型才能获得完整提示。

两种类型的区别:

  • 组件类型(GlobalComponents 里的):影响模板里使用组件标签时的提示,悬停能看到组件有哪些 props
  • 组件实例类型(XtxGuessInstance):影响通过 ref 拿到实例后调用方法时的提示,能看到 defineExpose 暴露的方法和属性

2.4 Git 版本控制配置

.gitignore 推荐配置:

logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* node_modules dist .DS_Store *.local # 微信开发者工具私有配置(含个人 AppID,不应提交) project.private.config.json

注意:project.config.json是公共配置可以提交,project.private.config.json含个人信息需要忽略。

拉取模板并推送到自己仓库:

# 克隆老师模板到当前目录 git clone 老师的仓库地址 . # 换成自己的远程仓库 git remote set-url origin 你的gitee仓库地址 # 改分支名 git branch -m master main # 推送 git push -u origin main

三、Pinia 状态管理

3.1 基本概念

Pinia 是 Vue3 的状态管理库,把多个组件都需要用到的数据集中存放统一管理。

概念说明类比
state存放数据data
getters计算数据computed
actions修改数据/请求接口methods

3.2 定义 Store

// stores/modules/member.ts import { defineStore } from 'pinia' import { ref } from 'vue' export const useMemberStore = defineStore( 'member', () => { const profile = ref<any>() const setProfile = (val: any) => { profile.value = val } const clearProfile = () => { profile.value = undefined } return { profile, setProfile, clearProfile } }, { persist: { // 小程序端需替换为兼容多端的 API storage: { setItem(key, value) { uni.setStorageSync(key, value) }, getItem(key) { return uni.getStorageSync(key) }, }, }, }, )

实践疑问:为什么要替换持久化 API?因为默认的localStorage在小程序端不兼容,需要换成uni.setStorageSync/uni.getStorageSync来兼容多端。


四、网络请求封装

4.1 拦截器

拦截器在请求发出前或响应回来后自动做统一处理。

发请求 → [请求拦截器] → 服务器 服务器 → [响应拦截器] → 拿到数据

请求拦截器能做什么:拼接 baseURL、设置超时、添加请求头、添加 token

响应拦截器能做什么:判断状态码、401 跳登录、提示错误、隐藏 loading

4.2 完整封装代码

// src/utils/http.ts import { useMemberStore } from '@/stores' const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net' const httpInterceptor = { invoke(options: UniApp.RequestOptions) { // 1. 非 http 开头需拼接地址 if (!options.url.startsWith('http')) { options.url = baseURL + options.url } // 2. 请求超时 options.timeout = 60000 // 3. 添加小程序端请求头标识 options.header = { 'source-client': 'miniapp', ...options.header, } // 4. 添加 token const memberStore = useMemberStore() const token = memberStore.profile?.token if (token) { options.header.Authorization = token } }, } uni.addInterceptor('request', httpInterceptor) uni.addInterceptor('uploadFile', httpInterceptor) type Data<T> = { code: string msg: string result: T } export const http = <T>(options: UniApp.RequestOptions) => { return new Promise<Data<T>>((resolve, reject) => { uni.request({ ...options, success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data as Data<T>) } else if (res.statusCode === 401) { // 清理用户信息,跳转登录页 const memberStore = useMemberStore() memberStore.clearProfile() uni.navigateTo({ url: '/pages/login/login' }) reject(res) } else { uni.showToast({ icon: 'none', title: (res.data as Data<T>).msg || '请求错误' }) reject(res) } }, fail(err) { uni.showToast({ icon: 'none', title: '网络错误,换个网络试试' }) reject(err) }, }) }) }

4.3 常见 HTTP 状态码

状态码含义场景
400请求参数错误传的参数格式不对
401未授权没登录或 token 过期
403禁止访问没有权限
404找不到资源接口地址不存在
500服务器内部错误后端代码报错
502网关错误服务器挂了或重启中

规律:4xx 是客户端的问题(请求有误、没权限),5xx 是服务器的问题(后端崩了)。

实践疑问:404 为什么还能有返回数据?uni.requestsuccess回调只要服务器有响应就触发,不管状态码是什么。fail只在网络错误(超时、断网)时触发。所以需要手动在success里判断状态码。

实践疑问:source-client: miniapp是自定义请求头,告诉服务器这个请求来自小程序端,服务器可根据此字段返回适合小程序的数据。


五、TypeScript 关键知识点

5.1 import type

TS 里导入类型需要加type关键字:

// 导入值(函数、变量)→ 普通 import import { http } from '@/utils/http' import { ref, onMounted } from 'vue' // 导入类型(type、interface)→ 加 type import type { PageResult } from '@/types/global' import type { BannerItem } from '@/types/home'

原因:类型只在编译阶段存在,编译成 JS 后会被完全删掉。import type明确告诉编译器这是纯类型导入,不打包进去。

5.2 泛型<T>

让函数在调用时可以指定返回数据的类型:

// 定义时加 <T> export const http = <T>(options: UniApp.RequestOptions) => { return new Promise<Data<T>>(...) } // 调用时传入具体类型 const res = await http<BannerItem[]>({ url: '/home/banner' }) // res.result 就是 BannerItem[] 类型,有完整提示

5.3 非空断言!vs 可选链?.

// 非空断言:强硬告诉 TS 有值,如果实际没值运行时报错 activeIndex.value = ev.detail.current! // 可选链 + 默认值:更安全的写法(推荐) activeIndex.value = ev.detail?.current || 0

5.4 有无花括号的导入

// 默认导出(export default)→ 不需要花括号,名字可自定义 import XtxSwiper from '@/components/XtxSwiper.vue' // 命名导出(export const/type)→ 需要花括号,名字必须一致 import { ref, computed } from 'vue' import { http } from '@/utils/http'

5.5 常用工具类型

工具类型作用场景
Required<T>把所有可选字段变成必填组件内部分页参数,保证字段一定有值
Partial<T>把所有必填字段变成可选接口传参时不想全部传

实践疑问:PageParams字段用?标记为可选,是为了方便外部调用接口时灵活传参。组件内部的分页变量用Required<PageParams>,是保证自己维护的分页状态一定有确定的值,防止页码累加时出现undefined++的问题。两个用途不一样。

5.6 属性简写(ES6)

// 完整写法 { data: data } // 简写(属性名和变量名一样时) { data }

实践疑问:接口函数里写了data: ''导致参数传不进去。正确写法是直接写data,这是 ES6 的属性简写,等同于data: data


六、Vue3 核心知识点

6.1 ref 响应式数据

// 定义响应式数据 const bannerList = ref<BannerItem[]>([]) // 泛型声明类型,初始值为空数组 // JS 中修改需要 .value bannerList.value = res.result // 模板中不需要 .value // <xtx-swiper :list="bannerList" />

原生小程序对比:

// 原生:用 setData 修改 this.setData({ bannerList: [...] }) // Vue3:直接赋值,页面自动更新 bannerList.value = [...]

ref 有两个用途:

// 用途1:响应式数据 const bannerList = ref<BannerItem[]>([]) // 用途2:获取组件/DOM实例 const guessRef = ref<XtxGuessInstance>() guessRef.value?.getMore()

6.2 defineProps 组件类型声明

// 子组件中声明接收的 props(必须写,否则没有类型提示) defineProps<{ list: BannerItem[] }>()

defineProps永远写在子组件里,用来接收父组件传来的数据。加了类型后,父组件传错类型会报错提醒。

6.3 页面生命周期钩子

UniApp 的页面钩子需要从@dcloudio/uni-app导入,不同于原生小程序的自动注入:

import { onLoad, onShow } from '@dcloudio/uni-app' onLoad(() => { getHomeBannerData() })
来源钩子
vueonMountedonUnmounted
@dcloudio/uni-apponLoadonShowonHideonReady

实践疑问:原生小程序不需要导入 onLoad,因为原生是配置式写法,钩子是对象的属性,框架自动识别。Vue3 是函数式写法,钩子是函数,需要先导入再调用。

6.4 defineExpose 暴露方法

子组件想让父组件调用自己的方法,需要用defineExpose暴露:

// 子组件暴露方法 defineExpose({ resetData, getMore: getHomeGoodsGuessLikeData, }) // 父组件获取实例并调用 const guessRef = ref<XtxGuessInstance>() guessRef.value?.getMore()

父组件调用子组件方法的完整流程:

子组件 defineExpose 暴露方法 ↓ 父组件模板 ref="guessRef" 拿到子组件实例 ↓ guessRef.value 就是子组件实例 ↓ guessRef.value.getMore() 调用子组件方法

6.5 动态绑定:的区别

<!-- 不加 :,传的是字符串 "false" --> <swiper autoplay="false"> <!-- 加 :,传的是布尔值 false --> <swiper :autoplay="false">

规律:凡是要传数字、布尔值、变量、表达式,都要加:,传普通字符串才不加。

6.6 Promise.all 并发请求优化

// 串行写法(慢):三个请求一个接一个执行 onLoad(async () => { await getHomeBannerData() await getHomeCategoryData() await getHomeHotData() }) // 并发写法(快):三个请求同时发出 onLoad(async () => { await Promise.all([ getHomeBannerData(), getHomeCategoryData(), getHomeHotData(), ]) })

适用场景:多个请求之间没有依赖关系时,用Promise.all并发请求可以显著提升页面加载速度。Promise.all会等所有请求都完成后才继续执行,任何一个失败则全部失败。


七、组件通信设计思路

模式适用场景例子
父传子(props)展示型组件,数据简单导航栏、轮播图
组件内部自己请求功能型组件,有独立业务逻辑、多处复用猜你喜欢、评论列表

核心思想:数据和逻辑放在最合适的地方,哪里用哪里管,减少不必要的耦合。


八、首页模块各组件梳理

8.1 自定义导航栏(CustomNavbar)

  • 属于首页的业务组件,存放在pages/index/components/
  • 需要在pages.json中隐藏默认导航栏
  • 通过uni.getSystemInfoSync()获取安全区域适配不同机型
// pages.json { "path": "pages/index/index", "style": { "navigationStyle": "custom" } }

8.2 轮播图组件(XtxSwiper)

  • 通用组件,存放在src/components/,首页和分类页都用
  • 通过defineProps接收list数据
  • 使用UniHelper.SwiperOnChange类型处理 swiper change 事件

8.3 热门推荐(HotPanel)

  • 首页业务组件,存放在pages/index/components/
  • 父组件请求数据,通过props传给子组件展示

8.4 猜你喜欢(XtxGuess)重难点

  • 通用组件,存放在src/components/,多页面复用
  • 组件内部自己请求数据(而非父传子)
  • 实现触底分页加载
  • 通过defineExpose暴露resetDatagetMore方法给父组件调用

分页核心逻辑:

const pageParams: Required<PageParams> = { page: 1, pageSize: 10 } const guessList = ref<GuessItem[]>([]) const finish = ref(false) const getHomeGoodsGuessLikeData = async () => { if (finish.value === true) { return uni.showToast({ icon: 'none', title: '没有更多数据~' }) } const res = await getHomeGoodsGuessLikeAPI(pageParams) guessList.value.push(...res.result.items) // 数组追加,不是替换 if (pageParams.page < res.result.pages) { pageParams.page++ } else { finish.value = true } }

8.5 骨架屏(PageSkeleton)

骨架屏在数据加载期间显示占位内容,提升用户体验。控制逻辑:

const isLoading = ref(false) onLoad(async () => { isLoading.value = true // 开始加载,显示骨架屏 await Promise.all([...]) isLoading.value = false // 加载完成,隐藏骨架屏 })
<PageSkeleton v-if="isLoading" /> <template v-else> <!-- 真实内容 --> </template>

实践疑问:本地开发网速快,骨架屏一闪而过看起来像空白。在微信开发者工具 Network 面板把网速调成 Slow 3G 或 2G 就能看到效果,代码本身没有问题。

8.6 下拉刷新

<scroll-view refresher-enabled @refresherrefresh="onRefresherrefresh" :refresher-triggered="isTriggered" scroll-y >
const isTriggered = ref(false) const onRefresherrefresh = async () => { isTriggered.value = true // 开启动画 guessRef.value?.resetData() // 重置猜你喜欢数据 await Promise.all([ getHomeBannerData(), getHomeCategoryData(), getHomeHotData(), guessRef.value?.getMore(), ]) isTriggered.value = false // 关闭动画 }

九、scroll-view 使用注意事项

scroll-view 不能滚动时排查以下三点:

1. 有没有加 scroll-y 属性?(没有这个属性默认不能纵向滚动) 2. 有没有固定高度?(高度自动撑开时不会触底) 3. 类名有没有写对?

正确配置:

<scroll-view scroll-y class="scroll-view"> <style> page { height: 100%; display: flex; flex-direction: column; } .scroll-view { flex: 1; } </style>

十、文件目录规范

src/ ├── components/ # 通用全局组件(多页面复用) ├── pages/ │ └── index/ │ └── components/ # 首页业务组件(仅首页使用) ├── services/ # 接口请求函数(按模块拆分) │ └── home.ts ├── stores/ # Pinia 状态管理 │ └── modules/ │ └── member.ts ├── types/ # TypeScript 类型声明 │ ├── global.d.ts # 通用类型(PageResult、PageParams) │ ├── home.d.ts # 首页相关类型 │ └── components.d.ts # 全局组件类型声明 └── utils/ # 工具函数 └── http.ts # 请求封装(拦截器等基础配置)

utils/放工具函数,services/放接口请求函数,两者职责不同。http.ts放在utils/是因为它是基础工具性质的封装。


十一、常见报错速查

报错信息原因解决方法
pnpm 不是内部命令未安装 pnpmnpm install -g pnpm
找不到名称 onLoad未导入import { onLoad } from '@dcloudio/uni-app'
import type报错类型用普通 import改为import type { xxx }
应有 1 个参数但获得 0 个调用函数时漏传参数按函数定义补充参数
不能将类型分配给类型Props 类型字段缺失或写成了字符串检查defineProps中类型字段,去掉引号
Error: timeout请求超时增大timeout值,检查域名校验设置
touristappid报错AppID 未配置manifest.json填入真实 AppID
修改代码不生效编译命令未运行执行pnpm dev:mp-weixin
页面不能滚动缺少 scroll-y 或高度未设置加上 scroll-y,给 scroll-view 设置高度
[object Object]报错对象被当成字符串拼接console.log 用逗号分隔而不是加号
请求参数没有传到接口接口函数里忘记写 data 字段在 http 调用里加上data

学习建议:跟课阶段不要纠结每个配置的具体含义,先把流程跑通。等做完整个项目再回头理解配置细节,性价比更高。

http://www.zskr.cn/news/1500250.html

相关文章:

  • 2026通辽漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 面向H200集群的大语言模型与VLA模型微调系统:全流程开发与部署解决方案
  • 发SCI心态崩了?来试试1区天菜PINN机器学习!简单好学易上手!
  • 从 CAD+SU 到逸模|效果图制作,告别反复手动同步主旨
  • 商业 |封了自家元宝,微信AI亲自下场
  • 物流数字化架构解析:多式联运全链路数据打通的落地与实践
  • 数据的加密与解密(22:30)
  • AI领域40多年,真正不变的是什么?
  • 工作流智能体_推理型智能体ReAct Agent_Agent平台---AI大模型系统从零开始0008
  • 当香云纱遇见东京:一场跨越千年的东方美学对话
  • 2026年6月天津律师测评!全方位婚姻策略指导/证据收集/谈判支持/诉讼 - 资讯快报
  • 12602华夏之光永存:黄大年茶思屋榜文126期 第2题 进程级抽象到容器级抽象容器原生OS架构解题
  • Codex App 从0到1完整入门教程
  • 鸿蒙从零掌握核心:幸运数字生成器实战
  • MinIO创建存储桶与密钥对赋权操作指南
  • 2026锦州漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 2026马鞍山漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 性价比高的教务系统供应商
  • 2026贵阳漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • Java IO输入输出流精讲|流的作用、划分方式与使用场景梳理
  • 100 万科研者的共同选择背后:中国科研正在发生什么结构性变化?
  • 2026邯郸漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • 2026年 无锡注册营业执照代办优选榜单:高效合规、一站式公司注册代办服务深度推荐 - 品牌发掘
  • 2026年口碑好的 青岛正规画室、美术培训学校排行:5家机构办学实力实测对比 - 起跑123
  • AI Agent Harness Engineering 作为企业家:自主发现并执行商业机会的潜力
  • 数据的加密与解密(22:35)
  • 2026晋中漏水维修攻略|一修匠修缮:厨卫 阳台 外墙 屋顶 地下室|靠谱防水门店 - 绿呼吸检测中心
  • Python课程设计-问题小结
  • 告别数据丢失!2026年超好用的文件自动备份工具推荐
  • 拆解一个真实的P2P金融系统:它的Web端、APP端和后管平台都是怎么协作的?