Taro3.5.7 开发微信小程序经验&踩坑

1,149 阅读7分钟

1、初始化构建项目

安装CLI 工具

npm install -g @tarojs/cli@3.5.7

初始化项目

taro init my-app-preact

image.png

安装Taro-ui

yarn add taro-ui@3.1.0-beta.4

2、加入格式校验工具

2.1 eslint + prettier

yarn add eslint -D
yarn add eslint-config-taro eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-prettier -D

typescript 相关的插件

yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser -D

创建eslintrc.js和eslintignore

// eslintrc.js 
module.exports = {
  extends: ["taro/react"],
  plugins: ["prettier", "react", "react-hooks"],
  globals: {  // 微信小程序原生写法时,不错误提示
    wx: true,
    getApp: true,
    Component: true,
  },

  rules: {
    "prettier/prettier": "warn",

    "react/jsx-uses-react": "off",

    "react/react-in-jsx-scope": "off",

    "no-unused-vars": ["error", { varsIgnorePattern: "Taro" }],

    "react/sort-comp": "off",

    "jsx-quotes": ["error", "prefer-double"],

    "react/jsx-filename-extension": [
      1,
      { extensions: [".js", ".jsx", ".tsx"] },
    ],

    "arrow-parens": ["error", "as-needed"], 

    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", // 生产环境中不允许使用debugger

    "no-console": "off",

    "react/jsx-no-target-blank": "off",

    "jsx-a11y/anchor-has-content": "off",

    "react-hooks/rules-of-hooks": "error",

    "react-hooks/exhaustive-deps": "warn",

    "jsx-a11y/anchor-is-valid": "off",

    "import/no-commonjs": "off",

    "react/no-string-refs": "off",
    
    'no-undef': 'off', // 这个不关会报错'definePageConfig' is not defined

    'react-hooks/exhaustive-deps': 'off',

    'import/no-named-as-default':'off',

    'import/no-named-as-default-member':'off',

    'import/prefer-default-export': 'off',

    "react/jsx-indent-props": "off",
    
    "no-unused-vars": "off", // 未使用的变量

    'no-shadow':'warn', // 检查局部变量与全局变量重名

    'import/export':'off',
  },

  parser: "babel-eslint",
};

2.2 stylelint

yarn add stylelint -D

其他插件:

  • stylelint-config-standard 插件会处理 import语句,导致build 打包失败,不建议
  • stylelint-config-recess-order 检测标签顺序

//stylelintrc 
{ 
        "ignoreFiles": ["src/**/*.jsx"], 
        "rules": 
            { 
                "indentation": 4, 
                "number-leading-zero": "never", 
                "string-quotes": "double", 
                "declaration-block-trailing-semicolon": "always", 
                "length-zero-no-unit": true, 
                "at-rule-no-unknown": null, 
                "declaration-empty-line-before": [ "never", { "ignore": ["after-declaration"] } ], 
                "rule-empty-line-before": [ "always", { "except": ["after-single-line-comment", "first-nested"] } ], 
                "block-closing-brace-empty-line-before": ["never"], 
                "at-rule-empty-line-before": [ "always", { "ignore": ["inside-block", "blockless-after-same-name-blockless"] } ], 
                "max-empty-lines": 1, 
                "no-eol-whitespace": true, 
                "no-missing-end-of-source-newline": true, 
                "unit-case": null, 
                "color-hex-case": "upper", 
                "value-keyword-case": "lower", 
                "function-name-case": "lower", 
                "property-case": "lower", 
                "at-rule-name-case": "lower", 
                "selector-pseudo-class-case": "lower", 
                "selector-pseudo-element-case": "lower", 
                "selector-type-case": "lower", 
                "media-feature-name-case": "lower", 
                "block-opening-brace-space-before": "always", 
                "comment-whitespace-inside": "always", 
                "declaration-colon-space-after": "always", 
                "declaration-colon-space-before": "never", 
                "declaration-block-semicolon-space-before": "never", 
                "function-comma-space-after": "always", 
                "selector-combinator-space-before": "always", 
                "selector-combinator-space-after": "always", 
                "selector-list-comma-space-before": "never", 
                "selector-descendant-combinator-no-non-space": true, 
                "value-list-comma-space-after": "always", 
                "value-list-comma-space-before": "never", 
                "media-feature-colon-space-after": "always", 
                "media-feature-colon-space-before": "never", 
                "no-descending-specificity": null 
        } 
}

2.3 commit 检查

使用husky在代码提交之前,进行代码规则检查。

  • 添加插件:
yarn add husky -D
  • 在package.json中添加prepare脚本,并执行一次:
{ 
    "scripts": { 
            "prepare": "husky install" 
        } 
}

或者,直接使用命令行

npm pkg set scripts.prepare="husky install"
npm run prepare
  • 创建hook:
npx husky add .husky/pre-commit "npm test"

然后,你的项目根目录会有名为pre-commit的shell脚本,并且有一条npm test的命令

image.png

npm test 命令根据你自己项目中script脚本而定,例如,我的项目script脚本如下:

"scripts": {
    "build:weapp": "taro build --type weapp",
    "dev:weapp": "npm run build:weapp -- --watch",
    "format": "prettier --write \"./src/**/*.{js,jsx,json}\"",
    "eslint": "eslint --fix --ext .js,.jsx src",
    "stylelint": "stylelint \"./src/**/*.{css,less,scss}\" --fix",
    "prepare": "husky install"
  }

那我的npm test可以改为 npm run format && npm run eslint && npm run stylelint

最后,把.husky/pre-commit提交到代码库即可。之后每次执行 git commit 或者 git commit --amend时,都会先触发pre-commit hook,执行代码检查命令啦~

2.4 commitlint规范

yarn add @commitlint/config-conventional @commitlint/cli -D

添加 commitlint.config.js

const types = [ 
    'build', // 主要目的是修改项目构建系统(例如glup,webpack,rollup的配置等)的提交 
    'docs', // 文档提交(documents) 
    'feature', // 新增功能 
    'fix', // 修复 bug 
    'refactor', // 代码重构 
    'style', // 不影响程序逻辑的代码修改、主要是样式方面的优化、修改 
    'test', // 测试相关的开发, 
    ], typeEnum = { 
            rules: { 'type-enum': [2, 'always', types], }, 
            value: () => { return types; }, 
        }; 
        
module.exports = { 
    extends: ['@commitlint/config-conventional'], 
    rules: { 
        'type-enum': typeEnum.rules['type-enum'], 
        'subject-full-stop': [0, 'never'], 
        'subject-case': [0, 'never'], 
    }
 };
 

3、React 替换为Preact

如果你一开始习惯性的选择了react框架,那很快你会发现,react占用的空间太大了,项目超出2M的限制。这时你可以选择把 React 替换为Preact。

Preact 是一款体积超小的类 React 框架,提供和 React 几乎一致的 API,而体积只有 5k 左右。

1、将 CLI、项目中 Taro 相关的依赖更新到 v3.4.0-beta 版本以上

2、安装依赖

yarn add preact @tarojs/plugin-framework-react

3、修改 Taro 编译配置:

// config/index.js
const config = {  
    framework: 'preact'  
}

4、修改 Babel 配置:

// babel.config.js
module.exports = {
  presets: [
    ['taro', {
      framework: 'preact'
    }]
  ]
}

5、开发时我们可以使用任意的 React 生态库,甚至对 React、ReactDOM 的 API 引用也不需要修改,只需要简单地配置 alias

// config/index.js  
const config = {  
    "resolve": {  
        "alias": {  
            "react": "preact/compat",  
            "react-dom/test-utils": "preact/test-utils",  
            "react-dom": "preact/compat",  
            "react/jsx-runtime": "preact/jsx-runtime"  
         }
    }  
}

6、如果项目使用了 TypeScript,请打开 skipLibCheck 配置,以避免和其它 React 生态库配合使用时报类型错误:

//tsconfig.json
{
  ...
  "skipLibCheck": true,
}

4、全局变量

在 Taro 中推荐使用 Redux 来进行全局变量的管理,但是对于一些小型的应用, Redux 就可能显得比较重了,这里使用了官方推荐的globalData的方式,存储用户信息、字典信息等。

const globalData = {}  
  
export function setGlobalData (key, val) {  
    globalData[key] = val  
}  
  
export function getGlobalData (key) {  
    return globalData[key]  
}

在实际应用时发现,很多时候异步请求的接口还没有完成,页面已经渲染,接口请求完毕,页面数据却无法更新,这需要我们监听异步请求,进行页面重新载入,我选择了 Taro.eventCenter 解决。

// app.js

// 执行完异步请求以后,触发一个事件,可传入多个参数
Taro.eventCenter.trigger('CHANGE_USER_INFO', _newData)


//index.jsx
const Index = () => {
    let [userInfo, setUserInfo] = useState({})
    useDidShow(() => {
        Taro.eventCenter.on('CHANGE_USER_INFO', (_newData) => {
           setUserInfo(_newData)
        })
    })
    
    useDidHide(() => {
      // 取消监听
      Taro.eventCenter.off('CHANGE_USER_INFO')
    })
}

5、打包优化

目前小程序分包大小有以下限制:

  • 整个小程序所有分包大小不超过 20M
  • 单个分包/主包大小不能超过 2M

首先进行项目的代码分析,可以用微信开发者工具自带的分析工具,或者借助 webpack-bundle-analyzer 插件,可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项,文件是否重复,压缩后大小如何,我们可以做针对性优化。

我的优化思路如下:

  • 静态资源上cdn

  • 去除重复代码,提取公用代码和组件

  • 慎用插件

  • dayjs 替代 momentjs

  • react 更换为preact

  • 配置分包

  • 启动图方案 🙋‍♀️

    • 准备一个空主页面,一旦渲染,便立即跳转到其他分包页面。
    • 这样主包的体积就基本固定了,业务持续迭代也不会增加主包的体积。但是需要评估交互体验是否可以被接受。
    • 👉 但是要注意,由于空主页面没有引用任何内容,很可能会导致taro-ui被分别打包到每一个分包中,从而使整个包的体积增大一倍!这时,我们只需要在空主页随便引用一个taro-ui的组件,而不必真的使用,就可以让taro-ui仍然被打包到主包中啦~
  • dev环境执行build命令

6、遇到的一些问题

6.1 部分机型上setState报错

下面这段代码,在开发者工具里、大部分手机都是好的,特定的手机上查看才会报错。某报错机型:iPhone 13 Pro Max,微信版本v 8.0.30。1、2、3、4 步是排查过程。

import Taro from '@tarojs/taro'
import { useState } from 'react'
import { View } from '@tarojs/components'

function Demo(props) {
	const [flag, setFlag] = useState(false)

	const onSign = () => {
        
                  // setFlag(true)  4、在这里执行不会报错
                  
		Taro.scanCode({
                success: result => {
                
                    console.log('扫码成功!')
                    
                    setFlag(true)   // 3、在这里执行也会报错
                    
                    onSuccess() 
                },
                fail: () => {}
		})
	}
        
        const onSuccess = () => {
        
               setFlag(true)  // 2、拿到异步函数外面执行也会报错
               
               // 1、这里执行一个异步函数,在then方法里执行setFlag(true)会报错
        }

	return (
		<View>
			<View onClick={onSign}>点我签到</View>
			<View>{flag ? '签到成功' : '未签到'}</View>
		</View>
	)
}

export default Demo

报错如下: image.png

在我的实际业务中,这里的签到状态其实可以在后端接口中拿到,于是我尝试了执行刷新页面,但是仍然会报错!我又尝试了把签到状态放到GlobalData中,签到成功后执行changeGlobalData,然后通过eventCenter监听拿到修改后的状态,执行成功~

🤔️ 但是这个报错原因仍然未知,有思路的小伙伴可以留言 ~

6.2 执行npm run dev 时无报错且可以正常访问页面,执行npm run build时也无报错但是访问页面一片空白

毛估估是页面代码有误导致根本没有渲染,由于无任何报错可参考,只能一行行看代码,最后发现有个函数的const被误删了,加上以后再执行执行 npm run build 就可以正常访问页面了

import { View } from '@tarojs/components'

function Demo(props) { 

    const onCheck=()=>{}
    
    const onSubmit=()=>{
        // 这个函数误删了 const
    }
    
    return <View>12345</View>
}

这种问题该如何避免呢???