Taro 开发微信小程序踩坑汇总
1. 项目背景及疑问
在做微信小程序前,已经上线了一个 h5 项目,h5 中采用的 UI 框架为
Vant
,Vue 版本为 3.x。为了保持用户体验,就采用了Vant Weapp
,但 Taro 中的 vue 版本选择了 2.x 版本。
为什么用
Taro
,不用uni-app
?答:下次就用 uni-app。
都用 Taro 了,为什么
jsx
不搞起来?答:Taro 主打 react 技术栈,但是 Vant Weapp 是主打 vue 的,我担心在 Taro + Vant Weapp 里面用 jsx,掉进坑里,爬不出来,所以采用了保险点的方式,vue 版本也选择了 2.x 版本,即
Taro + vue2.x + vant-weapp
。Taro 里使用 Vant-Weapp,即使用使用原生第三方组件库开发,不再具有多端转换的能力,那为什么不直接用微信小程序原生开发?
答:项目只要求微信小程序可以使用,不做多端,所以可以放心使用,还有就是 h5 项目里的一些逻辑可以复用。
2. 项目前的准备
2.1 安装及初始化项目
2.1.1 Taro 安装 + 初始化项目
注意: node 环境(>=12.0.0)
安装
yarn global add @tarojs/cli 复制代码
初始化
taro init taro_demo 复制代码
安装依赖
# 切换目录 cd taro_demo # 安装依赖 yarn install 复制代码
修改根目录
package.json
里的scripts
下的dev:weapp
开发启动命令
{ "scripts" : { - "dev:weapp": "taro build --type weapp" + # watch 同时开启压缩 + "dev:weapp": "set NODE_ENV=production && taro build --type weapp --watch" } } 复制代码
注意:关于这里将NODE_ENV
直接设置为production
,项目的配置就直接使用了生产配置,即config/prod.js
配置,本地开发,配置一些开发设置,难免不方便,我们可以安装cross-env
再配置一个变量,这样就可以解决了。
yarn add cross-env -D 复制代码
{ "scripts" : { - "build:weapp": "taro build --type weapp", + "build:weapp": "cross-env DEV_MODE_ENV=false taro build --type weapp", - "dev:weapp": "set NODE_ENV=production && taro build --type weapp --watch" + "dev:weapp": "cross-env NODE_ENV=production DEV_MODE_ENV=true taro build --type weapp --watch", } } 复制代码
这样我们就可以根据自己配置的DEV_MODE_ENV
变量来区分是生产还是开发。但这里有一个问题,process.env.DEV_MODE_ENV
这个变量在src
下是访问不到的,config
目录可以正常访问到。要想在 src 下访问自定义 env 变量,需要在config/*.js
里的env
里进行设置,如:
// config/dev.js module.exports = { env: { NODE_ENV: '"development"', // 添加SERVICES_BASE_URL变量 SERVICES_BASE_URL: '"http://xxx"', }, }; // config/prod.js module.exports = { env: { NODE_ENV: '"production"', // 这里添加的 DEV_MODE_ENV 就起作用了,进行生产、开发区分,不用每次手动去改了 SERVICES_BASE_URL: process.env.DEV_MODE_ENV === "true" ? "http://xxx" : "https://xxx", }, }; // 代码里访问`process.env.SERVICES_BASE_URL`是可以正常获取到的 复制代码
启动开发命令,成功后,即可看到根目录有新生成的
dist
目录
yarn run dev:weapp 复制代码
2.1.2 微信开发者工具
下载微信开发者工具
选择刚刚的
dist
进行打开,不出意外,可以正常预览了。
2.2 项目中主要 package version
后面出现的问题,都是基于这些版本。
package | version |
---|---|
Taro | 3.3.7 |
vant weapp | 1.9.2 |
echarts | 5.1.2 |
3. Taro 中使用 Vant Weapp
3.1 修改postcss
配置,不转换 vant 的样式
// config/index.js const config = { mini: { enableSourceMap: false, // 用于控制是否生成 js、css 对应的 sourceMap // http://taro-docs.jd.com/taro/docs/vant postcss: { pxtransform: { enable: true, config: { selectorBlackList: [/van-/], }, }, url: { enable: true, config: { limit: 1024, }, }, cssModules: { enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true config: { namingPattern: "module", // 转换模式,取值为 global/module generateScopedName: "[name]__[local]___[hash:base64:5]", }, }, }, }, }; 复制代码
3.2 配置小程序原生文件和引用 vant 的组件
3.2.1 根据文档,使用 vant 需要这样做
// config/index.js const config = { // ... copy: { patterns: [ // 前面3个是基础的 { from: "src/components/vant-weapp/dist/wxs", to: "dist/components/vant-weapp/dist/wxs", }, { from: "src/components/vant-weapp/dist/common/style", to: "dist/components/vant-weapp/dist/common/style", }, { from: "src/components/vant-weapp/dist/common/index.wxss", to: "dist/components/vant-weapp/dist/common/index.wxss", }, // 需要用到vant哪个组件就引入,比如引入button { from: `src/components/vant-weapp/dist/button/index.wxs`, to: `dist/components/vant-weapp/dist/button/index.wxs`, }, { from: "src/components/vant-weapp/dist/button/index.wxml", to: "dist/components/vant-weapp/dist/button/index.wxml", }, ], options: {}, }, }; 复制代码
// 页面的 index.config.js 中引入或者全局 src/app.config.js中的引入 export default { navigationBarTitleText: "首页", usingComponents: { "van-button": "../../components/vant-weapp/dist/button/index", }, }; 复制代码
发现没,这不是一堆重复动作吗,需要用到 vant 组件,就必须要到两个地方配置一下,很难受。
3.2.2 升级改造,优化引入步骤
根目录创建
vantComponentConf.js
文件这样只需每次修改 vantComponentConf 文件,组件引入就生效了,不用重复改动,当然有能力可以直接使用 babel,写一个 loader 解决
注意:
每次添加新的 vant 组件,项目还是得重启,不能像
vite
那样潇洒,改配置文件也能马上自动重启生效部分 vant 组件,引入重启后还是提示缺少其它组件,那就再把缺少的组件添加到 vantComponentConf 里去,如 button 组件就依赖 icon 和 loading,单独引入 button 是不行的。
const vantComponetNames = ["button", "icon", "loading"]; module.exports = vantComponetNames; 复制代码
修改
config/index.js
文件
+ const path = require("path"); + const fs = require("fs"); + const vantComponetNames = require("../vantComponentConf"); + + const handleGetPatterns = (keyArr) => { + // 这些为公共组件 + const defaultArr = [ + { + from: "src/components/vant-weapp/dist/wxs", + to: "dist/components/vant-weapp/dist/wxs", + }, + { + from: "src/components/vant-weapp/dist/common/style", + to: "dist/components/vant-weapp/dist/common/style", + }, + { + from: "src/components/vant-weapp/dist/common/index.wxss", + to: "dist/components/vant-weapp/dist/common/index.wxss", + }, + ]; + + const componentArr = keyArr + .map((componentName) => { + // 同步检测这个文件是否存在 + // 因为vant weapp部分组件文件下的不存在 wxs 文件 + const componentPath = path.resolve( + __dirname, + `../src/components/vant-weapp/dist/${componentName}/index.wxs` + ); + + const isState = fs.statSync(componentPath, { throwIfNoEntry: false }); + + return [ + { + from: `src/components/vant-weapp/dist/${componentName}/index.${ + isState ? "wxs" : "wxss" + }`, + to: `dist/components/vant-weapp/dist/${componentName}/index.${ + isState ? "wxs" : "wxss" + }`, + }, + { + from: + "src/components/vant-weapp/dist/" + componentName + "/index.wxml", + to: + "dist/components/vant-weapp/dist/" + componentName + "/index.wxml", + }, + ]; + }) + .flat(Infinity); + + return [...defaultArr, ...componentArr]; + }; const config = { copy: { + // http://taro-docs.jd.com/taro/docs/vant + patterns: handleGetPatterns(vantComponetNames), options: {}, }, } 复制代码
修改
src/app.config.js
文件
+ const vantComponetNames = require("../vantComponentConf"); + + const getVantComponetConf = (arr) => { + const result = {}; + arr.forEach((key) => { + result[`van-${key}`] = `./components/vant-weapp/dist/${key}/index`; + }); + + return result; + }; export default { usingComponents: { + ...getVantComponetConf(vantComponetNames), }, } 复制代码
4. 关于使用 Vant 遇到的一些问题
4.1 部分 vant 组件的插槽不能正确显示
如下search
组件不能正确显示 slot 内容
<view> <van-search use-action-slot="true" placeholder="请输入门店名称搜索..."> <view slot="action"> 搜索 </view> </van-search> </view> 复制代码
解决:
必须使用 taro 提供的slot-view
组件,React 中使用Slot
组件
<template> <view> <van-search use-action-slot="true" placeholder="请输入门店名称搜索..."> <slot-view name="action"> <text>搜索</text> </slot-view> </van-search> </view> </template> 复制代码
slot 里的内容正确显示了
4.2 vant-search
获取不到输入的内容
<template> <view> <van-search :value="searchVal" use-action-slot="true" > <slot-view name="action"> <text @tap.stop="onSearch">搜索</text> </slot-view> </van-search> </view> </template> <script> export default { data() { return { searchVal: "", } }, methods: { onSearch() { // 这里并不会打印出实时的输入值 console.log("this.searchVal", this.searchVal); } }, } </script> 复制代码
解决:
vant-search
并不会自己同步更新数据,需要手动添加change
事件更新
<template> <view> <van-search :value="searchVal" use-action-slot="true" + @change="onChange" > <slot-view name="action"> <text @tap.stop="onSearch">搜索</text> </slot-view> </van-search> </view> </template> <script> export default { data() { return { searchVal: "", } }, methods: { + onChange(e) { + this.searchVal = e.detail; + }, onSearch() { console.log("this.searchVal", this.searchVal); } }, } </script> 复制代码
4.3 使用van-field
组件时,vue 中的 computed 的值不能触发响应更新
计算属性computed
里定义的 submitBtnDisabled,里面依赖的值改变了,但是没触发响应更新。不用怀疑,肯定不是 vue 的问题
<template> <view class="phoneLoginWrap"> <form @submit="handleSubmit"> <van-cell-group> <van-field v-model="user" :required="true" name="user" label="账号:" placeholder="请输入账号" title-width="4em" /> <van-field v-model="password" :required="true" name="password" type="password" label="密码:" placeholder="请输入密码" /> </van-cell-group> <van-button type="info" form-type="submit" :block="true" :disabled="submitBtnDisabled">登录</van-button> </form> </view> </template> <script> export default { data() { return { user: "", password: "" } }, computed: { // 只有首次进入页面时触发,后面不被触发 // 导致登录按钮永远是disabeld状态 submitBtnDisabled: function () { return this.user !== '' && this.password !== '' ? false : true; } }, methods: { handleSubmit(e) { // vant-button 即使disabled为true,依然可以进入这里 // UI样式生效了,但事件依然可以被触发 // 所以这里要加判断 if (this.submitBtnDisabled) return; console.log("login!!!", e) } } } </script> 复制代码
解决:
其实跟上面的vant-search
一个毛病,需要自己手动给vant-field
加上change
事件,这样在改变输入框的值的时候,按钮的 diabled 属性可以动态更新
<template> <view> <form @submit="handleSubmit"> <van-cell-group> <van-field v-model="user" :required="true" name="user" label="账号:" placeholder="请输入账号" + @change="(...args) => handleFieldChange.call(this, 'user', ...args)" /> <van-field v-model="password" :required="true" name="password" type="password" label="密码:" placeholder="请输入密码" + @change="(...args) => handleFieldChange.call(this, 'password', ...args)" /> </van-cell-group> <van-button type="info" form-type="submit" :block="true" :disabled="submitBtnDisabled">登录</van-button> </form> </view> </template> <script> export default { name: 'PhoneNumLogin', data() { return { user: "", password: "" } }, computed: { submitBtnDisabled: function () { return this.user !== '' && this.password !== '' ? false : true; } }, methods: { + handleFieldChange(type, e) { + const val = e.detail.trim(); + // 这里手动更新data的值 + if (type === 'user') { + this.user = val; + } else { + this.password = val; + } }, handleSubmit(e) { if (this.submitBtnDisabled) return; console.log("login!!!", e) } } } </script> 复制代码
4.4 vant 组件使用的时候控制台大片警告
原因: 微信基础库修改了校验逻辑,所以才出现 slot 没找到或者 properties 类型不对等警告信息
5. 关于使用 ec-canvas
echarts 遇到的一些问题
5.1 echarts 体积过大问题
解决:
echarts 在线定制下载
一定要在线选择压缩,下载的文件放在 ec-canvas/echarts.js,然后一定要重命名为echarts.js
5.2 ec-canvas 报错 t.addEventListener is not a function
TypeError: t.addEventListener is not a function 复制代码
解决:
在 wx-canvas.js 文件中添加:
// wx-canvas.js export default class WxCanvas { constructor(ctx, canvasId, isNew, canvasNode) { // ... } + // 新增空函数,修复调用 echarts.init 时报错 + addEventListener() {} } 复制代码
5.3 ec-canvas 报错 this.Chart.init is not a function
TypeError: this.Chart.init is not a function 复制代码
解决:
Taro 3.0 以上需要用 selectComponent 获取子组件实例
import Taro, { getCurrentInstance } from "@tarojs/taro"; function initChart(el = "#canvasId") { const currentIns = getCurrentInstance(); currentIns.page.selectComponent(el).init((canvas, width, height) => { const sysInfo = Taro.getSystemInfoSync(); const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: sysInfo.devicePixelRatio || sysInfo.pixelRatio, // 像素 }); canvas.setChart(chart); var option = { // ... }; chart.setOption(option); return chart; }); } 复制代码
5.4 ec-canvas 接口请求数据后后异步渲染或 socket 获取数据实时动态渲染
<template> <view style="widh: 100%; height: 300px;"> <ec-canvas id="yearLineChart" canvas-id="month-line-chart" :ec="yearChart" /> </view> </template> <script> import Taro from '@tarojs/taro'; import * as echarts from '@/src/components/echarts-for-weixin-master/ec-canvas/echarts'; function setOption(chart) { var option = { // ... }; chart.setOption(option); } export default { data() { return { yearChart: { // 将 lazyLoad 设为 true 后,需要手动初始化图表 lazyLoad: true } } }, methods: { // 初始化echarts initChart(el, data) { const currentIns = Taro.getCurrentInstance(); currentIns.page.selectComponent(el) .init((canvas, width, height) => { const sysInfo = Taro.getSystemInfoSync(); const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: sysInfo.devicePixelRatio || sysInfo.pixelRatio // 像素 }); setOption(chart, data); // 注意这里一定要返回 chart 实例,否则会影响事件处理等 return chart; }); }, // ajax后台获取数据 getData() { // .... // 假设data从后台返回 const data = {} this.initChart('year', '#yearLineChart', data); } }, onShow() { this.getData(); } } </script> 复制代码
5.5 导入 echart 包后提示 it exceeds the max of 500KB
命令行警告,导致每次ctrl + s
保存代码,微信开发者工具那边更新缓慢
[BABEL] Note: The code generator has deoptimised the styling of "unknown" as it exceeds the max of "500KB" 复制代码
解决:
config/index.js 添加 exclude 排除项
module.exports = { // ... mini: { // ... compile: { exclude: [ path.resolve( __dirname, "../src/components/echarts-for-weixin-master/ec-canvas/echarts.js" ), ], }, }, }; 复制代码
在 babel 配置文件中添加
compact: false
,默认为 auto
module.exports = { presets: [ [ "taro", { framework: "vue", ts: false, }, ], ], compact: false, }; 复制代码
5.6 使用 ec-canvas 后,小程序桌面端打开一片显示空白
造成空白的原因,其实是已经报错了
有说是给ec-canvas
标签添加force-use-old-canvas="true"
属性为 true 后,既可以。但是我这里 echarts 用的是5.1.2
版本,实测没生效。
解决:
windows 终端小程序不支持 canvas2d,修改为在 windows 终端采用旧版 canvas
最新版本有无修复桌面端打开是否成功,我这里没有验证。我直接对着这个 PR 修改源码了,实测有效。
// 修改echarts-for-weixin-master/ec-canvas/ec-canvas.js Component({ methods: { - init: function (callback) { + init: async function (callback) { const version = wx.getSystemInfoSync().SDKVersion; const canUseNewCanvas = compareVersion(version, "2.9.0") >= 0; const forceUseOldCanvas = this.data.forceUseOldCanvas; - const isUseNewCanvas = canUseNewCanvas && !forceUseOldCanvas; + // const isUseNewCanvas = canUseNewCanvas && !forceUseOldCanvas; + // 修复桌面端微信打开,echarts一片空白,控制台报错 + const { platform } = await wx.getSystemInfo(); + let isUseNewCanvas = canUseNewCanvas && !forceUseOldCanvas; + // 直接通过正则判断,如果是windows桌面端微信打开 + // 将isUseNewCanvas的值设为false + if (platform && /windows/i.test(platform)) { + isUseNewCanvas = false; + } this.setData({ isUseNewCanvas }); // ... } } }) 复制代码
6. 关于 Taro 的一些问题
6.1 项目体积过大,使用微信开发者工具无法预览,要进行分包处理
注意: 配置在tabBar
中的页面不能分包写到subpackages
中去
分包前:
// src/app.config.js export default { pages: ["pages/foo1/index", "pages/foo2/index", "pages/foo3/index"], }; 复制代码
分包后:
// src/app.config.js export default { pages: ["pages/foo1/index", "pages/foo2/index"], subpackages: [ { root: "pages/foo3", pages: ["index"], }, ], }; 复制代码
6.2 Taro.eventCenter 全局消息的问题
6.2.1 页面路由跳转 Taro.eventCenter 全局消息首次监听不生效
问题描述
1.路由 A 页面点击确认按钮后 发送全局消息事件 Taro.eventCenter.trigger('getInfo', data)
2.然后跳转到路由 B 页面 B 页面在 componentWillMount 页面被加载的时候 监听 Taro.eventCenter.on('getInfo', e => { }) 监听不到该事件
解决:
手动在父页面手动延迟 trigger(不够优雅)
使用 vuex
6.2.2 Tabbar 之间的通信,Taro.eventCenter 通信首次不触发
原因:跟上面一样,事件已经被触发,但是后面页面还没被初始化,不支持历史消息订阅
解决:
手动在 tab foo1 页面 setTimeout 手动延迟 trigger,然后 tab foo2 就可以监听到了(不优雅)
Taro.switchTab({ url: "/pages/foo1/index", complete: () => { // 这样在foo2页面的onShow里就可以监听到了 setTimeout(() => { Taro.eventCenter.trigger(eventTypes.SENT_CUS_ID, 10086); }, 800); }, }); 复制代码
不使用原生
tab
,自己另造一个,就摆脱了这个局限使用 vuex(打包增加体积)
既然都 vue 了,
eventBus
挺好
import Vue from "vue"; import * as types from "./eventTypes"; // 特殊的eventBus // https://segmentfault.com/a/1190000012808179 const bus = new Vue({ data() { return { // 定义数据 cus_id: null, }; }, created() { // 绑定监听 this.$on(types.CHANGE_CUS_ID, (id) => { console.log("CHANGE_SHOP_ID==>"); this.cusId = id; }); }, }); export default bus; export { types }; 复制代码
vue 中的其他通信方式...
6.3 sitemap 索引情况提示
控制台警告
[sitemap 索引情况提示] 根据 sitemap 的规则[0],当前页面 [pages/overview/index] 将被索引 复制代码
解决:
修改小程序项目配置文件 project.config.json
的 setting 中配置字段 checkSiteMap 为 false
{ "setting": { "checkSiteMap": false } } 复制代码
6.4 生产打包去除 console
安装
注意:
最新版本的 terser 使用会报错
TypeError: Cannot read property 'javascript' of undefined 复制代码
降级安装 4.x 版本
yarn add terser-webpack-plugin@4.2.3 -D 复制代码
config/prod.js
配置文件修改
module.exports = { mini: { // http://taro-docs.jd.com/taro/docs/config-detail#miniwebpackchain webpackChain(chain, webpack) { chain.plugin("terser").use(TerserPlugin, [ { // https://www.npmjs.com/package/terser-webpack-plugin#terseroptionsco terserOptions: { mangle: true, compress: { // https://swc.rs/docs/configuration/minification drop_console: true, drop_debugger: true, }, output: { // https://drylint.com/Webpack/terser-webpack-plugin.html#%E5%AE%89%E8%A3%85 ascii_only: true, // 转义字符串和正则中的 Unicode 字符 }, }, }, ]); }, }, }; 复制代码
6.5 Taro.onSocketError 返回的错误信息“未完成的操作”,拿不到真实的错误
wx.onSocketError 提示未完成操作是什么意思?
未解决
6.6 微信小程序体验版报错 request fail
安卓上报错信息,换ios可以看到具体报错信息
request:fail -2:net::ERR_FAILED 复制代码
解决:
排查服务器域名配置,严格按照这个规范进行配置
检查证书配置,nginx 配中间证书
6.7 微信小程序新版本更新检测,自动更新
export default { onReady() { if (!Taro.canIUse("getUpdateManager")) { Taro.showModal({ title: "提示", content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试", showCancel: false, }); return; } // https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html const updateManager = Taro.getUpdateManager(); updateManager.onCheckForUpdate((res) => { // 请求完新版本信息的回调 if (res.hasUpdate) { updateManager.onUpdateReady(function () { Taro.showModal({ title: "更新提示", content: "新版本已经准备好,是否重启应用?", success: function (res) { if (res.confirm) { // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate(); } }, }); }); updateManager.onUpdateFailed(function () { // 新版本下载失败 Taro.showModal({ title: "更新提示", content: "新版本已上线,请删除当前小程序,重新搜索打开", }); }); } }); }, }; 复制代码
6.8 Taro 中无法使用 css 变量问题
解决:
如果是 vue3,就有现成的解决方案,vue3 单文件组件样式特性
// 通过在顶层元素设置 style 来解决 import './style.css'; const style = { '--primary-color': 'red' } return <View style={style} className='container'>container elemet</View> /* styles.css */ .container { color: var(--primary-color); } 复制代码
6.9 关于 Taro 使用原生 HTML 标签
伪原创工具 SEO网站优化 https://www.237it.com/
其实还是建议使用小程序原生标签,不推荐 html 标签,怕掉进坑。
安装
yarn add @tarojs/plugin-html 复制代码
配置插件
// config/index.js config = { // ... plugins: ['@tarojs/plugin-html'] } 复制代码
使用
<template> <div>Hello World!</div> </template>
作者:sRect
链接:https://juejin.cn/post/7035105329956257806