「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
本文是笔者实践工作经验总结的
Taro+React+TypeScript+Redux+自研UI库Tard+自研lint规范的模版仓库
文章速览:
一、工程目录
在开发中,首先第一步就是确定目录结构,一个好的目录结构往往让人神清气爽
文件目录的思想是预定大于配置
约定好哪个文件下放什么,在一个团队中
保持一致,打开一个接手的项目,一个工程目录一目了然,而不用去猜某个文件大概是干什么的 举几个例子
- 静态资源
static文件夹里面放不需要经过打包,比如说下载的第三方lib文件,而需要打包的资源放在asstes文件夹里面 - 常量统一都放在
constants文件夹下
具体的文件目录如下
taro-react-typescript-redux-template
├─ dist // 编辑结果目录
├─ config // taro编译配置目录
│ ├─ dev.js // 开发环境配置
│ ├─ index.js // 默认配置
│ └─ prod.js // 生产环境配置
├─ src // 源码目录
│ ├─ apis // 全局接口
│ ├─ assets // 需要打包的静态资源
│ │ └─ iconfont // 字体图标
│ │ └─ css // css
│ │ └─ img // 图片
│ ├─ components // 全局通用组件
│ ├─ constants // 全局常量
│ ├─ pages // 页面文件目录
│ │ └─ tab // tab-bar页面模块,后面小程序分包会讲到
│ │ │ ├─ apis // tab-bar页面下的接口
│ │ │ ├─ home // home页面
│ │ │ │ ├─ index.config.ts // 页面配置
│ │ │ │ ├─ index.less // 页面样式
│ │ │ └─ └─ index.tsx // 页面逻辑
│ │ ├─ user // 个人中心页面模块
│ │ │ ├─ apis // 个人中心下的接口
│ │ │ ├─ settings // 个人信息设置页
│ │ │ └─ address // 个人收获地址信息页
│ │ ├─ order // 订单页面模块
│ │ │ ├─ apis // 订单下的接口
│ │ │ ├─ list // 订单列表页
│ │ │ └─ detail // 订单详情页
│ ├─ types // ts类型文件
│ ├─ utils // 工具文件目录
│ │ ├─ cookie.ts // cookie模块
│ │ ├─ https.ts // 网络请求模块
│ │ └─ index.ts // 基础模块
│ ├─ app.config.ts // taro项目入口配置
│ ├─ app.less // 项目总样式
│ ├─ app.ts // 项目入口文件
│ ├─ index.html //
├─ .editorconfig
├─ .env // 开发环境环境变量
├─ .env.pre // 预发环境环境变量
├─ .env.prod // 生产环境环境变量
├─ .env.test // 测试环境环境变量
├─ .eslintrc.js // esLint规则配置
├─ .stylelintrc.js // styleLint规则配置
├─ .gitignore // git忽略文件
├─ babel.config.js // Babel配置
├─ tsconfig.json // ts配置文件
├─ global.d.ts
├─ iconfont.json // 图片处理配置文件
├─ package.json
├─ project.config.json
├─ project.tt.json
└─ tsconfig.json
二、代码规范
代码规范是必不可少的前置配置
使用ESLint+StyleLint+CommitLint,分别去约束js代码、css代码、git提交
模版仓库里面用的是自研的规范库,具体可参考之前写的文章如何在团队快速落地代码规范,当然你也可以直接换成你所在部门的使用的规范,直接修改相应的配置文件即可
1、ESLint
npm i eslint-config-selling -D
// .eslintrc.js
module.exports = {
"extends": ['selling/react']
}
2、StyleLint
npm i stylelint-config-selling -D
// .stylelintrc.js
module.exports = {
"extends": "stylelint-config-selling"
}
3、CommitLint
npm i commitlint-config-selling husky -D
// .commitlintrc.js
module.exports = {
"extends": "commitlint-config-selling"
}
// package.json
{
"lint-staged": {
"*.{js,jsx,json,ts,tsx,vue}": [
"eslint --fix",
"git add"
],
"src/**/*.less": [
"stylelint --config ./.stylelintrc --fix",
"git add"
]
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
如果用VScode,需要下载以下插件
- eslint
- stylelint
三、环境变量
在开发中,通常有多套环境去配置不同的环境变量
方案:将变量写在
.env.{NODE_ENV}文件中,利用 cross-env 注入环境标识,dotenv-flow 将.env.{NODE_ENV}文件加载到process.env,最后将process.env写入taro配置文件,环境变量统一用APP_开头
1、将变量写在 .env.{NODE_ENV} 文件
模版仓库里面,以有开发-dev、预发-pre、生产-prod、测试-test为例,分别对应文件.env、.env.pre、.env.prod、.env.test
2、利用 cross-env 注入环境标识
组合环境(dev/pre/prod/test) * 类型(dev/build) * 平台(小程序/H5) 比较多...但是比手动在命令行输入变量强
// package.json
"scripts": {
"build:weapp": "taro build --type weapp",
"build:h5": "taro build --type h5",
"dev:weapp": "npm run build:weapp -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"build-pre:weapp": "cross-env APP_ENV=pre taro build --type weapp",
"build-pre:h5": "cross-env APP_ENV=pre taro build --type h5",
"dev-pre:weapp": "cross-env APP_ENV=pre npm run build:weapp -- --watch",
"dev-pre:h5": "cross-env APP_ENV=pre npm run build:h5 -- --watch",
"build-test:weapp": "cross-env APP_ENV=test taro build --type weapp",
"build-test:h5": "cross-env APP_ENV=test taro build --type h5",
"dev-test:weapp": "cross-env APP_ENV=test npm run build:weapp -- --watch",
"dev-test:h5": "cross-env APP_ENV=test npm run build:h5 -- --watch",
"build-prod:weapp": "cross-env APP_ENV=prod taro build --type weapp",
"build-prod:h5": "cross-env APP_ENV=prod taro build --type h5",
"dev-prod:weapp": "cross-env APP_ENV=prod npm run build:weapp -- --watch",
"dev-prod:h5": "cross-env APP_ENV=prod npm run build:h5 -- --watch"
}
3、利用dotenv-flow 将 .env.{NODE_ENV} 文件加载到 process.env并写入taro配置文件
默认注入dev标识,起h5 dev服务执行yarn dev:h5即可
// config/dev.js | config/prod.js
process.env.APP_ENV = process.env.APP_ENV || 'dev'
require('dotenv-flow').config({
// node_dev表示.env.{node_env}文件
node_env: process.env.APP_ENV
})
module.exports = {
env: {
APP_VERSION: process.env.npm_package_version,
APP_ENV: process.env.APP_ENV,
APP_API: process.env.APP_API
}
};
四、图片处理
方案,小图标用
iconfont,借助 taro-iconfont-cli 这个包将图片处理成svg;大图标用CDN
1、小图标
(1)安装taro-iconfont-cli
npm i taro-iconfont-cli -D
(2)初始化配置文件
npx iconfont-init
可以看到根目录生成了配置文件iconfont.json,具体参数说明可查看taro-iconfont-cli官网
{
// 直接复制[iconfont](http://iconfont.cn/)官网提供的项目链接
"symbol_url": "http://at.alicdn.com/t/font_1373348_kk9y3jk2omq.js",
// 根据iconfont图标生成的组件存放的位置。每次生成组件之前,该文件夹都会被清空。
"save_dir": "./src/assets/iconfont",
// 如果您的项目使用Typescript编写,请设置为true。这个选项将决定生成的图标组件是`.tsx`还是`.js`后缀。
"use_typescript": true,
// 选择需要支持的平台,默认是`*`,意味着所有平台都需要支持(如果有)。如果你只想支持部分平台,也可以设置成数组:
"platforms": ["weapp", "h5"],
// 是否使用[尺寸单位rpx]还是普通的像素单位`px`。默认值为true,与Taro保持一致的缩放。您也可以设置为false,强制使用`px`
"use_rpx": true,
"trim_icon_prefix": "icon",
"default_icon_size": 18,
"design_width": 750
}
(3)生成Taro标准组件
执行yarn icon
// package.json
"scripts": {
"icon": "npx iconfont-taro"
}
(4)使用图标
//xx.tsx
import { resolve } from 'path';
<IconFont name='user' size={40} color="#333" />
图标多了,会出现图片体积增大的问题,如果你的项目体积非常紧张(小程序体积有要求),没有多余的体积给图标,可以考虑
字体图标,直接使用字体图标的话可能会对首屏时间有一点影响,如果关注这个的话,可以直接使用CDN
2、大图标
就比较简单了,将CDN前缀抽离为常量 CDN_IMG 写在 constants 文件夹下,使用时,拼接图片地址
// src/constants/index.ts
export const CDN_IMG = 'https://www.baidu.com/img/'
// xx.tsx
import { Image } from "@tarojs/components";
import { CDN_IMG } from "@/constants";
<Image src={`${CDN_IMG}PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png`} />
五、网络请求封装
方案:基于API Taro.request 封装,
H5携带cookie配置credentials=include,而小程序需要自己实现传递cookie,可实现类似H5cookie通用方案,微信小程序cookies方案
封装如下
import { request } from "@tarojs/taro";
// 请求头类型
const contentTypes = {
json: 'application/json; charset=utf-8',
urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
multipart: 'multipart/form-data',
}
// 默认参数
const defaultOptions = {
timeout: 3000,
credentials: "include", // 设置H5端携带Cookie
dataType: "json" // 返回的数据结构
}
interface OptionParams {
url: string,
method?: "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT",
data?: object,
options?: any,
contentType: 'json' | 'urlencoded' | 'multipart',
prefixUrl: string
}
const callApi = ({
url,
method = 'GET',
data,
options,
contentType = 'json',
prefixUrl = 'api'
}: OptionParams) => {
if (!url) {
const error = new Error('请传入url')
return Promise.reject(error)
}
const fullUrl = `/${prefixUrl}/${url}`
const newOptions = {
...defaultOptions,
header: {
'content-type':
(options.header && options.header['content-type']) ||
contentTypes[contentType],
'X-Requested-With': 'XMLHttpRequest',
},
...options,
data,
method
}
return request({
url: fullUrl,
...newOptions
})
.then((response) => {
const { data } = response
if (data.code === 'xxx') {
// 与服务端约定
// 登录校验失败
} else if (data.code === 'xxx') {
// 与服务端约定
// 无权限
} else if (data.code === 'xxx') {
// 与服务端约定
return Promise.resolve(data)
} else {
return Promise.reject(data)
}
})
.catch((error) => {
return Promise.reject(error)
})
}
export default callApi
六、样式适配
1、确定设计稿尺寸
- Taro 默认以
750px作为换算尺寸标准;如果设计搞的尺寸不是750,可通过designWidth配置 - 默认支持
750、640、828三种尺寸设计稿;如果需要支持自定义设计稿,可通过deviceRatio自定义;比如'375':2/1
方案:要求UI设计稿750px,无需更改config/index.js配置,直接使用
默认配置
// config/index.js
const config = {
...
// 设计稿尺寸
designWidth: 750,
// 设计稿尺寸换算规则
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2,
350: 2 / 1
},
...
}
2、外联样式写法
- 根据设计搞
1:1直接写px、或者%即可,Taro默认会对所有的单位进行转换;当转成微信小程序的时候,尺寸将默认转换为rpx,当转成H5时将默认转换为以rem为单位的值 - 不希望被转换的单位,在 px 单位中增加一个大写字母,例如
Px或者PX,还有一些其他方案,不过不常用,具体可见官网
- 配置
1px不转换,通过配置onePxTransform字段为false,如下
// config/index.js
const config = {
...
h5: {
publicPath: '/',
staticDirectory: 'static',
postcss: {
autoprefixer: {
enable: true
},
pxtransform: {
enable: true,
config: {
onePxTransform: false // H5配置 1px 不需要被转换
}
}
}
},
...
}
3、内联样式写法
在编译时,Taro 会帮你对样式做尺寸转换操作,但是如果是在 JS 中书写了行内样式,那么编译时就无法做替换了,针对这种情况,Taro 提供了 Taro.pxTransform 来做运行时的尺寸转换
七、样式隔离
方案,用
less,bem规范,每个样式文件的前缀确保唯一,为什么不用CSS module写法比较复杂,不符合常规开发习惯 代码示例
<View className="buyer-show-detail" >
{
showDetail?.mediaInfos?.length > 1 && isShowInfo &&
<View className="buyer-show-detail__num">
{currentId} / {imgList.length}
</View>
}
<Swiper className="buyer-show-detail__swiper">
{
imgList.map((img, index) => (
<SwiperItem className="buyer-show-detail__swiper-item" key={ index }>
<HsImage
className="buyer-show-detail__img" src={ img.picUrl }
style={ { height: `${showLoadList[index]?.height}` } }
mode="widthFix"
/>
</SwiperItem>
))
}
</Swiper>
{
isShowInfo && <View className="buyer-show-detail-bottom">
...
</View>
}
</View>
.buyer-show-detail {
height: 100vh;
overflow: hidden;
font-size: 24px;
color: #fff;
background: #222;
&__num {
position: fixed;
top: 0;
z-index: 99;
width: 100%;
padding-top: 24px;
font-size: 28px;
text-align: center;
}
&__swiper {
height: 100%;
&-item {
display: flex;
align-items: center;
}
}
&__img {
width: 100%;
}
&-bottom {
position: fixed;
bottom: 0;
display: flex;
width: 100%;
}
}
八、小程序分包
1、解释什么是分包
官方文档说明如下,简而言之就说分包是一个优化项,可以优化小程序首次启动的下载时间
2、怎么做分包
配置文件如下
export default {
// 主包
pages: [
"pages/tab/home/index", // 首页
"pages/tab/mine/index" // 我的
],
// 子包
subpakages: [ // 子包:user,个人中心
{
root: 'pages/user/',
pages: [
"address/index", // 个人地址信息页
"settings/index" // 个人信息设置页
]
},
{
root: 'pages/order/', // 子包:order,订单中心
pages: [
"list/index", // 订单列表
"detail/index" // 订单详情
]
}
],
window: {
backgroundTextStyle: "light",
navigationBarBackgroundColor: "#fff",
navigationBarTitleText: "WeChat",
navigationBarTextStyle: "black",
},
};
写在最后
本文所有的内容都总结为一个模版仓库,GitHub地址,感兴趣的可以点个star,有问题欢迎提issue,后续如果有更好的方案会持续更新~