UniApp前端面试

3 阅读30分钟

UniApp前端面试

一、基础信息

1. 什么是UniApp?它的核心特点是什么?与传统前端框架(Vue/React)、原生开发(iOS/Android)、其他跨端框架(Flutter/React Native/Taro)的区别是什么?

答案

  • 定义:UniApp是DCloud推出的基于Vue.js的跨端开发框架,支持“一套代码”编译为iOS、Android、微信小程序、H5、支付宝小程序等10+平台。

  • 核心特点

    ① 跨端一致性:一套代码多端运行,降低多端开发成本;

    ② Vue生态兼容:复用Vue语法、组件化、Vuex/Pinia,学习成本低;

    ③ 原生能力调用:通过uni API直接调用各端原生能力(如支付、定位);

    ④ 性能贴近原生:App端支持nvue(原生渲染),小程序端复用原生渲染逻辑。

  • 区别

    • 与Vue/React:Vue/React是单端框架,UniApp是跨端框架(基于Vue扩展跨端能力);

    • 与原生开发:原生需分别编写iOS/Android代码,UniApp一套代码覆盖多端,但复杂原生能力需插件补充;

    • 与Flutter:Flutter是自绘引擎(UI一致性强),UniApp适配各端原生渲染(更贴合平台特性);

    • 与React Native:RN通过JS桥接原生组件,UniApp编译为各端原生代码(小程序端无桥接损耗);

    • 与Taro:Taro基于React语法,UniApp基于Vue语法,且UniApp对小程序生态支持更深度。

2. UniApp支持哪些端的开发?不同端的打包流程有什么差异?

答案

  • 支持端:App(iOS/Android)、微信/支付宝/百度/字节跳动/QQ等小程序、H5、快应用、桌面端(Windows/macOS)。

  • 打包流程差异

    H5端:直接运行或打包为静态资源(HTML/CSS/JS),部署到服务器即可;

    小程序端:HBuilderX中选择对应平台“发行”,生成小程序代码包,导入各平台开发者工具后上传审核;

    App端:先配置manifest.json(应用名称、权限、SDK),选择“发行-原生App-云打包/本地打包”:

    • 云打包:直接生成apk/ipa安装包(无需本地SDK);

    • 本地打包:需安装Android Studio/Xcode,生成原生工程后编译打包;④ 桌面端:需安装UniApp桌面端打包插件,编译为exe/dmg文件。

3. UniApp的项目结构是怎样的?pages.json/manifest.json/App.vue/main.js分别有什么作用?

答案

  • 核心结构

    ├── pages/           // 页面目录(每个页面一个子目录,含.vue/.json/.js/.wxss等)
    ├── static/          // 静态资源(图片、字体等,编译时不压缩)
    ├── components/      // 自定义组件目录
    ├── uni_modules/     // 插件市场下载的模块
    ├── pages.json       // 页面路由、全局配置
    ├── manifest.json    // 应用配置(名称、权限、各端参数)
    ├── App.vue          // 应用入口(全局样式、应用生命周期)
    ├── main.js          // Vue实例创建、全局挂载
    ├── uni.scss         // 全局样式变量(如主题色)
    └── unpackage/       // 打包输出目录
    
  • 文件作用

    • pages.json:配置页面路由(pages数组)、tabbar、全局导航栏样式、下拉刷新、分包加载等;

    • manifest.json:配置应用名称、图标、版本号、权限(如相机/定位)、各端特有配置(如小程序appid、App的SDK密钥);

    • App.vue:应用级根组件,包含onLaunch/onShow/onHide等应用生命周期,可定义全局样式;

    • main.js:创建Vue实例(createApp/createSSRApp),挂载全局组件、状态管理(Vuex/Pinia)、全局拦截器等。

4. UniApp中easycom组件自动引入机制的原理是什么?如何配置?

答案

  • 原理easycom是UniApp的组件自动引入机制,无需手动importVue.component注册,框架会根据约定的目录结构自动扫描、注册组件,降低组件使用成本。

  • 约定规则

    ① 组件需放在components目录下(或自定义目录),且组件目录名与组件文件名一致(如components/uni-button/uni-button.vue);

    ② 页面中直接使用组件标签(如<uni-button>),框架会自动匹配并引入对应组件。

  • 配置方式pages.json):

    {
      "easycom": {
        "autoscan": true, // 开启自动扫描
        "custom": {
          // 自定义匹配规则:正则匹配组件名 → 组件路径
          "^uni-(.*)": "@/components/uni-$1/uni-$1.vue",
          "^my-(.*)": "@/components/my-components/my-$1.vue"
        }
      }
    }
    
  • 注意easycom默认开启,无需手动配置即可使用官方组件或符合约定的自定义组件。

5. UniApp的条件编译如何使用?请举例说明(如区分H5和小程序、区分iOS和Android)。

答案

  • 条件编译:通过#ifdef(存在某平台)/#ifndef(不存在某平台)+ 平台标识,实现不同平台的代码差异化编译(非运行时判断),打包时会剔除无用代码,减少体积。

  • 常用平台标识

    • APP-PLUS:App端;APP-IOS:iOS端;APP-ANDROID:Android端;

    • MP-WEIXIN:微信小程序;MP-ALIPAY:支付宝小程序;H5:H5端;

    • MP:所有小程序平台;PLUS:所有App平台。

  • 示例

    <!-- 模板中区分H5和微信小程序 -->
    <view>
      #ifdef H5
      <button @click="openH5Pay">H5支付</button>
      #endif
      #ifdef MP-WEIXIN
      <button @click="openWxPay">微信支付</button>
      #endif
    </view>
    
    <script>
    export default {
      methods: {
        getDeviceInfo() {
          // 脚本中区分iOS和Android
          #ifdef APP-IOS
          uni.showToast({title: 'iOS设备'});
          #endif
          #ifdef APP-ANDROID
          uni.showToast({title: 'Android设备'});
          #endif
        }
      }
    }
    </script>
    <style>
    /* 样式中区分App和H5 */
    #ifdef APP-PLUS
    .container {padding: 20px;}
    #endif
    #ifdef H5
    .container {padding: 10px;}
    #endif
    </style>
    

6. UniApp中全局样式和页面样式的优先级是怎样的?如何修改原生组件的样式(如导航栏、tabbar)?

答案

  • 样式优先级:页面内局部样式 > App.vue全局样式 > uni.scss全局变量样式;行内样式 > 带!important的样式 > 普通样式;小程序端不支持*选择器,且原生组件(如picker)样式需用组件自身属性或深度选择器修改。

  • 修改原生组件样式:① 导航栏:在pages.json中配置(全局/页面级):

    // 全局导航栏
    {
      "globalStyle": {
        "navigationBarTitleText": "标题",
        "navigationBarBackgroundColor": "#ffffff",
        "navigationBarTextStyle": "black",
        "backgroundColor": "#f5f5f5"
      },
      // 页面级导航栏(覆盖全局)
      "pages": [
        {
          "path": "pages/index/index",
          "style": {
            "navigationBarTitleText": "首页",
            "enablePullDownRefresh": true
          }
        }
      ]
    }
    

    tabbar:在pages.json中配置:

    "tabBar": {
      "color": "#666666", // 未选中颜色
      "selectedColor": "#ff5722", // 选中颜色
      "backgroundColor": "#ffffff",
      "list": [
        {
          "pagePath": "pages/index/index",
          "text": "首页",
          "iconPath": "static/tab/home.png",
          "selectedIconPath": "static/tab/home-active.png"
        },
        {
          "pagePath": "pages/my/my",
          "text": "我的",
          "iconPath": "static/tab/my.png",
          "selectedIconPath": "static/tab/my-active.png"
        }
      ]
    }
    

    原生组件(如 input / picker :通过组件属性(如style/class)或深度选择器(::v-deep/>>>)修改:

    ::v-deep .uni-input-input {
      font-size: 14px;
      color: #333;
    }
    

7. UniApp与Vue的语法有哪些差异?(如指令、生命周期、API调用)

答案

  • 指令差异

    • UniApp新增@tap(替代@click,兼容各端点击事件,小程序无click事件);

    • 不支持Vue的v-html(小程序限制),需用rich-text组件渲染富文本;

    • v-for必须加key(强制要求,否则报错),且不支持v-forv-if同级使用。

  • 生命周期差异

    • UniApp新增应用生命周期onLaunch/onShow/onHide)和页面生命周期onLoad/onShow/onReady/onPullDownRefresh);

    • Vue的created/mounted在UniApp中仍可用,但页面渲染完成建议用onReady(小程序端mounted可能早于页面渲染)。

  • API差异

    • 路由跳转:UniApp用uni.navigateTo/uni.switchTab等,替代Vue的$router.push

    • 数据请求:UniApp用uni.request,替代axios/fetch(H5端可兼容axios,但需处理跨域);

    • 本地存储:UniApp用uni.setStorage/uni.getStorage,替代localStorage(小程序无localStorage)。

二、生命周期类

1. UniApp的生命周期分为哪几类?(应用生命周期、页面生命周期、组件生命周期)

答案

UniApp的生命周期分为三类:

  • 应用生命周期:在App.vue中定义,对应应用的启动、显示、隐藏等全局状态,常用钩子:

    • onLaunch:应用初始化完成(全局只触发一次);

    • onShow:应用前台显示(如从后台切回、首次启动);

    • onHide:应用后台隐藏(如切到桌面);

    • onError:应用报错时触发。

  • 页面生命周期:在页面.vue文件中定义,对应页面的加载、显示、卸载等,常用钩子:

    • onLoad:页面加载(接收跳转参数,仅触发一次);

    • onShow:页面显示(每次打开页面都触发);

    • onReady:页面初次渲染完成(可操作DOM/原生组件);

    • onHide:页面隐藏(如跳转到其他页面);

    • onUnload:页面卸载(如返回上一页);

    • onPullDownRefresh:下拉刷新触发;

    • onReachBottom:上拉触底触发。

  • 组件生命周期:与Vue组件生命周期一致(Vue2/Vue3略有差异),常用钩子:

    • Vue2:beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed

    • Vue3:setuponBeforeMountonMountedonBeforeUpdateonUpdatedonBeforeUnmountonUnmounted

    • UniApp新增onInit(组件初始化,仅Vue3支持,比created更早)。

2. UniApp的应用生命周期(如onLaunch/onShow/onHide)与页面生命周期(如onLoad/onShow/onReady)的执行顺序是怎样的?

答案

  • 应用首次启动App.vue onLaunch → 首页onLoad → 首页onShow → 首页onReadyApp.vue onShow

  • 应用切换后台/前台

    • 前台→后台:当前页面onHideApp.vue onHide

    • 后台→前台:App.vue onShow → 当前页面onShow

  • 页面跳转(A→B):A页面onHide → B页面onLoad → B页面onShow → B页面onReady

  • 页面返回(B→A):B页面onUnload → A页面onShow

3. UniApp的页面生命周期与Vue的生命周期(created/mounted)有什么关联?执行时机有何不同?

答案

  • 关联:UniApp页面生命周期基于Vue生命周期扩展,执行顺序为:onLoadbeforeCreatecreatedonShowbeforeMountmountedonReady

  • 执行时机差异

    • onLoad:页面加载时触发,可接收跳转参数(如options.id),仅执行一次;

    • created:Vue实例创建完成(未挂载DOM),此时可访问数据,但无法操作DOM;

    • mounted:Vue实例挂载完成(DOM已生成),但小程序端可能存在“虚拟DOM已挂载,原生组件未渲染完成”的情况;

    • onReady:页面原生渲染完成(小程序端对应Page.onReady),此时操作原生组件(如地图、视频)更安全,比mounted更晚触发。

4. 小程序端和App端的UniApp生命周期有哪些差异?(如小程序的onUnload vs App的onBackPress

答案

  • 页面卸载

    • 小程序端:页面返回时触发onUnload(页面实例销毁);

    • App端:页面返回时先触发onHide,只有关闭应用或跳转到非栈内页面才触发onUnload

  • 返回键监听

    • 小程序端:无物理返回键,无法监听返回事件;

    • App端:支持onBackPress钩子(监听物理返回键/导航栏返回按钮),返回true可阻止默认返回:

      onBackPress(options) {
        // options.from:返回来源('backbutton'=物理键,'navigateBack'=API调用)
        if (this.isEditing) {
          uni.showModal({
            title: '提示',
            content: '是否放弃编辑?',
            success: (res) => {
              if (res.confirm) uni.navigateBack();
            }
          });
          return true; // 阻止默认返回
        }
        return false; // 允许返回
      }
      
  • 应用退出

    • 小程序端:无onExit钩子,直接退出;

    • App端:可通过plus.runtime.quit()主动退出,或监听onUniNViewMessage处理退出逻辑。

5. 自定义组件的生命周期与页面生命周期的区别是什么?组件中如何监听页面的生命周期?

答案

  • 区别

    • 页面生命周期包含UniApp特有钩子(如onLoad/onShow),组件生命周期与Vue一致(无页面钩子);

    • 页面生命周期由框架直接触发,组件生命周期依赖页面(页面卸载时组件触发destroyed/onUnmounted)。

  • 组件监听页面生命周期

    ① 通过uni.$on/uni.$emit全局事件:

    // 页面中触发
    onShow() {
      uni.$emit('pageShow', '首页显示');
    }
    // 组件中监听
    mounted() {
      uni.$on('pageShow', (data) => {
        console.log(data); // 首页显示
      });
    },
    unmounted() {
      uni.$off('pageShow'); // 解绑事件,避免内存泄漏
    }
    

    ② 通过@hook:生命周期监听:

    // 组件中监听页面onShow
    mounted() {
      this.$parent.$on('hook:onShow', () => {
        console.log('页面显示');
      });
    }
    

    ③ 使用uni.requireNativePlugin('uni-page-lifecycle')(原生插件,仅App端支持)。

6. 页面跳转时(如navigateTo),原页面和新页面的生命周期执行顺序是怎样的?

答案

以页面A通过uni.navigateTo跳转到页面B为例:

  1. 页面A触发onHide(页面隐藏);

  2. 页面B触发onLoad(页面加载,接收参数);

  3. 页面B触发onShow(页面显示);

  4. 页面B触发onReady(页面渲染完成);

若从页面B返回页面A:

  1. 页面B触发onUnload(页面卸载);

  2. 页面A触发onShow(页面重新显示)。

若使用uni.redirectTo(关闭当前页跳转):

页面A触发onUnload → 页面B触发onLoadonShowonReady

7. onPullDownRefreshonReachBottom的触发条件是什么?如何配置?

答案

  • onPullDownRefresh (下拉刷新)

    • 触发条件:页面下拉时触发(需先开启下拉刷新配置);

    • 配置方式:

      ① 全局开启:pages.jsonglobalStyle中设置"enablePullDownRefresh": true

      ② 页面级开启:页面style中设置"enablePullDownRefresh": true

    • 使用示例:

      onPullDownRefresh() {
        // 刷新数据
        this.loadData().then(() => {
          uni.stopPullDownRefresh(); // 停止下拉刷新动画
        });
      }
      
  • onReachBottom (上拉触底)

    • 触发条件:页面滚动到底部时触发(可配置触底距离);

    • 配置方式:

      ① 全局配置:pages.jsonglobalStyle中设置"onReachBottomDistance": 50(触底距离,单位px);

      ② 页面级配置:页面style中设置"onReachBottomDistance": 50

    • 使用示例:

      onReachBottom() {
        if (!this.isLoading && this.hasMore) {
          this.pageNum++;
          this.loadMoreData(); // 加载下一页
        }
      }
      

三、跨端实现与原理类

1. UniApp的跨端原理是什么?(编译器层面:将Vue代码编译为各端原生代码;运行时层面:统一的JS引擎和渲染层)

答案

UniApp的跨端核心是**“编译器+运行时框架”**双层架构:

  • 编译器层面:将Vue代码编译为各端原生代码/配置:

    ① 小程序端:将.vue文件拆分为wxml(模板)、wxss(样式)、js(逻辑),生成小程序配置文件(app.json/page.json);

    ② App端:Vue代码编译为JS,结合nvue(原生Vue)编译为Android的Java/iOS的OC代码;

    ③ H5端:编译为标准HTML/CSS/JS(类似Vue CLI打包)。

  • 运行时层面:提供统一的运行时框架,屏蔽各端差异:

    ① 统一API层:uni API(如uni.request/uni.navigateTo)封装各端原生API,调用时自动适配(如uni.request在H5端用fetch,小程序端用wx.request);

    ② 统一渲染层:App端支持nvue(基于weex,原生渲染)和vue(webview渲染),小程序/H5端用各端原生渲染引擎;

    ③ 统一JS引擎:App端用V8/JSCore,小程序端用各平台内置JS引擎,保证逻辑层一致。

2. UniApp的“一套代码多端运行”是如何实现的?与Taro的编译原理有什么不同?

答案

  • UniApp实现“一套代码多端运行”的核心

    语法抽象:基于Vue语法,抽象出跨端通用的组件(如<view>/<text>)和API(uni前缀),编译时映射为各端原生组件/API;

    条件编译:通过#ifdef等语法剔除非目标平台代码,保证打包产物纯净;

    运行时适配:运行时框架根据当前平台自动切换API实现,无需开发者关注底层差异。

  • 与Taro的编译原理差异

    • 语法基础:Taro基于React/JSX语法,UniApp基于Vue/SFC(单文件组件)语法;

    • 编译方式:Taro是“转译式”(将JSX转译为各端代码,如微信小程序的wxml);UniApp是“编译+适配”(Vue代码直接编译为各端代码,结合运行时适配);

    • 小程序支持:UniApp对小程序生态支持更深度(如兼容小程序原生组件/插件),Taro需额外适配;

    • App端性能:UniApp支持nvue原生渲染,Taro App端基于React Native(桥接渲染,性能略低)。

3. UniApp中“条件编译”和“运行时判断”的区别是什么?分别适用于什么场景?

答案

特性条件编译运行时判断
执行阶段编译阶段(打包时)运行阶段(代码执行时)
代码体积剔除无用代码,体积更小保留所有平台代码,体积更大
灵活性低(需提前确定平台)高(可动态判断)
实现方式#ifdef/#ifndef+平台标识uni.getSystemInfoSync()判断
  • 条件编译适用场景:差异化功能模块(如H5端的支付逻辑、小程序端的分享逻辑)、原生组件替换(如App端用nvue组件,H5端用web组件);

  • 运行时判断适用场景:小范围逻辑差异(如提示语、样式微调)、需动态获取平台信息的场景(如根据设备类型调整布局)。

  • 示例对比

    // 条件编译(编译时剔除其他平台代码)
    #ifdef MP-WEIXIN
    uni.showMenuButton();
    #endif
    
    // 运行时判断(保留所有代码,运行时分支执行)
    const platform = uni.getSystemInfoSync().platform;
    if (platform === 'weixin') {
      uni.showMenuButton();
    }
    

4. UniApp的渲染层和逻辑层是分离的吗?(如小程序的双线程模型)这种架构带来的优缺点是什么?

答案

  • 渲染层与逻辑层分离情况

    • 小程序端:采用双线程模型(逻辑层JS引擎+渲染层WebView/原生组件),渲染层与逻辑层分离,通过数据通信同步状态;

    • App端(vue页面):基于webview,渲染层与逻辑层在同一线程;App端(nvue页面):原生渲染,逻辑层与渲染层分离;

    • H5端:渲染层与逻辑层不分离(同标准web)。

  • 分离架构的优点

    ① 安全性高:逻辑层运行在独立线程,避免渲染层DOM操作影响逻辑层;

    ② 稳定性强:渲染层崩溃不影响逻辑层;

    ③ 小程序端:可限制逻辑层性能消耗,避免卡顿。

  • 分离架构的缺点

    ① 通信开销:渲染层与逻辑层需通过数据传递同步状态(如小程序的setData),频繁通信会导致性能损耗;

    ② 开发限制:逻辑层无法直接操作DOM(需通过数据驱动),原生组件交互需通过桥接;

    ③ 调试复杂:双线程模型增加调试难度(如断点需分别调试逻辑层和渲染层)。

5. UniApp如何调用原生SDK(如iOS的高德地图SDK、Android的支付SDK)?

答案

UniApp调用原生SDK有三种方式:

  • 方式1:使用插件市场的原生插件直接安装插件市场中已封装好的SDK插件(如高德地图、支付宝支付),通过uni.requireNativePlugin调用:

    const amap = uni.requireNativePlugin('AMap-Location');
    amap.getLocation((res) => {
      console.log(res.latitude, res.longitude);
    });
    
  • 方式2:开发自定义原生插件若需自定义SDK调用,需开发原生插件:

    ① Android端:用Java/Kotlin编写插件,注册到UniApp插件框架;

    ② iOS端:用OC/Swift编写插件,集成SDK并暴露API;

    ③ 插件打包后导入UniApp项目,通过uni.requireNativePlugin调用。

  • 方式3:App端通过 plus ** API调用原生能力**App端可直接使用plus API调用部分原生SDK(如定位、文件操作):

    // App端调用原生定位
    plus.geolocation.getCurrentPosition((res) => {
      console.log(res.coords.latitude);
    });
    

6. UniApp的原生插件(Native Plugin)开发流程是怎样的?如何在项目中集成?

答案

  • 原生插件开发流程(以Android为例):

    ① 创建Android Library项目,集成目标SDK(如高德地图);

    ② 实现UniPluginModule接口,暴露JS调用的方法(用@UniJSMethod注解):

    public class MyPlugin extends UniPluginModule {
        @UniJSMethod(uiThread = true)
        public void getSDKVersion(JSCallback callback) {
            String version = "1.0.0"; // SDK版本
            callback.invoke(version);
        }
    }
    

    ③ 配置插件清单(plugin.json),声明插件名称、类名、方法;④ 打包为aar文件,iOS端打包为.framework文件。

  • 项目集成原生插件

    ① 将插件文件放入项目nativeplugins目录;

    ② 在manifest.json中配置插件:

    "app-plus": {
      "nativePlugins": {
        "MyPlugin": {
          "plugins": [
            {
              "type": "module",
              "name": "MyPlugin",
              "class": "com.example.myplugin.MyPlugin"
            }
          ]
        }
      }
    }
    

    ③ 在代码中调用:

    const myPlugin = uni.requireNativePlugin('MyPlugin');
    myPlugin.getSDKVersion((res) => {
      console.log(res);
    });
    

7. UniApp中nvue(native vue)与普通vue页面的区别是什么?适用场景有哪些?

答案

  • nvue与普通vue页面的核心区别
特性nvue页面普通vue页面
渲染方式原生渲染(Android/iOS原生组件)WebView渲染(HTML/CSS)
性能高(接近原生App)中(依赖WebView性能)
样式支持仅支持flex布局,样式限制多支持标准CSS(Flex/Grid等)
组件支持仅支持nvue内置组件支持所有UniApp组件+Web组件
适用端仅App端所有端(App/小程序/H5)
  • nvue适用场景

    ① App端高性能页面(如长列表、动画密集的页面);

    ② 需要与原生组件深度交互的页面(如地图、视频播放);

    ③ 对流畅度要求高的页面(如电商商品详情、直播页面)。

  • 普通vue页面适用场景

    ① 多端兼容需求(需同时支持小程序/H5);

    ② 样式复杂的页面(如富文本、自定义布局);

    ③ 快速开发的页面(无需关注原生渲染细节)。

四、组件与API类

1. UniApp的组件分为哪几类?(内置组件、自定义组件、原生组件)请举例说明常用内置组件的差异(如view vs divtext vs span)。

答案

  • 组件分类

    内置组件:UniApp提供的跨端通用组件(如<view>/<text>/<button>/<image>);

    自定义组件:开发者编写的可复用组件(如<my-card>/<uni-button>);

    原生组件:各端原生组件(如小程序的<canvas>/<video>,App端的<map>)。

  • 常用内置组件差异

    • <view> vs <div><view>是UniApp的块级容器组件,对应H5的<div>、小程序的<view>,支持Flex布局,跨端兼容;<div>仅H5端支持,小程序/App端不识别。

    • <text> vs <span><text>是行内文本组件,对应H5的<span>,仅支持文本渲染(小程序限制),可通过selectable属性开启文本选择;<span>仅H5端支持。

    • <image> vs <img><image>支持跨端图片渲染,内置懒加载(lazy-load)、图片裁剪(mode属性,如widthFix/aspectFill);<img>仅H5端支持,无跨端适配能力。

    • <button> vs HTML <button>:UniApp的<button>支持跨端样式适配(如小程序的open-type属性),HTML <button>仅H5端支持。

2. 如何开发UniApp的自定义组件?全局注册和局部注册的区别是什么?组件间如何通信?(props/ emit、全局事件总线、Vuex/Piniauni.emit、全局事件总线、Vuex/Pinia、`uni. on/$emit`)

答案

  • 开发自定义组件步骤

    ① 在components目录下创建组件目录(如my-card),新建my-card.vue文件;

    ② 编写组件逻辑(模板/样式/脚本):

    <template>
      <view class="my-card">{{ title }}</view>
    </template>
    <script>
    export default {
      props: {
        title: {
          type: String,
          default: '默认标题'
        }
      }
    }
    </script>
    <style scoped>
    .my-card {padding: 10px; border: 1px solid #eee;}
    </style>
    

    ③ 注册并使用组件(通过easycom自动注册,或手动注册)。

  • 全局注册vs局部注册

    • 全局注册:在main.js中通过app.component('my-card', MyCard)注册,所有页面/组件可直接使用,适合高频组件;

    • 局部注册:在页面/组件的components选项中注册,仅当前页面/组件可用,适合低频组件(减少全局内存占用)。

  • 组件间通信方式

    props/$emit:父传子用props,子传父用$emit

    // 父组件
    <my-card :title="parentTitle" @change="handleChange"></my-card>
    // 子组件
    this.$emit('change', '新标题');
    

    ② 全局事件总线(uni.$on/$emit):跨组件/页面通信:

    // 发送事件
    uni.$emit('globalEvent', '数据');
    // 接收事件
    uni.$on('globalEvent', (data) => {console.log(data);});
    

    ③ 状态管理(Vuex/Pinia):全局数据共享(如用户信息、购物车);

    $parent/$children:直接访问父子组件实例(不推荐,耦合度高);

    provide/inject:跨层级组件通信(父组件provide提供数据,子组件inject注入数据)。

3. UniApp中slot(插槽)的使用与Vue有差异吗?如何实现作用域插槽?

答案

  • slot 使用差异:UniApp的slot基本兼容Vue语法,但小程序端有以下限制:

    ① 不支持slot的动态名称(如slot="dynamicName");、

    ② 小程序端slot嵌套深度有限(建议不超过3层);

    ③ 作用域插槽在小程序端需用template包裹。

  • 作用域插槽实现:子组件通过slotProps传递数据,父组件通过v-slot接收:

    <!-- 子组件(my-list.vue) -->
    <template>
      <view>
        <slot v-for="item in list" :item="item" :index="index"></slot>
      </view>
    </template>
    <script>
    export default {
      data() {
        return {
          list: ['A', 'B', 'C']
        };
      }
    }
    </script>
    
    <!-- 父组件使用 -->
    <template>
      <my-list>
        <template v-slot="slotProps">
          <view>{{ slotProps.index }}-{{ slotProps.item }}</view>
        </template>
      </my-list>
    </template>
    

    (注:小程序端需用template包裹作用域插槽内容,H5端可直接使用)

4. UniApp的API与微信小程序的API有什么关系?如何调用各端的原生API(如小程序的wx.request、App的plus API)?

答案

  • UniApp API与微信小程序API的关系:UniApp的uni API大量借鉴微信小程序API的设计(如uni.navigateTo对应wx.navigateTouni.request对应wx.request),但做了跨端适配(支持多平台);微信小程序API仅适用于微信小程序端,uni API适用于所有端。

  • 调用各端原生API的方式

    通过 uni ** API调用**(推荐,跨端兼容):

    uni.request({
      url: 'https://api.example.com',
      method: 'GET',
      success: (res) => {console.log(res.data);}
    });
    

    条件编译调用原生API(仅特定端可用):

    // 微信小程序端调用wx API
    #ifdef MP-WEIXIN
    wx.getStorage({key: 'token', success: (res) => {}});
    #endif
    
    // App端调用plus API
    #ifdef APP-PLUS
    plus.screen.lockOrientation('portrait-primary');
    #endif
    

    通过原生插件调用:复杂原生API(如高德地图SDK)需通过原生插件封装后调用。

5. UniApp中网络请求的方式有哪些?(uni.request、axios适配、封装请求拦截器)如何处理跨域问题(H5端)?

答案

  • 网络请求方式:① uni.request(官方推荐,跨端兼容):

    uni.request({
      url: 'https://api.example.com/user',
      method: 'POST',
      data: {id: 1},
      header: {'Content-Type': 'application/json'},
      success: (res) => {console.log(res.data);},
      fail: (err) => {console.error(err);}
    });
    

    ② 封装请求拦截器(统一处理token、错误):

    // utils/request.js
    export default function request(options) {
      // 请求拦截:添加token
      options.header = options.header || {};
      const token = uni.getStorageSync('token');
      if (token) {
        options.header.Authorization = `Bearer ${token}`;
      }
      // 返回Promise
      return new Promise((resolve, reject) => {
        uni.request({
          ...options,
          success: (res) => {
            if (res.statusCode === 200) {
              resolve(res.data);
            } else {
              reject(res);
            }
          },
          fail: (err) => {
            reject(err);
          }
        });
      });
    }
    

    ③ Axios适配(仅H5端):需配置跨域代理,不推荐跨端使用。

  • H5端跨域问题处理:① 开发环境:在manifest.json中配置代理:

    "h5": {
      "devServer": {
        "proxy": {
          "/api": {
            "target": "https://api.example.com",
            "changeOrigin": true,
            "pathRewrite": {"^/api": ""}
          }
        }
      }
    }
    

    生产环境

    • 后端配置CORS(Access-Control-Allow-Origin: *);

    • 前端部署在与后端同域名下,或通过Nginx反向代理。

6. UniApp中如何实现文件上传/下载?(uni.uploadFile/uni.downloadFile)需要注意哪些问题?

答案

  • 文件上传( uni.uploadFile

    uni.chooseImage({
      count: 1,
      success: (res) => {
        const tempFilePath = res.tempFilePaths[0];
        uni.uploadFile({
          url: 'https://api.example.com/upload', // 上传接口
          filePath: tempFilePath,
          name: 'file', // 后端接收文件的字段名
          formData: {user: 'test'}, // 额外参数
          success: (uploadRes) => {
            console.log(uploadRes.data); // 上传结果
          },
          fail: (err) => {
            console.error(err);
          }
        });
      }
    });
    
  • 文件下载( uni.downloadFile

    uni.downloadFile({
      url: 'https://example.com/file.pdf', // 下载链接
      success: (res) => {
        if (res.statusCode === 200) {
          // 保存文件(App端)
          #ifdef APP-PLUS
          plus.io.resolveLocalFileSystemURL(res.tempFilePath, (entry) => {
            entry.copyTo(plus.io.PRIVATE_DOC, 'file.pdf', (newEntry) => {
              console.log('保存成功:', newEntry.fullPath);
            });
          });
          #endif
          // 打开文件(小程序端)
          #ifdef MP-WEIXIN
          uni.openDocument({
            filePath: res.tempFilePath,
            showMenu: true
          });
          #endif
        }
      }
    });
    
  • 注意问题

    ① 上传文件的name字段需与后端一致;

    ② 小程序端需配置upload/download域名白名单(在小程序管理后台);

    ③ App端需申请文件读写权限(manifest.json中配置);

    ④ 大文件上传建议分片上传(通过uni.uploadFile分块发送)。

7. UniApp的本地存储API(uni.setStorage/uni.getStorage)与浏览器的localStorage有什么区别?如何处理大容量数据存储?

答案

  • 核心区别
特性uni.setStorage/uni.getStorage浏览器localStorage
跨端支持支持所有端(App/小程序/H5)仅H5端支持
存储类型支持异步/同步(uni.setStorageSync仅同步
存储大小小程序端约10MB,App端无限制约5MB
数据格式自动序列化/反序列化(支持对象)仅支持字符串
  • 大容量数据存储方案

    App端:使用plus.io操作本地文件(如JSON文件存储):

    #ifdef APP-PLUS
    // 写入文件
    plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
      fs.root.getFile('bigData.json', {create: true}, (fileEntry) => {
        fileEntry.createWriter((writer) => {
          writer.write(JSON.stringify(bigData));
        });
      });
    });
    // 读取文件
    plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
      fs.root.getFile('bigData.json', {create: false}, (fileEntry) => {
        fileEntry.file((file) => {
          const reader = new FileReader();
          reader.onload = () => {
            const data = JSON.parse(reader.result);
          };
          reader.readAsText(file);
        });
      });
    });
    #endif
    

    小程序端:使用云开发数据库(如微信云开发)或分块存储(将大对象拆分为多个key存储);

    H5端:使用IndexedDB(支持大容量结构化数据存储)。

8. 如何在UniApp中使用地图、支付、分享等原生能力?(如调用微信支付、支付宝支付)

答案

  • 地图能力

    ① 使用UniApp内置<map>组件(跨端兼容):

    <map :latitude="39.9042" :longitude="116.4074" :markers="markers"></map>
    

    ② App端使用原生地图SDK(如高德/百度地图):通过插件市场插件或自定义原生插件调用。

  • 支付能力

    ① 微信小程序支付:

    #ifdef MP-WEIXIN
    uni.request({
      url: 'https://api.example.com/getWxPayParams', // 后端获取支付参数
      success: (res) => {
        wx.requestPayment({
          ...res.data,
          success: (payRes) => {console.log('支付成功');},
          fail: (err) => {console.error('支付失败');}
        });
      }
    });
    #endif
    

    ② App端支付宝支付:通过uni-app支付插件或plus.payment调用:

    #ifdef APP-PLUS
    plus.payment.request(channel, payInfo, (res) => {
      console.log('支付成功');
    }, (err) => {
      console.error('支付失败');
    });
    #endif
    
  • 分享能力

    ① 使用uni.share(跨端兼容):

    uni.share({
      provider: 'weixin',
      type: 0,
      title: '分享标题',
      imageUrl: 'https://example.com/img.png',
      success: (res) => {console.log('分享成功');}
    });
    

    ② 小程序端使用wx.showShareMenu自定义分享内容:

    #ifdef MP-WEIXIN
    wx.showShareMenu({
      withShareTicket: true,
      menus: ['shareAppMessage', 'shareTimeline']
    });
    

五、路由与页面跳转类

1. UniApp的路由配置文件是哪个?(pages.json)如何配置tabbar、页面路径、导航栏样式?

答案

  • 路由配置文件pages.json是UniApp的路由配置文件,负责页面路径、全局样式、tabbar等配置。

  • 配置页面路径

    {
      "pages": [
        {
          "path": "pages/index/index", // 页面路径(必填)
          "style": { // 页面级样式(可选,覆盖全局)
            "navigationBarTitleText": "首页",
            "enablePullDownRefresh": true
          }
        },
        {
          "path": "pages/detail/detail",
          "style": {
            "navigationBarTitleText": "详情页"
          }
        }
      ]
    }
    
  • 配置tabbar

    {
      "tabBar": {
        "color": "#666666", // 未选中文字颜色
        "selectedColor": "#ff5722", // 选中文字颜色
        "borderStyle": "black", // 边框样式
        "backgroundColor": "#ffffff", // 背景色
        "list": [
          {
            "pagePath": "pages/index/index", // tab页路径(必须在pages数组中)
            "text": "首页", // tab文字
            "iconPath": "static/tab/home.png", // 未选中图标
            "selectedIconPath": "static/tab/home-active.png" // 选中图标
          },
          {
            "pagePath": "pages/cart/cart",
            "text": "购物车",
            "iconPath": "static/tab/cart.png",
            "selectedIconPath": "static/tab/cart-active.png"
          },
          {
            "pagePath": "pages/my/my",
            "text": "我的",
            "iconPath": "static/tab/my.png",
            "selectedIconPath": "static/tab/my-active.png"
          }
        ]
      }
    }
    
  • 配置导航栏样式

    {
      "globalStyle": {
        "navigationBarBackgroundColor": "#ffffff", // 导航栏背景色
        "navigationBarTextStyle": "black", // 文字颜色(black/white)
        "navigationBarTitleText": "UniApp", // 全局标题
        "backgroundColor": "#f5f5f5", // 页面背景色
        "app-plus": {
          "titleNView": { // App端原生导航栏配置
            "backgroundColor": "#ffffff",
            "titleText": "UniApp",
            "titleColor": "#333333"
          }
        }
      }
    }
    

2. UniApp的页面跳转方式有哪些?(uni.navigateTo/uni.redirectTo/uni.switchTab/uni.reLaunch/uni.navigateBack)各自的使用场景和区别是什么?

答案

UniApp提供5种页面跳转方式,核心区别在于是否保留当前页面是否跳转到tabbar页

跳转方式保留当前页关闭当前页支持tabbar页适用场景
uni.navigateTo普通页面跳转(如列表→详情)
uni.redirectTo替换当前页(如登录→首页)
uni.switchTab跳转到tabbar页(如首页→我的)
uni.reLaunch✅(所有页)重启应用(如退出登录→首页)
uni.navigateBack-✅(当前页)返回上一页(如详情→列表)
  • 示例

    // 保留当前页,跳转到详情页(带参数)
    uni.navigateTo({
      url: '/pages/detail/detail?id=1&name=test'
    });
    
    // 关闭当前页,跳转到首页
    uni.redirectTo({
      url: '/pages/index/index'
    });
    
    // 跳转到tabbar的“我的”页面
    uni.switchTab({
      url: '/pages/my/my'
    });
    
    // 关闭所有页,跳转到首页
    uni.reLaunch({
      url: '/pages/index/index'
    });
    
    // 返回上一页(delta=2返回上两页)
    uni.navigateBack({
      delta: 1
    });
    

3. 页面跳转时如何传递参数?接收参数的方式有哪些?如果参数过大(如对象/数组),如何处理?

答案

  • 传递参数的方式

    URL拼接(适用于小参数,如字符串/数字):

    uni.navigateTo({
      url: `/pages/detail/detail?id=1&status=active&list=${JSON.stringify([1,2,3])}`
    });
    

    全局状态管理(Vuex/Pinia,适用于大参数):

    // 存储参数
    store.commit('setDetailData', bigData);
    // 跳转
    uni.navigateTo({url: '/pages/detail/detail'});
    

    本地存储uni.setStorageSync,适用于超大参数):

    // 存储参数
    uni.setStorageSync('detailData', bigData);
    // 跳转
    uni.navigateTo({url: '/pages/detail/detail'});
    
  • 接收参数的方式

    ① URL参数:在目标页onLoad中接收:

    onLoad(options) {
      const id = options.id; // 1
      const status = options.status; // 'active'
      const list = JSON.parse(options.list); // [1,2,3]
    }
    

    ② 全局状态管理:在目标页直接获取:

    onLoad() {
      const bigData = store.state.detailData;
    }
    

    ③ 本地存储:在目标页读取后删除(避免残留):

    onLoad() {
      const bigData = uni.getStorageSync('detailData');
      uni.removeStorageSync('detailData'); // 读取后删除
    }
    
  • 大参数处理建议:优先使用Vuex/Pinia(内存存储,效率高),超大参数(如图片二进制数据)使用本地文件存储(App端)或云存储(小程序端)。

4. 如何实现页面返回时刷新数据?(如通过onShow、事件总线、Vuex/Pinia)

答案

  • 方式1:利用 onShow 生命周期(简单场景):列表页在onShow中重新加载数据(返回时自动触发):

    // 列表页(pages/list/list.vue)
    onShow() {
      this.loadListData(); // 返回时重新加载列表
    }
    
  • 方式2:事件总线( uni.$on/$emit (精准刷新):

    // 列表页(监听事件)
    onLoad() {
      uni.$on('refreshList', () => {
        this.loadListData();
      });
    },
    onUnload() {
      uni.$off('refreshList'); // 解绑事件
    }
    
    // 详情页(触发事件)
    onUnload() {
      uni.$emit('refreshList'); // 返回前触发刷新
    }
    
  • 方式3:Vuex/Pinia状态同步(复杂场景):详情页修改数据后更新状态,列表页监听状态变化:

    // 详情页
    store.commit('updateItem', newData);
    
    // 列表页
    watch: {
      'store.state.listData': {
        handler() {
          this.loadListData();
        },
        deep: true
      }
    }
    

5. Tabbar页面和非Tabbar页面的跳转限制是什么?(如navigateTo无法跳转到tabbar页面)

答案

  • 核心限制

    uni.navigateTo/uni.redirectTo无法跳转到tabbar页面(会报错);

    ② 跳转到tabbar页面必须使用uni.switchTab(会关闭所有非tabbar页面);

    ③ tabbar页面之间跳转只能用uni.switchTab(不能用navigateTo)。

  • 示例

    // 错误:navigateTo跳转到tabbar页
    uni.navigateTo({url: '/pages/my/my'}); // 小程序端报错,App端无响应
    
    // 正确:switchTab跳转到tabbar页
    uni.switchTab({url: '/pages/my/my'});
    
  • 特殊需求处理(保留当前页跳转到tabbar页):先通过uni.navigateTo跳转到非tabbar的“我的”页面,再在该页面用uni.switchTab跳转到tabbar页(不推荐,易导致页面栈混乱)。

6. UniApp中路由拦截如何实现?(如登录验证、权限控制)

答案

UniApp通过uni.addInterceptor实现路由拦截(支持navigateTo/redirectTo/switchTab等所有跳转方式):

  • 登录验证拦截示例

    // main.js
    uni.addInterceptor('navigateTo', {
      // 跳转前拦截
      invoke(options) {
        // 需要登录的页面路径(如/pay、/my/order)
        const needLoginPages = ['/pages/pay/pay', '/pages/my/order/order'];
        const isNeedLogin = needLoginPages.some(page => options.url.includes(page));
        if (isNeedLogin) {
          const token = uni.getStorageSync('token');
          if (!token) {
            // 未登录,跳转到登录页
            uni.navigateTo({url: '/pages/login/login'});
            // 阻止原跳转
            return false;
          }
        }
      },
      // 跳转后回调
      success(options) {
        console.log('跳转成功:', options.url);
      }
    });
    
    // 拦截switchTab(如跳转到“我的”页需登录)
    uni.addInterceptor('switchTab', {
      invoke(options) {
        if (options.url.includes('/pages/my/my')) {
          const token = uni.getStorageSync('token');
          if (!token) {
            uni.navigateTo({url: '/pages/login/login'});
            return false;
          }
        }
      }
    });
    
  • 权限控制拦截示例

    uni.addInterceptor('navigateTo', {
      invoke(options) {
        if (options.url.includes('/admin')) {
          const userRole = uni.getStorageSync('userRole');
          if (userRole !== 'admin') {
            uni.showToast({title: '无权限访问', icon: 'none'});
            return false;
          }
        }
      }
    });
    

六、性能优化类

1. UniApp项目的性能优化有哪些常用方法?(分包加载、图片优化、数据懒加载、组件复用)

答案

UniApp性能优化需分端针对性处理,核心方法如下:

  • 分包加载:小程序端主包≤2M,将非核心页拆分为分包,按需加载(减少首屏加载时间);

  • 图片优化

    ① 使用image组件的lazy-load属性(懒加载);

    ② 采用webp格式(体积比jpg小30%);

    ③ 小程序端使用CDN加速,App端使用本地图片(常用图标);

  • 列表优化

    ① 长列表用recycle-view(回收复用组件,减少DOM节点);

    ② 分页加载(onReachBottom加载下一页),避免一次性渲染大量数据;

    v-forkey(避免DOM复用错误),且不与v-if同级使用;

  • 组件优化

    ① 高频组件全局注册(减少重复初始化);

    ② 复杂组件按需加载(import()动态导入);

  • 数据优化

    ① 减少setData次数(小程序端),合并数据更新;

    ② 避免在onShow中执行复杂计算(影响页面切换流畅度);

  • 启动优化

    App.vueonLaunch中延迟加载非必要数据(如统计、广告);

    ② App端使用启动图(splashscreen)掩盖加载过程;

  • 样式优化

    ① 减少样式层级(小程序端样式解析效率低);

    ② 避免使用!important(增加样式计算开销)。

2. 什么是UniApp的分包加载?如何配置主包和分包?分包的大小限制是什么(如微信小程序主包≤2M)?

答案

  • 分包加载:将小程序代码拆分为主包(包含首页、tabbar页、公共代码)和分包(其他页面),用户打开分包页面时才下载分包,减少首屏加载时间,突破小程序体积限制。

  • 配置方式pages.json):

    {
      "pages": [
        // 主包页面(首页、tabbar页)
        "pages/index/index",
        "pages/my/my"
      ],
      "subPackages": [
        {
          "root": "pages/sub1", // 分包1根目录
          "pages": [
            "detail/detail", // 分包页面路径:pages/sub1/detail/detail
            "list/list"
          ]
        },
        {
          "root": "pages/sub2", // 分包2根目录
          "pages": [
            "pay/pay",
            "order/order"
          ]
        }
      ],
      "preloadRule": {
        // 预加载规则:进入首页时预加载sub1分包
        "pages/index/index": {
          "network": "all", // 网络类型(all/wifi)
          "packages": ["pages/sub1"]
        }
      }
    }
    
  • 分包大小限制

    • 微信小程序:主包≤2M,所有包合计≤20M;

    • 支付宝小程序:主包≤2M,所有包合计≤16M;

    • 字节跳动小程序:主包≤2M,所有包合计≤12M。

3. 如何优化UniApp的启动速度?(如减少启动页耗时、预加载数据、优化首屏渲染)

答案

  • 减少启动页耗时

    ① 简化App.vueonLaunch逻辑(避免同步请求、复杂计算);

    ② App端配置启动图(manifest.json),掩盖加载过程:

    "app-plus": {
      "splashscreen": {
        "alwaysShowBeforeRender": true,
        "waiting": true,
        "autoclose": false, // 手动关闭启动图
        "bgColor": "#ffffff",
        "image": "static/splash.png"
      }
    }
    

    加载完成后手动关闭:

    // App.vue onLaunch
    #ifdef APP-PLUS
    setTimeout(() => {
      plus.navigator.closeSplashscreen();
    }, 1000);
    #endif
    
  • 预加载数据

    ① 首屏数据提前请求(如在onLaunch中请求首页数据,存储到Vuex);

    ② 分包预加载(preloadRule),提前下载常用分包。

  • 优化首屏渲染

    ① 首屏只渲染核心内容(如骨架屏),非核心内容延迟渲染;

    ② 减少首屏组件数量(隐藏非必要组件);

    ③ 小程序端开启“按需注入”(微信开发者工具→详情→本地设置)。

4. 列表渲染时(如v-for)如何优化性能?(如加key、虚拟列表、分段加载)

答案

  • 基础优化

    v-for必须加唯一key(避免DOM复用错误,推荐用id,不用index):

    <view v-for="item in list" :key="item.id">{{ item.name }}</view>
    

    ② 避免v-forv-if同级使用(会遍历所有元素后再判断,效率低):

    <!-- 错误 -->
    <view v-for="item in list" :key="item.id" v-if="item.show">...</view>
    <!-- 正确 -->
    <view v-for="item in filteredList" :key="item.id">...</view>
    
  • 分段加载:通过onReachBottom实现分页加载,每次加载10-20条数据:

    data() {
      return {
        list: [],
        pageNum: 1,
        pageSize: 10,
        hasMore: true
      };
    },
    onLoad() {
      this.loadData();
    },
    onReachBottom() {
      if (this.hasMore) {
        this.pageNum++;
        this.loadData();
      }
    },
    methods: {
      async loadData() {
        const res = await request({
          url: '/api/list',
          data: {pageNum: this.pageNum, pageSize: this.pageSize}
        });
        if (res.data.length < this.pageSize) {
          this.hasMore = false;
        }
        this.list = [...this.list, ...res.data];
      }
    }
    
  • 虚拟列表(长列表优化):使用recycle-view组件(UniApp官方推荐),仅渲染可视区域内的元素:

    <recycle-view class="list" :data="list" height="100%">
      <view slot-scope="props" class="item">
        {{ props.item.name }}
      </view>
    </recycle-view>
    

5. UniApp中图片优化的策略有哪些?(如懒加载、使用webp格式、图片压缩、CDN加速)

答案

  • 懒加载image组件添加lazy-load属性(小程序/App端默认支持,H5端需配置):

    <image :src="imgUrl" lazy-load mode="widthFix"></image>
    
  • 格式优化:优先使用webp格式(体积比jpg/png小30%-50%),小程序/App端自动兼容:

    <image :src="imgUrl.replace('.jpg', '.webp')" lazy-load></image>
    
  • 图片压缩

    ① 服务端压缩(如七牛云/阿里云OSS的图片处理接口,指定宽度/质量):

    <image src="https://example.com/img.jpg?imageView2/1/w/300/h/200/q/80"></image>
    

    ② 前端压缩(上传时压缩图片):

    uni.chooseImage({
      count: 1,
      sizeType: ['compressed'], // 压缩图片
      success: (res) => { /* 上传 */ }
    });
    
  • CDN加速:图片部署到CDN(如阿里云CDN、腾讯云CDN),减少图片加载时间。

  • 本地图片缓存:App端使用plus.cache缓存图片,避免重复下载:

    #ifdef APP-PLUS
    plus.cache.saveFile(imgUrl, (res) => {
      this.localImgUrl = res.target; // 本地缓存路径
    });
    #endif
    

6. 如何减少UniApp的内存占用?(如及时销毁定时器、取消事件监听、避免大数组渲染)

答案

  • 及时销毁定时器/订阅:在页面onUnload或组件unmounted中清除定时器、解绑事件:

    onLoad() {
      this.timer = setInterval(() => { /* 逻辑 */ }, 1000);
      uni.$on('event', this.handleEvent);
    },
    onUnload() {
      clearInterval(this.timer); // 销毁定时器
      uni.$off('event', this.handleEvent); // 解绑事件
    }
    
  • 避免大数组渲染

    ① 分页加载数据(如每次加载20条);

    ② 长列表使用虚拟列表(仅渲染可视区域);

    ③ 不用的数据及时置为null(释放内存):

    onUnload() {
      this.list = null; // 清空大数组
    }
    
  • 优化图片内存

    ① 小程序端避免同时渲染大量图片(限制≤50张);

    ② App端使用image组件的resize属性压缩图片尺寸。

  • 减少组件嵌套:组件嵌套深度≤5层(减少虚拟DOM树复杂度,降低内存占用)。

7. H5端的性能优化与小程序/App端的优化有什么差异?

答案

H5端、小程序端、App端的运行环境不同,性能优化侧重点差异显著:

优化维度H5端优化重点小程序端优化重点App端优化重点
渲染层优化DOM操作、CSS渲染、WebView性能减少setData次数、优化WXML渲染选择nvue原生渲染、优化原生组件交互
资源加载开启HTTP/2、gzip压缩、懒加载资源分包加载、配置CDN域名白名单

七、状态管理类

1. UniApp中如何实现全局状态管理?(Vuex/Pinia、全局变量、uni.$emit/$on

答案

UniApp支持多种全局状态管理方案,根据复杂度选择:

  • Vuex/Pinia(推荐,复杂应用)

    • Vuex

      ① 创建store/index.js

      import Vue from 'vue';
      import Vuex from 'vuex';
      Vue.use(Vuex);
      export default new Vuex.Store({
        state: { token: '', userInfo: {} },
        mutations: {
          SET_TOKEN(state, token) { state.token = token; },
          SET_USER(state, user) { state.userInfo = user; }
        },
        actions: {
          login({ commit }, data) {
            commit('SET_TOKEN', data.token);
            commit('SET_USER', data.user);
          }
        },
        getters: {
          isLogin: state => !!state.token
        }
      });
      

      ② 在main.js挂载:

      import store from './store';
      const app = new Vue({ store, ...App });
      

      ③ 组件中使用:

      this.$store.commit('SET_TOKEN', 'xxx');
      this.$store.dispatch('login', {token: 'xxx', user: {name: 'test'}});
      console.log(this.$store.getters.isLogin);
      
    • Pinia(Vue3推荐)

      ① 创建stores/user.js

      import { defineStore } from 'pinia';
      export const useUserStore = defineStore('user', {
        state: () => ({ token: '', userInfo: {} }),
        actions: {
          login(data) {
            this.token = data.token;
            this.userInfo = data.user;
          }
        },
        getters: {
          isLogin: state => !!state.token
        }
      });
      

      ② 组件中使用:

      import { useUserStore } from '@/stores/user';
      const userStore = useUserStore();
      userStore.login({token: 'xxx', user: {name: 'test'}});
      console.log(userStore.isLogin);
      
  • 全局变量(简单场景):在App.vue中定义全局变量,通过getApp().globalData访问:

    // App.vue
    onLaunch() {
      getApp().globalData = { token: '', userInfo: {} };
    }
    // 组件中使用
    const app = getApp();
    app.globalData.token = 'xxx';
    
  • uni.$emit/$on (临时状态传递):适合跨组件临时通信,不适合长期存储状态(页面卸载需解绑)。

2. Vuex/Pinia在UniApp中的使用与在Vue项目中有什么区别?如何处理多端兼容?

答案

  • 使用区别

    ① 核心语法一致(Vuex的state/mutations/actions、Pinia的defineStore),无本质差异;

    ② UniApp中需注意小程序端的异步限制:Vuex的actions中避免使用过多嵌套异步(可能导致性能问题);

    ③ Pinia在UniApp中需确保Vue版本兼容(Vue3项目直接使用,Vue2需安装pinia-plugin-persistedstate兼容)。

  • 多端兼容处理

    ① 状态持久化:

    • Vuex使用vuex-persistedstate插件,Pinia使用pinia-plugin-persistedstate,将状态保存到uni.setStorage

      // Pinia持久化配置
      import { createPersistedState } from 'pinia-plugin-persistedstate';
      const pinia = createPinia();
      pinia.use(createPersistedState({
        storage: {
          getItem: key => uni.getStorageSync(key),
          setItem: (key, value) => uni.setStorageSync(key, value),
          removeItem: key => uni.removeStorageSync(key)
        }
      }));
      

    ② 避免存储非序列化数据(如函数、DOM对象),否则小程序端会报错。

3. 如何实现状态的持久化存储?(结合uni.setStorage、Vuex插件)

答案

  • Vuex持久化:使用vuex-persistedstate插件,将状态同步到本地存储:

    // store/index.js
    import createPersistedState from 'vuex-persistedstate';
    const store = new Vuex.Store({
      // ...其他配置
      plugins: [
        createPersistedState({
          storage: {
            getItem: key => uni.getStorageSync(key),
            setItem: (key, value) => uni.setStorageSync(key, value),
            removeItem: key => uni.removeStorageSync(key)
          },
          paths: ['token', 'userInfo'] // 只持久化指定状态
        })
      ]
    });
    
  • Pinia持久化:使用pinia-plugin-persistedstate插件:

    // main.js(Vue3)
    import { createPinia } from 'pinia';
    import { createPersistedState } from 'pinia-plugin-persistedstate';
    const pinia = createPinia();
    pinia.use(createPersistedState({
      storage: {
        getItem: (key) => uni.getStorageSync(key),
        setItem: (key, value) => uni.setStorageSync(key, value),
      },
    }));
    app.use(pinia);
    
    // stores/user.js(直接配置persist)
    export const useUserStore = defineStore('user', {
      state: () => ({ token: '' }),
      persist: true // 自动持久化
    });
    
  • 手动持久化:在状态变更时手动调用uni.setStorage

    // Vuex mutation中
    SET_TOKEN(state, token) {
      state.token = token;
      uni.setStorageSync('token', token);
    }
    // 初始化时读取
    state: {
      token: uni.getStorageSync('token') || ''
    }
    

4. 全局状态和页面状态的边界是什么?如何避免状态污染?

答案

  • 边界划分

    全局状态:跨页面/组件共享的数据(如用户信息、token、全局配置);

    页面状态:仅当前页面使用的数据(如表单输入、分页参数、临时列表)。

  • 避免状态污染

    ① 全局状态仅存储必要数据,不存储页面临时数据;

    ② Vuex/Pinia使用模块化(Vuex的modules、Pinia的多store):

    // Vuex模块化
    const userModule = {
      namespaced: true, // 开启命名空间
      state: { token: '' },
      mutations: { SET_TOKEN(state, token) { state.token = token; } }
    };
    const store = new Vuex.Store({
      modules: { user: userModule, cart: cartModule }
    });
    // 调用时指定模块
    this.$store.commit('user/SET_TOKEN', 'xxx');
    

    ③ 页面卸载时清空临时状态:

    onUnload() {
      this.formData = {}; // 清空页面表单数据
    }
    

    ④ 避免直接修改全局状态(通过mutation/actions修改)。

八、UI框架与组件库类

1. UniApp常用的UI组件库有哪些?(uni-ui、uView、ColorUI)各自的特点和适用场景是什么?

答案

  • uni-ui(DCloud官方)

    • 特点:轻量、跨端兼容性最好(适配所有端)、与UniApp深度集成;

    • 组件数量:约30+常用组件(日历、表单、列表等);

    • 适用场景:追求轻量、跨端一致性的项目。

  • uView(uView UI,Vue2/Vue3)

    • 特点:组件丰富(100+)、样式美观、文档完善、支持主题定制;

    • 组件覆盖:表单、导航、媒体、交互等全品类;

    • 适用场景:中大型项目(需丰富组件支持)。

  • ColorUI

    • 特点:纯CSS组件库(无JS逻辑)、样式精美、体积小;

    • 适用场景:需自定义交互逻辑,仅需UI样式的项目。

  • 其他库

    • NutUI(京东):适配小程序,电商场景友好;

    • Vant Weapp(有赞):小程序原生组件库,UniApp可通过条件编译使用。

2. 如何在UniApp中引入和使用uView组件库?需要注意哪些配置?

答案

  • 安装uView(Vue3版)

    ① npm安装:

    npm install uview-plus
    

    ② 配置main.js

    import uviewPlus from 'uview-plus';
    app.use(uviewPlus);
    

    ③ 配置uni.scss(全局样式):

    @import 'uview-plus/theme.scss';
    

    ④ 配置pages.json(easycom自动引入):

    {
      "easycom": {
        "^u-(.*)": "uview-plus/components/u-$1/u-$1.vue"
      }
    }
    
  • 使用示例

    <template>
      <u-button type="primary" @click="handleClick">按钮</u-button>
      <u-form :model="form" :rules="rules" ref="formRef">
        <u-form-item label="用户名" prop="username">
          <u-input v-model="form.username"></u-input>
        </u-form-item>
      </u-form>
    </template>
    
  • 注意事项

    ① Vue2和Vue3版本需区分(uView 2.x对应Vue2,uView Plus对应Vue3);

    ② 小程序端需确保组件路径配置正确(easycom规则匹配);

    ③ 自定义主题需覆盖uni.scss中的变量(如$u-primary-color: #ff5722;)。

3. 如何自定义UniApp UI组件的样式?(如覆盖uView组件的默认样式、使用深度选择器)

答案

  • 覆盖全局样式:在uni.scss中修改UI库的全局变量(如uView的主题色):

    // uView全局变量覆盖
    $u-primary-color: #ff5722; // 主色
    $u-button-border-radius: 8px; // 按钮圆角
    
  • 局部样式覆盖(深度选择器):小程序/H5端使用::v-deep(Vue2)或:deep()(Vue3)穿透组件样式:

    /* Vue3 */
    :deep(.u-button__content) {
      font-size: 16px;
    }
    /* Vue2 */
    ::v-deep(.u-input__inner) {
      height: 44px;
    }
    
  • 自定义组件class:多数UI组件支持custom-class属性,直接添加自定义样式:

    <u-button custom-class="my-btn">按钮</u-button>
    <style>
    .my-btn {
      background: #007aff;
    }
    </style>
    

4. 如何开发UniApp的自定义组件库?(发布到npm、支持easycom自动引入)

答案

  • 开发步骤

    ① 创建组件目录结构:

    
    my-components/
    ├── components/
    │   ├── my-button/
    │   │   ├── my-button.vue
    │   │   └── index.js
    │   └── my-card/
    ├── index.js
    └── package.json
    

    ② 编写组件(my-button.vue):

    <template>
      <button class="my-button" @click="$emit('click')">{{ text }}</button>
    </template>
    <script>
    export default {
      name: 'MyButton',
      props: { text: String }
    }
    </script>
    

    ③ 配置index.js(批量导出):

    import MyButton from './components/my-button/my-button.vue';
    import MyCard from './components/my-card/my-card.vue';
    const components = [MyButton, MyCard];
    const install = (app) => {
      components.forEach(component => {
        app.component(component.name, component);
      });
    };
    export default { install };
    export { MyButton, MyCard };
    
  • 发布到npm

    ① 配置package.json

    {
      "name": "my-uniapp-components",
      "version": "1.0.0",
      "main": "index.js",
      "keywords": ["uniapp", "components"]
    }
    

    ② 登录npm并发布:

    npm login
    npm publish
    
  • 支持easycom自动引入:在用户项目的pages.json中配置:

    {
      "easycom": {
        "autoscan": true,
        "custom": {
          "^my-(.*)": "my-uniapp-components/components/my-$1/my-$1.vue"
        }
      }
    }
    

九、调试与发布类

1. UniApp的调试方式有哪些?(HBuilderX调试、小程序开发者工具调试、真机调试)

答案

  • HBuilderX调试

    ① 模拟器调试:HBuilderX内置模拟器(支持App/H5/小程序),点击“运行→运行到模拟器”;

    ② 控制台调试:HBuilderX底部“控制台”查看日志、网络请求、错误信息;

    ③ 断点调试:在代码行号处点击添加断点,运行后触发断点可查看变量、调用栈。

  • 小程序开发者工具调试

    ① 运行到小程序模拟器:HBuilderX中“运行→运行到小程序模拟器→微信开发者工具”;

    ② 打开微信开发者工具,导入生成的小程序项目,使用其调试工具(Console、Network、WXML);

    ③ 真机调试:微信开发者工具中“预览→真机调试”,扫码在手机上调试。

  • App真机调试

    ① 安卓真机:开启USB调试,连接电脑,HBuilderX中“运行→运行到手机或模拟器→选择设备”;

    ② iOS真机:需配置开发者证书,通过Xcode或HBuilderX的iOS真机运行功能;

    ③ 远程调试:App端通过uni.showDebugger()开启调试面板,或使用vconsole插件(H5/App端):

    #ifdef H5
    import VConsole from 'vconsole';
    new VConsole();
    #endif
    

2. 如何在UniApp中调试网络请求?(查看请求参数、响应数据、错误信息)

答案

  • HBuilderX网络调试:底部“控制台→网络”标签,可查看所有uni.request/uni.uploadFile的请求URL、参数、响应数据、状态码;

  • 小程序开发者工具调试:打开“Network”面板,筛选“Request”,查看请求详情(Headers、Response、Preview);

  • 封装请求日志:在请求拦截器中打印日志:

    function request(options) {
      console.log('请求参数:', options);
      return new Promise((resolve, reject) => {
        uni.request({
          ...options,
          success: (res) => {
            console.log('响应数据:', res);
            if (res.statusCode !== 200) {
              console.error('请求错误:', res);
              reject(res);
            } else {
              resolve(res.data);
            }
          },
          fail: (err) => {
            console.error('请求失败:', err);
            reject(err);
          }
        });
      });
    }
    

3. UniApp的发布流程是什么?(H5发布、小程序发布、App发布)

答案

  • H5发布

    ① HBuilderX中“发行→网站-H5手机版”;

    ② 配置发布参数(如域名、标题),选择输出目录,生成静态资源;

    ③ 将生成的文件上传到服务器(如Nginx、阿里云OSS)。

  • 小程序发布

    ① HBuilderX中“发行→小程序-微信”(或对应平台);

    ② 生成小程序代码包,打开微信开发者工具,导入项目;

    ③ 在微信开发者工具中“上传→填写版本号/描述→上传成功”;

    ④ 登录微信公众平台,在“版本管理”中提交审核,审核通过后发布。

  • App发布

    ① 云打包:HBuilderX中“发行→原生App-云打包”,配置应用名称、图标、权限,选择Android/iOS,生成安装包;

    ② 本地打包:

    • 安卓:生成Android Studio工程,编译为apk/aab包,上传到应用商店(华为、小米、应用宝);

    • iOS:生成Xcode工程,配置证书,打包为ipa文件,上传到App Store Connect审核发布;③ 应用商店审核:需提供应用截图、隐私政策、资质文件(如ICP备案、软著)。

4. 小程序发布时需要注意哪些审核规范?(如内容合规、功能合规、隐私政策)

答案

  • 内容合规

    ① 不得包含敏感内容(色情、暴力、政治);

    ② 不得使用夸大/虚假宣传词汇(“最佳”“国家级”);

    ③ 小程序名称、图标、简介需与内容一致,不得侵权。

  • 功能合规

    ① 不得包含违规功能(赌博、虚拟货币、医疗美容);

    ② 不得强制用户分享/关注才能使用功能;

    ③ 不得诱导用户下载App(小程序内禁止App推广)。

  • 隐私政策

    ① 收集用户数据(手机号、位置、相册)需提前告知,并提供隐私政策页面;

    ② 隐私政策需明确数据用途、存储期限、第三方共享情况;

    ③ 需获得用户授权(如弹窗同意隐私政策)。

  • 其他规范

    ① 小程序需有实际功能(不得为空壳);

    ② 性能达标(首屏加载≤5秒,无明显卡顿);

    ③ 电商类小程序需提供ICP备案、食品经营许可证(如卖食品)。

5. App打包时如何配置权限、图标、启动图?(manifest.json配置)

答案

  • 权限配置:在manifest.json的“App权限配置”中勾选所需权限(如相机、定位、存储),或手动编辑:

    "app-plus": {
      "android": {
        "permissions": [
          "<uses-permission android:name="android.permission.CAMERA"/>",
          "<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>"
        ]
      },
      "ios": {
        "plist": {
          "NSCameraUsageDescription": "需要相机权限用于拍照",
          "NSLocationWhenInUseUsageDescription": "需要位置权限用于定位"
        }
      }
    }
    
  • 图标配置:在manifest.json的“App图标配置”中上传图标(或放置到static/logo目录),支持不同分辨率:

    "app-plus": {
      "icons": {
        "android": {
          "mdpi": "static/logo/android/mdpi.png",
          "hdpi": "static/logo/android/hdpi.png"
        },
        "ios": {
          "appstore": "static/logo/ios/appstore.png",
          "iphone": "static/logo/ios/iphone.png"
        }
      }
    }
    
  • 启动图配置:在manifest.json的“App启动图配置”中上传启动图:

    "app-plus": {
      "splashscreen": {
        "image": "static/splash.png",
        "android": {
          "hdpi": "static/splash/android/hdpi.png"
        },
        "ios": {
          "iphone": "static/splash/ios/iphone.png"
        }
      }
    }
    

6. 如何处理UniApp的版本更新?(小程序自动更新、App热更新/整包更新)

答案

  • 小程序版本更新:微信小程序会自动检测更新(用户下次打开时触发),或手动检测:

    // app.vue onLaunch
    #ifdef MP-WEIXIN
    const updateManager = wx.getUpdateManager();
    updateManager.onCheckForUpdate((res) => {
      if (res.hasUpdate) {
        updateManager.onUpdateReady(() => {
          wx.showModal({
            title: '更新提示',
            content: '新版本已准备好,是否重启?',
            success: (res) => {
              if (res.confirm) updateManager.applyUpdate();
            }
          });
        });
      }
    });
    #endif
    
  • App热更新(wgt包更新):无需应用商店审核,直接更新代码:

    // 检测更新
    uni.request({
      url: 'https://api.example.com/version',
      success: (res) => {
        const serverVersion = res.data.version;
        const localVersion = plus.runtime.version;
        if (serverVersion > localVersion) {
          // 下载wgt包
          uni.downloadFile({
            url: res.data.wgtUrl,
            success: (downloadRes) => {
              // 安装更新
              plus.runtime.install(downloadRes.tempFilePath, {force: true}, () => {
                plus.runtime.restart();
              });
            }
          });
        }
      }
    });
    
  • App整包更新:版本差异较大时,引导用户下载新安装包:

    wx.showModal({
      title: '版本更新',
      content: '发现新版本,请前往应用商店下载',
      showCancel: false,
      success: () => {
        plus.runtime.openURL('https://app.example.com/download');
      }
    });
    

十、兼容性与适配类

1. UniApp如何适配不同尺寸的屏幕?(rpx、flex、媒体查询)

答案

  • rpx单位(推荐):rpx是UniApp的自适应单位,1rpx = 屏幕宽度/750(设计稿以750px宽度为准),自动适配不同屏幕:

    .box {
      width: 375rpx; /* 屏幕宽度的一半 */
      height: 100rpx;
      font-size: 28rpx;
    }
    
  • flex布局:弹性布局适配不同屏幕,如均分容器:

    <view class="flex-container">
      <view class="item">1</view>
      <view class="item">2</view>
      <view class="item">3</view>
    </view>
    <style>
    .flex-container {
      display: flex;
      width: 100%;
    }
    .item {
      flex: 1; /* 均分宽度 */
      height: 100rpx;
    }
    </style>
    
  • 媒体查询:针对不同屏幕宽度定制样式:

    /* 小屏手机 */
    @media (max-width: 375px) {
      .title { font-size: 24rpx; }
    }
    /* 大屏手机 */
    @media (min-width: 414px) {
      .title { font-size: 32rpx; }
    }
    
  • 动态计算:通过uni.getSystemInfoSync()获取屏幕信息:

    onLoad() {
      const sysInfo = uni.getSystemInfoSync();
      this.screenWidth = sysInfo.screenWidth;
      this.screenHeight = sysInfo.screenHeight;
      this.statusBarHeight = sysInfo.statusBarHeight; // 状态栏高度
    }
    

2. iOS和Android端的兼容性差异有哪些?如何处理?

答案

  • 常见差异

    状态栏高度:iOS刘海屏状态栏高度44px,Android通常24-48px;

    键盘行为:iOS键盘弹出时挤压页面,Android可能覆盖输入框;

    样式渲染:iOS支持-webkit-appearance,Android对部分CSS属性支持差异;

    权限申请:iOS需在plist中声明权限描述,Android在AndroidManifest.xml中声明。

  • 处理方案:① 动态计算状态栏高度:

    const sysInfo = uni.getSystemInfoSync();
    this.navBarHeight = sysInfo.statusBarHeight + (sysInfo.platform === 'ios' ? 44 : 48);
    

    ② 监听键盘高度调整布局:

    uni.onKeyboardHeightChange((res) => {
      this.inputBottom = res.height; // 输入框底部间距
    });
    

    ③ 条件编译适配样式:

    #ifdef APP-IOS
    .button { border-radius: 8px; }
    #endif
    #ifdef APP-ANDROID
    .button { border-radius: 4px; }
    #endif
    

3. 小程序端的兼容性限制有哪些?(如API限制、样式限制、性能限制)

答案

  • API限制

    ① 小程序不支持window/document对象(H5端可用);

    ② 部分uni API在小程序端有差异(如uni.share需配置分享权限);

    ③ 网络请求需配置域名白名单(小程序管理后台)。

  • 样式限制

    ① 不支持*选择器、:after/:before伪元素(部分小程序支持);

    ② 行内样式不支持!important

    ③ 背景图片需使用网络地址或base64(本地图片需转base64)。

  • 性能限制

    ① 主包体积≤2M,所有包合计≤20M(微信小程序);

    ② 页面节点数≤1000,WXML层级≤30层;

    setData单次数据≤1024KB,调用频率≤20次/秒。

  • 处理方案

    ① 条件编译屏蔽小程序不支持的API:

    #ifdef H5
    document.title = '标题';
    #endif
    

    ② 分包加载减少主包体积;③ 优化WXML结构,减少节点层级。

4. H5端的兼容性问题有哪些?(如浏览器兼容、跨域、路由模式)

答案

  • 浏览器兼容

    ① 低版本浏览器(如IE)不支持ES6+语法,需配置babel转译:

    // babel.config.js
    module.exports = {
      presets: [['@babel/preset-env', { targets: { chrome: '58', ios: '10' } }]]
    };
    

    ② 部分CSS属性(如flex-wrap)需加前缀:

    .box {
      display: -webkit-flex;
      display: flex;
      -webkit-flex-wrap: wrap;
      flex-wrap: wrap;
    }
    
  • 跨域问题:开发环境配置代理(manifest.json),生产环境后端配置CORS:

    "h5": {
      "devServer": {
        "proxy": {
          "/api": {
            "target": "https://api.example.com",
            "changeOrigin": true,
            "pathRewrite": {"^/api": ""}
          }
        }
      }
    }
    
  • 路由模式:H5端支持hashhistory模式,history模式需后端配置重定向:

    "h5": {
      "router": {
        "mode": "history",
        "base": "/app/"
      }
    }
    

十一、实战场景类

1. 如何实现UniApp的登录功能?(账号密码登录、微信登录、Token存储)

答案

  • 账号密码登录

    // 登录请求
    async login() {
      const res = await request({
        url: '/api/login',
        method: 'POST',
        data: { username: this.username, password: this.password }
      });
      // 存储Token和用户信息
      uni.setStorageSync('token', res.token);
      this.$store.commit('SET_TOKEN', res.token);
      this.$store.commit('SET_USER', res.user);
      // 跳转到首页
      uni.switchTab({ url: '/pages/index/index' });
    }
    
  • 微信登录(小程序端)

    async wxLogin() {
      // 获取微信code
      const wxRes = await uni.login({ provider: 'weixin' });
      // 后端换取Token
      const res = await request({
        url: '/api/wxlogin',
        method: 'POST',
        data: { code: wxRes.code }
      });
      // 存储状态
      uni.setStorageSync('token', res.token);
      uni.switchTab({ url: '/pages/index/index' });
    }
    
  • 登录状态校验:在App.vueonLaunch中校验Token:

    onLaunch() {
      const token = uni.getStorageSync('token');
      if (!token) {
        uni.navigateTo({ url: '/pages/login/login' });
      }
    }
    

2. 如何实现下拉刷新和上拉加载更多?

答案

  • 下拉刷新

    ① 配置pages.json开启下拉刷新:

    {
      "pages": [
        {
          "path": "pages/list/list",
          "style": { "enablePullDownRefresh": true }
        }
      ]
    }
    

    ② 页面中实现逻辑:

    onPullDownRefresh() {
      this.pageNum = 1;
      this.list = [];
      this.loadData().then(() => {
        uni.stopPullDownRefresh(); // 停止刷新动画
      });
    }
    
  • 上拉加载更多

    ① 配置触底距离:

    {
      "pages": [
        {
          "path": "pages/list/list",
          "style": { "onReachBottomDistance": 50 }
        }
      ]
    }
    

    ② 页面中实现逻辑:

    data() {
      return { list: [], pageNum: 1, hasMore: true, isLoading: false };
    },
    onReachBottom() {
      if (!this.isLoading && this.hasMore) {
        this.isLoading = true;
        this.pageNum++;
        this.loadData().finally(() => {
          this.isLoading = false;
        });
      }
    },
    async loadData() {
      const res = await request({
        url: '/api/list',
        data: { pageNum: this.pageNum }
      });
      if (res.data.length === 0) {
        this.hasMore = false;
        return;
      }
      this.list = [...this.list, ...res.data];
    }
    

3. 如何实现图片预览、上传、裁剪功能?

答案

  • 图片预览

    previewImage(current) {
      uni.previewImage({
        current: current, // 当前显示图片的链接
        urls: this.imageList // 所有图片链接列表
      });
    }
    
  • 图片上传

    chooseImage() {
      uni.chooseImage({
        count: 3, // 最多选择3张
        sizeType: ['compressed'], // 压缩图片
        sourceType: ['album', 'camera'], // 相册/相机
        success: (res) => {
          // 上传图片
          res.tempFilePaths.forEach((path) => {
            uni.uploadFile({
              url: '/api/upload',
              filePath: path,
              name: 'file',
              success: (uploadRes) => {
                this.uploadedImages.push(JSON.parse(uploadRes.data).url);
              }
            });
          });
        }
      });
    }
    
  • 图片裁剪(App端):使用uni.chooseImage结合第三方裁剪插件(如uni-image-cropper):

    <uni-image-cropper ref="cropper" :src="tempImage" @confirm="onCropConfirm"></uni-image-cropper>
    <script>
    chooseAndCrop() {
      uni.chooseImage({
        success: (res) => {
          this.tempImage = res.tempFilePaths[0];
          this.showCropper = true; // 显示裁剪组件
        }
      });
    },
    onCropConfirm(e) {
      // e.path为裁剪后的图片路径
      uni.uploadFile({
        url: '/api/upload',
        filePath: e.path,
        name: 'file'
      });
    }
    </script>
    

4. 如何实现小程序的分享功能?(好友分享、朋友圈分享)

答案

  • 页面内分享按钮

    <button open-type="share" @click="onShare">分享</button>
    
  • 自定义分享内容

    onShareAppMessage(res) {
      if (res.from === 'button') {
        // 按钮触发的分享
        return {
          title: '自定义标题',
          path: '/pages/detail/detail?id=1',
          imageUrl: 'https://example.com/share.jpg'
        };
      }
      // 右上角菜单触发的分享
      return {
        title: '默认标题',
        path: '/pages/index/index'
      };
    },
    // 朋友圈分享(仅微信小程序)
    onShareTimeline() {
      return {
        title: '朋友圈分享标题',
        imageUrl: 'https://example.com/share.jpg'
      };
    }
    
  • 主动触发分享

    share() {
      uni.share({
        provider: 'weixin',
        type: 0, // 0=好友,1=朋友圈
        title: '分享标题',
        imageUrl: 'https://example.com/share.jpg',
        success: () => {
          uni.showToast({ title: '分享成功' });
        }
      });
    }
    

总结

UniApp作为跨端开发框架,核心优势是“一套代码多端运行”,但需掌握其生命周期、跨端适配、性能优化等关键点:

  1. 跨端原理:通过编译器将Vue代码编译为各端原生代码,运行时通过uni API适配差异;

  2. 性能优化:分包加载、虚拟列表、图片懒加载是提升体验的核心手段;

  3. 状态管理:复杂项目优先使用Vuex/Pinia,结合本地存储实现持久化;

  4. 调试发布:分端调试(模拟器/真机/开发者工具),严格遵循各平台审核规范。

掌握以上内容,可高效开发稳定、高性能的UniApp应用。