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

小兔鲜儿_第一周综合笔记 小兔鲜儿 UniApp 项目 - 第一周综合笔记本笔记结合老师官方笔记重点理论 实践中遇到的疑问与解答适合复习和查阅。一、开发环境搭建1.1 UniApp 项目创建方式企业中最常见的三种方式方式适用场景HBuilderX 可视化创建快速原型、小团队Vue CLI 脚手架中大型项目、需要工程化Vite 脚手架官方推荐新项目、追求构建速度# 当前企业最推荐方式 npm create uni-applatest1.2 HBuilderX 安装Windows下载正式版.zipMac Intel2020年前下载正式版.dmgMac 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 常用包管理器 pnpmpnpm 比 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-typeslatest uni-helper/uni-ui-typeslatest2.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 InstanceTypetypeof 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存放数据datagetters计算数据computedactions修改数据/请求接口methods3.2 定义 Store// stores/modules/member.ts import { defineStore } from pinia import { ref } from vue export const useMemberStore defineStore( member, () { const profile refany() 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 跳登录、提示错误、隐藏 loading4.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 DataT { code: string msg: string result: T } export const http T(options: UniApp.RequestOptions) { return new PromiseDataT((resolve, reject) { uni.request({ ...options, success(res) { if (res.statusCode 200 res.statusCode 300) { resolve(res.data as DataT) } 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 DataT).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.request的success回调只要服务器有响应就触发不管状态码是什么。fail只在网络错误超时、断网时触发。所以需要手动在success里判断状态码。实践疑问source-client: miniapp是自定义请求头告诉服务器这个请求来自小程序端服务器可根据此字段返回适合小程序的数据。五、TypeScript 关键知识点5.1 import typeTS 里导入类型需要加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 PromiseDataT(...) } // 调用时传入具体类型 const res await httpBannerItem[]({ url: /home/banner }) // res.result 就是 BannerItem[] 类型有完整提示5.3 非空断言!vs 可选链?.// 非空断言强硬告诉 TS 有值如果实际没值运行时报错 activeIndex.value ev.detail.current! // 可选链 默认值更安全的写法推荐 activeIndex.value ev.detail?.current || 05.4 有无花括号的导入// 默认导出export default→ 不需要花括号名字可自定义 import XtxSwiper from /components/XtxSwiper.vue // 命名导出export const/type→ 需要花括号名字必须一致 import { ref, computed } from vue import { http } from /utils/http5.5 常用工具类型工具类型作用场景RequiredT把所有可选字段变成必填组件内部分页参数保证字段一定有值PartialT把所有必填字段变成可选接口传参时不想全部传实践疑问PageParams字段用?标记为可选是为了方便外部调用接口时灵活传参。组件内部的分页变量用RequiredPageParams是保证自己维护的分页状态一定有确定的值防止页码累加时出现undefined的问题。两个用途不一样。5.6 属性简写ES6// 完整写法 { data: data } // 简写属性名和变量名一样时 { data }实践疑问接口函数里写了data: 导致参数传不进去。正确写法是直接写data这是 ES6 的属性简写等同于data: data。六、Vue3 核心知识点6.1 ref 响应式数据// 定义响应式数据 const bannerList refBannerItem[]([]) // 泛型声明类型初始值为空数组 // JS 中修改需要 .value bannerList.value res.result // 模板中不需要 .value // xtx-swiper :listbannerList /原生小程序对比// 原生用 setData 修改 this.setData({ bannerList: [...] }) // Vue3直接赋值页面自动更新 bannerList.value [...]ref 有两个用途// 用途1响应式数据 const bannerList refBannerItem[]([]) // 用途2获取组件/DOM实例 const guessRef refXtxGuessInstance() 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() })来源钩子vueonMounted、onUnmounted等dcloudio/uni-apponLoad、onShow、onHide、onReady等实践疑问原生小程序不需要导入 onLoad因为原生是配置式写法钩子是对象的属性框架自动识别。Vue3 是函数式写法钩子是函数需要先导入再调用。6.4 defineExpose 暴露方法子组件想让父组件调用自己的方法需要用defineExpose暴露// 子组件暴露方法 defineExpose({ resetData, getMore: getHomeGoodsGuessLikeData, }) // 父组件获取实例并调用 const guessRef refXtxGuessInstance() guessRef.value?.getMore()父组件调用子组件方法的完整流程子组件 defineExpose 暴露方法 ↓ 父组件模板 refguessRef 拿到子组件实例 ↓ guessRef.value 就是子组件实例 ↓ guessRef.value.getMore() 调用子组件方法6.5 动态绑定:的区别!-- 不加 :传的是字符串 false -- swiper autoplayfalse !-- 加 :传的是布尔值 false -- swiper :autoplayfalse规律凡是要传数字、布尔值、变量、表达式都要加:传普通字符串才不加。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暴露resetData和getMore方法给父组件调用分页核心逻辑const pageParams: RequiredPageParams { page: 1, pageSize: 10 } const guessList refGuessItem[]([]) 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-ifisLoading / template v-else !-- 真实内容 -- /template实践疑问本地开发网速快骨架屏一闪而过看起来像空白。在微信开发者工具 Network 面板把网速调成 Slow 3G 或 2G 就能看到效果代码本身没有问题。8.6 下拉刷新scroll-view refresher-enabled refresherrefreshonRefresherrefresh :refresher-triggeredisTriggered 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 classscroll-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-appimport 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学习建议跟课阶段不要纠结每个配置的具体含义先把流程跑通。等做完整个项目再回头理解配置细节性价比更高。