Skip to content

组件库、生命周期与 pinia(一) ​

本页对应 组件库、生命周期与 pinia,共 7 段视频,约 1 小时 21 分。

学习目标 ​

  • 能在 HBuilderX 中创建 Vue3 版 uni-app 项目,完成 uViewPlus 的安装与四项核心配置,并通过按钮、日历组件验证组件库生效。
  • 能区分 uni-app 中应用、页面、组件三类生命周期,并按“应用/页面用小程序生命周期、组件用 Vue 生命周期”的原则正确选用。
  • 能在 Vue3 组合式 API 中从 @dcloudio/uni-app 引入 onLoad、onShow 等页面生命周期,并运行验证其执行结果。
  • 能使用 uni.request 等 uni-app API,说明命名空间替换规则,并验证传入 success/fail 回调时不返回 Promise、不传回调时返回 Promise 的行为差异。
  • 能使用条件编译特殊注释,在 JS、CSS、模板中按 H5、小程序、App 等平台包含或排除代码,并解释其解决跨端兼容问题的作用。
  • 能规范克隆并导入神领物流项目,在 HBuilderX 中运行 H5 端,说明 task、user、message 三个分包及任务、消息、我的模块的主要业务流转。

知识点 ​

本节目标与边界 ​

本节的任务是把“神领物流”司机端的静态页面拿到本地,导入 HBuilderX 并跑通 H5,同时认识项目结构和业务主流程。转写稿中重点展开的是拉取代码、导入启动、分包与业务介绍;网络请求封装和公共方法封装在本节视频里没有展开,本文不虚构,后续随项目开发再补。

为什么需要:先有可运行基线,后面写页面、调接口才有对照。怎么想:先跑通,再读结构,最后改代码。怎么做:固定目录克隆、导入、运行 H5、课下再验小程序和 App。易错点:项目名和目录乱放,后面自己都不知道运行的是哪一份。

拉取代码:目录要规整 ​

为什么需要:从今天起连续几天都做同一个“神领物流”,如果桌面、下载目录到处克隆同名项目,排错时很难判断当前运行的是哪份代码。怎么想:把项目当成接下来几天的唯一工作副本,放在固定、整齐的目录里。怎么做:在目标目录打开终端,粘贴讲师提供的仓库地址克隆:

bash
# 在固定目录打开终端,粘贴讲师提供的仓库地址并克隆

易错点:不要今天克隆一份、明天再克隆一份;不要放桌面或随手目录。转写稿特别强调“名字都叫神领物流”时最容易混乱。

导入 HBuilderX 与启动 ​

为什么需要:代码拉下来只是文件,必须导入开发工具才能运行和调试。怎么想:拖拽和“导入本地”都可以,关键是路径清楚。怎么做:打开 HBuilderX,可把项目目录拖进去,也可点“导入→本地”选择目录。课堂先跑 H5,并切到手机模式;课下再跑小程序端和 App 端验证。易错点:从列表移除不是删除文件,别误点删除;控制台里小图标 404 可先忽略,转写稿说明它不影响主流程。

项目结构:uni-ui、三分包与入口文件 ​

为什么需要:先认路,后面才知道页面、样式、分包往哪放。怎么想:这个项目已经选用 uni-ui,不再重复整合 uView Plus。转写稿演示 uView Plus 时是“改四行代码、装两个依赖”,但神领物流不要照搬,否则容易整合乱。怎么做:展开目录,重点看 App.vue、main.js、uni.scss、static 字体图标,以及 task、user、message 三个分包,分别对应任务、我的、消息模块。易错点:不要把演示项目的组件库配置硬塞进本项目;main.js 里有 Vue3 条件编译判断,读代码时要能看懂。

业务主流程:任务、消息、我的 ​

为什么需要:不理解业务,页面状态变化就看不懂。怎么想:司机端核心是“接任务→提货→运输→交付”。怎么做:任务详情里可延迟提货或提货;延迟要填原因,提货要填凭证、上传照片;运输中可能异常上报,最后交付,任务进入已完成。消息模块含任务通知和公告;我的模块展示个人信息、车牌、里程等,系统设置、换手机号、换密码、清缓存等页面暂时没有接口,是摆设。易错点:不要纠结无接口页面,先跑通主流程。

条件编译:跨端兼容要会读 ​

为什么需要:uni-app 号称一套代码多端运行,但 H5 和小程序能力不同,比如扫码在 H5 基本做不到,小程序和 App 可以。怎么想:需要按平台保留或屏蔽代码时,用条件编译。怎么做:JS、CSS、模板里都用特殊注释:#ifdef H5 到 #endif 表示仅 H5 生效;#ifndef H5 表示非 H5 生效。项目里任务样式有 H5 高度判断,main.js 有 Vue3 判断。易错点:CSS 注释是 /* */,不是 //;开始和结束必须配对;条件表达式很多,不必背全,会查文档即可。

读代码前的基础结论 ​

为什么需要:项目里页面和组件混用生命周期与 API。怎么想:应用、页面优先用小程序生命周期;自定义组件用 Vue 生命周期。API 统一从微信的 wx.xxx 换成 uni.xxx。怎么做:页面里写 onLoad、onShow;组件里写 created、mounted。调 uni.request 想要 Promise 时,不要传 success、fail、complete。易错点:在 Vue 组合式 API 里直接用小程序生命周期要按 @dcloudio/uni-app 引入;传了回调函数就不会按 Promise 返回。

收尾验证 ​

为什么需要:没跑起来,下午写代码会掉队。怎么想:先 H5 验证,再课下补小程序和 App。怎么做:运行到浏览器,切手机模式,点进任务详情看页面跳转;改一处条件编译样式或文案,确认热更新生效。易错点:只导入不运行、只跑一个端就认为全通,都是本节常见问题。

登录状态是神领物流接口的前置条件 ​

为什么需要:本节笔记开宗明义:神领物流所有接口的调用都必须是登录状态才可以。也就是说,登录不是孤立页面,而是后续任务、消息、我的等模块的前置条件。没有登录状态,后续请求没有统一凭证,业务无法展开。

怎么想:登录成功后第一件事不是只做页面跳转,而是把登录凭证 token 记录下来,并让所有页面、所有请求都能拿到它。token 属于跨页面共享状态,不能只放在某个页面的局部变量里。

怎么做:登录成功后把 token 写入统一的状态管理;请求封装时统一读取 token;未登录或 token 失效时回到登录流程。转写稿中项目叫“神领物流”,不是“申领物流/森林物流”,统一纠正。

易错点:只在当前页面存 token,刷新或切页丢失;多个页面各自存一份导致不一致;把 token 当用户信息;只做跳转不记录登录态。

为什么选 Pinia:Vue3 下的状态管理选型 ​

为什么需要:笔记指出,用户登录后需要记录登录状态信息;Vue2 项目用 Vuex3,Vue3 项目需要用 Vuex4 或 Pinia。选型不解决,token 就没有稳定落点。

怎么想:Pinia 是 Vue 专属状态管理库,是 Vuex 的替代品。它的特点包括:更简单的 API(去掉 mutation)、组合式风格 API、去掉 modules 概念且每个 store 独立模块、配合 TypeScript 更友好并提供可靠类型推断。对登录 token 这种跨页面共享状态,独立 user/auth store 更清晰。

怎么做:Vue3 项目优先按笔记选 Pinia;按业务拆 store,登录相关只放 token 和必要用户标识;组件按需引入,不把所有业务塞进一个 store。

易错点:仍按 Vuex 的 mutation 思路写;把 Pinia 当普通全局变量乱放;store 里塞不可序列化大对象;Vuex4 与 Pinia 混用造成维护混乱。

Pinia 特点逐条落地 ​

为什么需要:理解特点是为了避免设计错。笔记列出的四条不是背诵项,而是设计约束。

怎么想:API 更简单,意味着少样板代码;组合式风格,意味着和 setup 写法一致;无 modules,意味着按文件拆独立 store;TS 友好,意味着 token 类型、用户标识类型应明确。

怎么做:登录 store 只暴露 token、设置登录态、清除登录态等方法;页面只调用方法,不直接改内部状态;请求层从 store 取 token。

易错点:以为“无 modules”就不用拆分;在 store 里直接发登录请求导致状态与请求耦合;退出登录忘记清除 token;类型写成 any。

安装 Pinia:保留笔记代码块,不臆补命令 ​

为什么需要:使用 Pinia 前要安装。但本节笔记在“2.1.1 安装 Pinia”后给出的代码块是空的,没有具体安装命令,不能擅自编造。

怎么想:先看项目 package.json 和锁文件是否已含 Pinia;若没有,再按团队使用的包管理器安装;安装后按项目脚手架实际方式注册。笔记未给注册代码,本节不展开臆写。

怎么做:保留笔记原代码块如下:

bash

易错点:盲目复制网上命令导致版本不一致;装完不重启开发服务;只验证 H5 不验证小程序/App;把空代码块当成可执行命令。

结合项目:登录模块在司机端的落点与调试 ​

为什么需要:转写稿介绍神领物流司机端有三个分包:task、user、message,对应任务、我的、消息模块。任务列表、消息通知、个人信息等接口都要登录后才能拉取。

怎么想:先跑通 H5 验证项目,再确认三个分包;登录后 token 供三个分包共用;H5 与小程序/App 存在差异,例如转写稿中扫码在 H5 做不到,需要条件编译。

怎么做:把代码克隆到规整目录,用 HBuilderX 导入并跑 H5;登录后请求任务列表观察是否携带 token;若用 uni.request 并 await,不要同时传 success 回调——转写稿验证过,传入 success 后不会返回 Promise。

易错点:代码克隆多份找不到当前项目;只跑 H5 不验证小程序/App;await 与 success 混用;H5 强行做扫码;token 未带导致接口无数据。

代码示例 ​

示例一:页面生命周期 + 条件编译(index.vue) ​

vue
<template>
  <view class="content">
    <text class="title">{{ title }}</text>

    <!-- #ifdef H5 -->
    <button>只有 H5 端才会渲染这个按钮</button>
    <!-- #endif -->
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'

const title = ref('首页')

// 页面级生命周期:用小程序的生命周期,由 uni-app 提供
onLoad(() => {
  console.log('页面加载了...')
})

onShow(() => {
  console.log('页面显示了...')
})

// #ifdef H5
console.log('当前是 H5 环境')
// #endif

// #ifndef H5
console.log('当前不是 H5 环境')
// #endif
</script>

<style lang="scss">
/* #ifdef H5 */
.content {
  height: 100vh;
  background-color: red;
}
/* #endif */

/* #ifndef H5 */
.content {
  background-color: #fff;
}
/* #endif */
</style>

要点解释 / 易错点

  • 页面里不要写 onMounted 代替 onLoad:onLoad / onShow / onReady 是小程序页面生命周期,vue 里根本没有,必须从 @dcloudio/uni-app 引入;直接从 vue 引入 onLoad 会拿不到、也不会执行。
  • 选哪个生命周期,看你在写什么:App.vue 是应用级 → onLaunch / onShow / onHide;页面 → onLoad / onShow / onReady;自定义组件 → 用 vue 的 onMounted / onCreated 等。
  • 条件编译是“特殊的注释”:必须 #ifdef 开头、#endif 结尾,成对出现,少写一个 #endif 后面的样式会整片失效(转写稿里演示时就踩了这个坑)。
  • 注释符号跟着语言走:JS 用 //,CSS 用 /* */(不是 //),模板用 <!-- -->;#ifndef 是“不是”,别和 #ifdef 混。
  • title 用 ref 定义,否则模板里使用未定义变量控制台会报警告。

示例二:uni.request 返回 Promise 的写法 ​

js
// 页面中发起网络请求
uni.request({
  url: 'http://your-host/api/task/list' // 神领物流接口需登录态才返回数据
}).then(res => {
  console.log(res)
})

// 错误示范:传了 success 回调,就没有 Promise 了
uni.request({
  url: 'http://your-host/api/task/list',
  success: (res) => {
    console.log(123)
  }
}).then(res => {
  console.log(res) // 这里不会执行,且会报错
})

要点解释 / 易错点

  • 命名空间换成 uni:原来是 wx.request / wx.showLoading,在 uni-app 里统一换成 uni.request / uni.showLoading,用法完全一致。
  • 只有“不传回调”才返回 Promise:uni-app 里所有 API 都能返回 Promise,但一旦传入了 success / fail / complete,就不会返回 Promise,此时再 .then 就会报错——这是最常踩的坑。
  • 原生小程序里并不是所有 API 都返回 Promise,uni-app 做了统一,但“传回调即失去 Promise”这一点仍然成立。
  • 神领物流的接口必须登录后才能拿到数据,未登录时 .then 里能执行但返回的是空数据,别误判成请求失败。

示例三:uview-plus 组件库整合(main.js / uni.scss / App.vue) ​

js
// main.js
import App from './App'
import { createSSRApp } from 'vue'
import uviewPlus from '@/uni_modules/uview-plus'

export function createApp() {
  const app = createSSRApp(App)
  app.use(uviewPlus)
  return { app }
}
scss
/* uni.scss:把 uview-plus 的变量文件引入,全局生效 */
@import '@/uni_modules/uview-plus/theme.scss';
vue
<!-- App.vue -->
<script>
export default {
  onLaunch: function () {},
  onShow: function () {},
  onHide: function () {}
}
</script>

<style lang="scss">
/* App.vue 的 style 相当于全局样式,这里引入组件库样式 */
@import '@/uni_modules/uview-plus/index.scss';
</style>
bash
# 组件库依赖的库(转写稿:倒计时/时间用到 dayjs,另一个按文档一并安装)
npm install dayjs

要点解释 / 易错点

  • 总共只加了 4 行代码:main.js 两行(import uviewPlus + app.use(uviewPlus)),uni.scss 一行变量文件,App.vue 一行全局样式——其余都是项目模板自带的。
  • createSSRApp 不是 createApp:uni-app 的 main.js 模板本身就是 createSSRApp,别按网页端写法改掉。
  • App.vue 的 <style> 相当于 App.wxss(全局样式),<script> 相当于 App.js;它里面不能写 template,所以必须加 lang="scss" 才能让 scss 生效。
  • SCSS 已装可跳过:用 HBuilderX 创建的项目已内置 sass 编译,不必重复安装;装完依赖记得重启 HBuilderX。
  • 使用方式:<u-button type="success" :loading="true" loading-text="加载中">按钮</u-button>、<u-calendar :show="true"></u-calendar>,type 取值 primary / success / info / warning / error。

常见错误 ​

1. uView Plus 装完,组件能渲染但样式全丢 / 控制台报 Sass 变量未定义 现象:<u-button> 有 DOM 没样式,或编译报 Undefined variable: $u-primary。 原因:只装了 npm 包,四步接入没做全——main.js 里 import uviewPlus 后没 app.use(uviewPlus);uni.scss 没 @import 'uview-plus/theme.scss';App.vue 没引入 uview-plus/index.scss 或引入时漏了 lang="scss"。 修法:把四行代码逐条对回去,App.vue 的 <style> 必须带 lang="scss";改完重启 HBuilderX(不是刷新浏览器),否则新依赖不参与编译。

2. 组合式 API 里写 onLoad 直接报 onLoad is not defined 现象:<script setup> 中写 onLoad(() => {}),编译或运行时报未定义。 原因:onLoad / onShow / onReady 是小程序页面生命周期,Vue 不提供,import { onLoad } from 'vue' 必然拿不到。 修法:从 @dcloudio/uni-app 引入。顺带一条判据:凡是从 vue 能引到的(onMounted、onUnmounted)属于组件级;页面级、应用级的都必须走 uni-app 的包。

3. 生命周期写对了语法,但就是不执行 现象:App.vue 里写 onLoad 没反应;页面里写 onLaunch 没反应;自定义组件里写 onShow 没反应。 原因:应用级、页面级、组件级三类生命周期串了位置。文件是"什么身份",就只能用对应那一类。 修法:App.vue 只放 onLaunch / onShow / onHide;pages 下的页面放 onLoad / onShow / onReady / onHide;components 下的组件一律用 Vue 的 created / mounted / unmounted。写之前先问自己一句"这文件是应用、页面还是组件"。

4. uni.request(...).then() 拿到 undefined,或 success 里代码不执行 现象:加了 .then 但回调不触发;或者反过来,写了 .then 又填了 success,两边只活一个。 原因:传了 success / fail / complete 任一回调,就不再返回 Promise;两者是互斥的。 修法:二选一。要 await / then 就一个回调都别传;要回调风格就别接 .then。注意 uni.request 原生不支持 Promise,这个坑在网络请求里出现频率最高。

5. 条件编译写了等于没写,或整段代码凭空消失 现象:// #ifdef H5 里的代码在 H5 下没生效;或某段模板/样式编译后不见了。 原因:格式错——漏了 #、用了 // 注释却写在 CSS 里(CSS 必须 /* #ifdef H5 */)、平台标识小写 h5(应为 H5)、漏写 #endif、模板中把条件编译写进了标签属性内部而不是包在标签外层。 修法:JS 用 //,CSS 用 /* */,模板用 <!-- -->,三者前缀都是 #ifdef / #ifndef,收尾必须 #endif;平台标识大小写严格。养成"先写 #ifdef 紧接着写 #endif,再往中间填内容"的习惯,能避免九成的丢代码问题。

6. 明明改了代码,页面跑的还是老逻辑 现象:改完保存,行为不变;或打开的是上一个版本的项目。 原因:一是 HBuilderX 缓存 / 依赖未重装(尤其装完 uView Plus、dayjs 之后);二是本地克隆了多份同名项目,运行的压根不是当前这份。 修法:先重启 HBuilderX 再判断;项目目录固定一处、命名带版本意识,从列表清理旧项目时用"移除"而不是"删除"。

练习 ​

  1. 接入自检:新建空白 uni-app(Vue3)项目,完整接入 uView Plus,在首页放一个 u-button(type="success" + loading)和一个 u-calendar(show 为 true),改一次按钮主题色验证 uni.scss 变量确实全局生效。全程记录你改动了几处文件、几行代码。

  2. 生命周期归位:在 App.vue、pages/index/index.vue、components/demo.vue 三个文件里各打三条 console.log,分别用应用级、页面级、组件级生命周期,运行后对照控制台顺序,画出"打开首页 → 切后台 → 回前台"的完整执行序列。

  3. 同一功能两种写法:把练习 2 中首页的 onLoad、onShow 改写为 <script setup> 组合式 API 版本,对比选项式与组合式在引入来源、调用方式上的差异,写三行结论。

  4. Promise 二选一:调用同一个接口写两版——async/await 版(不传任何回调)和 success 回调版,各自打印返回值,验证"传回调则无 Promise"这条结论;再故意两个都写,观察报错信息。

  5. 条件编译三处落地:在 JS、CSS、模板三处各写一组 #ifdef H5 / #ifndef H5,H5 与微信小程序各跑一次,比对三处输出;再模拟"扫码按钮仅非 H5 显示"这一真实场景,用条件编译实现。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1uni-app中使用uViewPlus15:26
2uni-app中生命周期的用法-111:52
3uni-app中生命周期的用法-210:05
4uni-app中API的调用09:33
5uni-app中的条件编译15:24
6神领物流-拉取代码07:29
7神领物流-业务及代码情况介绍11:34

小结 ​

  1. uView Plus 的接入成本不在"会不会用组件",而在四行代码 + 两个依赖是否齐全;漏任何一处,症状都是"样式没了"而不是"报错",排查要按清单走,不要凭感觉。
  2. 装完依赖后重启 HBuilderX 是必要动作,不重启就别怀疑代码写错。
  3. 生命周期只有一条判断链:先认文件身份(应用 / 页面 / 组件),再选生命周期家族(小程序 / 小程序 / Vue),最后才选写法(选项式 / 组合式)。顺序颠倒必错。
  4. 组合式 API 下,Vue 能引的归 Vue,页面级与应用级的归 @dcloudio/uni-app——这条边界比记具体函数名更重要。
  5. uni.request 的 Promise 化是"有回调就没 Promise"的二选一,不是"都能用";这条同样适用于绝大多数 uni API。
  6. 条件编译本质是编译期剪枝,不是运行时判断——写错了不会报错,只会静默丢代码,因此格式(注释符、#、大小写、#endif 配对)必须一次到位。
  7. 跨端从来不是"一套代码零成本",getUserInfo、扫码这类能力本身就端端不同;条件编译就是官方给的兼容出口,遇到平台差异第一反应应该是它。
  8. 项目目录规整不是洁癖:多份同名克隆导致的"改了不生效",排查成本远高于当初多花十秒选目录。