因此闲话少叙,咱们说回 Taro……css
Taro 的开发体我的又比较喜欢 React,Taro 的社区活跃度和版本迭代速度可喜,因此毫无心外选择了 Taro。你有过 React 的开发经验,能够毫无困难地顺滑上手;若是没有,直接看 Taro 的 官方文档 来入门也是没有问题的。html
从安装到创建一个新项目使用——git
$ npm install -g @tarojs/cli
$ taro init myApp
$ cd myApp
$ npm install
# 开发
$ npm run dev:weapp
# 编译
$ npm run build:weapp
复制代码复制代码
这里的开发和编译指令中的
weapp
换成h5
/swan
/alipay
/tt
/rn
后,便可在对应的其余端编译运行。多端的代码逻辑能够不一样,详情请看 跨平台开发。github
官方有一篇基于 Redux 的项目最佳实践文章:《Taro深度开发实践》。npm
官方推荐的项目结构——json
├── config 配置目录
| ├── dev.js 开发时配置
| ├── index.js 默认配置
| └── prod.js 打包时配置
├── src 源码目录
| ├── components 公共组件目录
| ├── pages 页面文件目录
| | ├── index index 页面目录
| | | ├── banner 页面 index 私有组件
| | | ├── index.js index 页面逻辑
| | | └── index.css index 页面样式
| ├── utils 公共方法库
| ├── app.css 项目总通用样式
| └── app.js 项目入口文件
└── package.json
复制代码复制代码
我在项目中并无用到 Redux / MobX,项目「发展壮大」后的结构也比较简单明了——小程序
├── dist 编译结果目录
├── config 配置目录
| ├── dev.js 开发时配置
| ├── index.js 默认配置
| └── prod.js 打包时配置
├── src 源码目录
| ├── assets 公共资源目录(内含图标等资源)
| ├── components 公共组件目录
| | └── Btn 公共组件 Btn 目录
| | ├── Btn.js 公共组件 Btn 逻辑
| | └── Btn.scss 公共组件 Btn 样式
| ├── pages 页面文件目录
| | └── index index 页面目录
| | ├── components index 页面的独有组件目录
| | | └── Banner index 页面的 Banner 组件目录
| | | ├── Banner.js index 页面的 Banner 组件逻辑
| | | └── Banner.scss index 页面的 Banner 组件样式
| | ├── index.js index 页面逻辑
| | └── index.scss index 页面样式
| ├── subpackages 分包目录(项目过大时建议分包)
| | └── profile 一个叫 profile 的分包目录
| | └── pages 该分包的页面文件目录
| | └── index 该分包的页面 index 目录(其下结构与主包的页面文件一致)
| ├── utils 项目辅助类工具目录
| | └── api.js 好比辅助类 api 等
| ├── app.css 项目总通用样式
| └── app.js 项目入口文件
└── package.json
复制代码复制代码
什……这也叫「简单明了」吗? (゚д゚≡゚д゚)微信小程序
这是我我的比较喜欢的组织方式。个人项目已经不算小了,总计近 30 个页面,使用上面这种方式维护,确实感受还挺省心舒服的。固然你也能够按照你的喜爱组织文件~api
编译配置存放于项目根目录下 config
目录中,包含三个文件bash
index.js
是通用配置dev.js
是项目预览时的配置prod.js
是项目打包时的配置下面说一些使用案例和某些坑的解决方案——
在项目中不断 import
相对路径的后果就是,不能方便直观地明白目录结构;若是要迁移改动一个目录,IDE 又没有很准确的重构功能的话,须要手动更改每个 import
的路径,很是难受。
因此咱们想把:
import A from '../../componnets/A'
复制代码复制代码
变成
import A from '@/componnets/A'
复制代码复制代码
这种引用。
方式以下:
/* config/index.js */
const path = require('path')
alias: {
'@/components': path.resolve(__dirname, '..', 'src/components'),
'@/utils': path.resolve(__dirname, '..', 'src/utils'),
},
复制代码复制代码
/* config/dev.js */
env: {
NODE_ENV: '"development"', // JSON.stringify('development')
},
复制代码复制代码
/* config/prod.js */
env: {
NODE_ENV: '"production"', // JSON.stringify('development')
},
复制代码复制代码
代码中能够经过 process.env.NODE_ENV === 'development'
来判断环境。
/* config/dev.js */
defineConstants: {
BASE_URL: '"https://dev.com/api"',
},
复制代码复制代码
/* config/prod.js */
defineConstants: {
BASE_URL: '"https://prod.com/api"',
},
复制代码复制代码
如此一来,能够直接在代码中引用 BASE_URL
来基于环境获取不一样的 API Gateway。
若是你在开发中遇到了开发环境时样式没有问题,可是编译打包后出现部分样式丢失,多是由于 csso 的 restructure 特性。能够在 plugins.csso
中将其关闭:
/* config/prod.js */
plugins: {
csso: {
enable: true,
config: {
restructure: false,
},
},
},
复制代码复制代码
若是你遇到了编译时,压缩过的 js 文件过编译器报错,能够将其排除编译:
/* config/index.js */
weapp: {
compile: {
exclude: [
'src/utils/qqmap-wx-jssdk.js',
'src/components/third-party/wemark/remarkable.js',
],
},
},
复制代码复制代码
若是你遇到了编译后,资源文件(如图片)没有被编译到 dist
目录中致使找不到,能够令其直接被复制过来:
/* config/index.js */
copy: {
patterns: [
{
from: 'src/assets/',
to: 'dist/assets/',
},
],
},
复制代码复制代码
官方文档:nervjs.github.io/taro/docs/m…。
须要注意的是,若是这么作了,项目就不能再多端编译了。
使用方式看官方文档描述便可,十分简单。但我实际在使用的过程当中,仍是踩了坑的。好比我尝试集成 wemark 来作 markdown 的渲染。发现了两个问题:
wxss
中引用的 wxss
文件。解决方式是须要 copy
配置把全部文件在编译时所有拷贝过去。copy
配置。解决方式是须要 exclude
配置把压缩的 js 文件排除。(如上面提到的那样。)因此以 wemark 为例,项目集成原生组件,须要另加配置:
/* config/index.js */
copy: {
patterns: [
{
from: 'src/components/wemark',
to: 'dist/components/wemark',
},
],
},
weapp: {
compile: {
exclude: [
'src/components/wemark/remarkable.js',
],
},
},
复制代码复制代码
而后能够引用了——
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
export default class Comp extends Component {
config = {
usingComponents: {
wemark: '../components/wemark/wemark'
}
}
state = {
md: '# heading'
}
render() {
return (
<View>
<wemark md={this.state.md} link highlight type="wemark" />
</View>
)
}
}
复制代码复制代码
简而言之,若是你在集成原生组件中遇到了相似问题,能够试试直接 copy
整个组件目录,而且排除掉某些 js 文件防止过编译。
咱们但愿在项目中拥有本身的图标字体组件,使用方法以下:
<UIcon icon="home" />
复制代码复制代码
为何是
UIcon
,而不是Icon
?由于命名不能与官方自带的组件Icon
冲突呀……(|||゚д゚) 你也能够管他叫OhMyIcon
之类的。
这里先说实践,再说说坑……
实践就是,若是你们没有专业的设计师或者公司内部的图标库的话,可使用 Iconfont 的图表库,优势是图标多而优、CDN 开箱即用。你能够新建一个项目,选择适合你项目的图标后,直接获取到 @font-face
的引用代码:
Unicode 和 Font class 的引用效果几乎同样,后者的优点是 class name 的语义化,而因为咱们须要再进行一层包装,将 class name 变得可定制,因此推荐你们选择 Unicode。
而 Symbol 的优点就在于支持多色图标,那为何不用它呢……踩坑啦踩坑啦,微信小程序不兼容 svg 图标 QwQ。(在官方社区搜了不少帖子,官方只会说「好的,咱们看看」、「请贴一下代码片断」而后就没动静了……相似的状况还有不少,提了几年的 bug,始终不修,留着当传家宝……(/‵Д′)/~ ╧╧ )
那么咱们在组件的样式文件里加上上面这段 @font-face
的代码后,再写相似下面的这段:
/* UIcon.scss */
.u-icon {
display: inline-block;
&:before {
font-family: 'iconfont' !important;
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
text-align: center;
}
}
复制代码复制代码
而后针对每一个图标,给出它的 unicode 定义:
/* UIcon.scss */
.u-icon-add::before {
content: '\e6e0';
}
.u-icon-addition::before {
content: '\e6e1';
}
/* ... */
复制代码复制代码
UIcon.js
如此包装:
/* UIcon.js */
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import './UIcon.scss'
export default class UIcon extends Component {
static externalClasses = ['uclass']
static defaultProps = {
icon: '',
}
render() {
return <View className={`uclass u-icon u-icon-${this.props.icon}`} />
}
}
复制代码复制代码
这里注意到我加了一个 externalClasses
的配置,以及一个 uclass
的 className
。缘由是我想在组件外部定义内部的样式,如此定义后,能够这么调用:
<UIcon icon="home" uclass="external-class" />
复制代码复制代码
详情能够看文档 组件的外部样式和全局样式。(这篇文档就是我当时踩完这个坑帮忙给补上的 QwQ……)
若是你有主动发送小程序模板消息卡片的需求,可能须要这样的组件。
小程序目前的策略是你只能在用户触发一个 button
点击事件后,汇报给你一个一次性的 7 天过时 formId
,你用它来发送一次模板消息。因此涌现一批机智的小程序开发者,把 button
包裹在整个页面上,用户每一次点击都汇报一个 formId
,存起来以后,七天以内反正不愁啦,慢慢发。而官方貌似也一直睁一只眼闭一只眼…… (●` 艸 ´)
Taro 中实现这样的包裹器也很简单:
/* FormIdReporter.js */
import Taro, { Component } from '@tarojs/taro'
import { Button, Form } from '@tarojs/components'
import './FormIdReporter.scss'
export default class FormIdReporter extends Component {
handleSubmit = e => {
console.log(e.detail.formId) // 这里处理 formId
}
render() {
return (
<Form reportSubmit onSubmit={this.handleSubmit}>
<Button plain formType="submit" hoverClass="none">
{this.props.children}
</Button>
</Form>
)
}
}
复制代码复制代码
在调用时,把整个页面包裹上便可:
<FormIdReporter>
{/* 一些其余组件 */}
</FormIdReporter>
复制代码复制代码
须要注意的是,这里的 Button
须要使用下面的样式代码清除掉默认样式,达到「隐藏」的效果:
/* FormIdReporter.scss */
button {
width: 100%;
border-radius: 0;
padding: 0;
margin: 0;
&:after {
border: 0;
}
}
复制代码复制代码
因为这部份内容也是我从他处学到的,而且有既成的教程,我就再也不添油加醋啦。参考:
下面说的这些,更多的是关于小程序自身的一些实践案例了。固然,也是以 Taro 为背景的。
因为项目须要实现小程序文本国际化,我找了不少案例,最终参考了这个比较简洁的方案的思路:weapp-i18n。已经运用到两个项目中了。在 Taro 中,能够包装成下面这个类:
/* utils/i18n.js */
export default class T {
constructor(locales, locale) {
this.locales = locales
if (locale) {
this.locale = locale
}
}
setLocale(code) {
this.locale = code
}
_(line) {
const { locales, locale } = this
if (locale && locales[locale] && locales[locale][line]) {
line = locales[locale][line]
}
return line
}
}
复制代码复制代码
新建一个 locales.js
,写上你的本地化语言,key
名要和微信系统语言的叫法一致:
/* utils/locales.js */
locales.zh_CN = {
Discover: '发现',
Schools: '学校',
Me: '我',
'Courses of My Faculty': '个人院系课程',
'Popular Evaluations Monthly': '本月热门评测',
'Popular Evaluations': '热门评测',
'Recent Evaluations': '最新评测',
'Top Courses': '高分课程',
/* ... */
}
locales.zh_TW = {
...locales.zh_CN,
Discover: '發現',
Schools: '學校',
Me: '我',
'Courses of My Faculty': '个人院系課程',
'Popular Evaluations Monthly': '本月熱門評測',
'Popular Evaluations': '熱門評測',
'Recent Evaluations': '最新評測',
'Top Courses': '高分課程',
/* ... */
}
复制代码复制代码
使用方式是在 App.js
中先初始化:
/* App.js */
componentWillMount() {
this.initLocale()
}
initLocale = () => {
let locale = Taro.getStorageSync('locale')
if (!locale) { // 初始化语言
const systemInfo = await Taro.getSystemInfo()
locale = systemInfo.language // 默认使用系统语言
Taro.setStorage({ key: 'locale', data: locale })
}
Taro.T = new T(locales, locale) // 初始化本地化工具实例并注入 Taro.T
// 手动更改 TabBar 语言(目前只能这么作)
Taro.setTabBarItem({
index: 0,
text: Taro.T._('Discover'),
})
Taro.setTabBarItem({
index: 1,
text: Taro.T._('Me'),
})
}
复制代码复制代码
组件中使用:
<Button>{Taro.T._('Hello')}</Button>
复制代码复制代码
若是小程序提供了更改语言的功能,用户更改后,储存配置,而后直接 Taro.reLaunch
到首页,而且依次如上所述更改 TabBar 的语言便可。
确实挫了一点,不过在我看来,已是在小程序里实现国际化的最方即可行的办法啦……(*´ω`)人(´ω`*)
虽然 Taro 提供了 Taro.request
这个方法,但我仍是选择了 Fly.js 这个库,包装了一个本身的 request 方法:
/* utils/request.js */
import Taro from '@tarojs/taro'
import Fly from 'flyio/dist/npm/wx'
import config from '../config'
import helper from './helper'
const request = new Fly()
request.config.baseURL = BASE_URL
const newRquest = new Fly() // 这是用来 lock 时用的,详见后面
// 请求拦截器,我在这里的使用场景是:除了某些路由外,若是没有权限的用户「越界」了,就报错给予提示
request.interceptors.request.use(async conf => {
const { url, method } = conf
const allowedPostUrls = [
'/login',
'/users',
'/email',
]
const isExcept = allowedPostUrls.some(v => url.includes(v))
if (method !== 'GET' && !isExcept) {
try {
await helper.checkPermission() // 一个用来检测用户权限的方法
} catch (e) {
throw e
}
}
return conf
})
// 响应拦截器,我在这里的使用场景是:若是用户的 session 过时了,就锁定请求队列,完成从新登陆,而后继续请求队列
request.interceptors.response.use(
response => response,
async err => {
try {
if (err.status === 0) { // 网络问题
throw new Error(Taro.T._('Server not responding'))
}
const { status } = err.response
if (status === 401 || status === 403) { // 这两个状态码表示用户没有权限,须要从新登陆领 session
request.lock() // 锁定请求队列,避免重复请求
const { errMsg, code } = await Taro.login() // 从新登陆
if (code) {
const res = await newRquest.post('/login', { code }) // 使用新实例完成登陆
const { data } = res.data
const { sessionId, userInfo } = data
Taro.setStorageSync('sessionId', sessionId) // 储存新 session
if (userInfo) {
Taro.setStorageSync('userInfo', userInfo) // 更新用户信息
}
request.unlock() // 解锁请求队列
err.request.headers['Session-Id'] = sessionId // 在请求头加上新 session
return await request.request(err.request) // 从新完成请求
} else {
request.unlock()
throw new Error(Taro.T._('Unable to get login status'), errMsg)
return err
}
}
} catch (e) {
Taro.showToast({ title: e.message, icon: 'none' })
throw e
}
},
)
export default request
复制代码复制代码
你能够在这个的基础上,再包装一层 api.js
的 SDK。用起来很舒服~σ ゚∀ ゚) ゚∀゚)σ
第三方统计我目前用过两个,阿拉丁 和 TalkingData。两者进行对比后,发现大同小异,阿拉丁社区更活跃一些,而 TalkingData 提供了数据获取的 API。但使用中发现,TalkingData 并不能很好地兼容 Taro,在我反馈后,获得的回复是因为小程序第三方开发框架太多,因此没有支持的计划 (´c_`);阿拉丁虽然以前也有这样的问题,但在几个月前的版本中已经修复,并且提供了集成的参考文档。
因此若是有这方面需求的话,能够考虑看看阿拉丁~
最后,说一个统一全局样式的实践吧。很简单,好比建立一个 _scheme.scss
的文件:
/* utils/_scheme.js */
$f1: 80px; // 阿拉伯数字信息,如:金额、时间等
$f2: 40px; // 页面大标题,如:结果、控状态等信息单一页面
$f3: 36px; // 大按钮字体
$f4: 34px; // 首要层级信息,基准的,能够是连续的,如:列表标题、消息气泡
$f5: 28px; // 次要描述信息,服务于首要信息并与之关联,如:列表摘要
$f6: 26px; // 辅助信息,需弱化的内容,如:连接、小按钮
$f7: 22px; // 说明文本,如:版权信息等不须要用户关注的信息
$f8: 18px; // 十分小
$color-primary: #ff9800; // 品牌颜色
$color-secondary: lighten($color-primary, 10%);
$color-tertiary: lighten($color-primary, 20%);
$color-line: #ececec; // 分割线颜色
$color-bg: #ebebeb; // 背景色
$color-text-primary: #000000; // 主内容
$color-text-long: #353535; // 大段说明的主要内容
$color-text-secondary: #888888; // 次要内容
$color-text-placeholder: #b2b2b2; // 缺省值
$color-link-normal: #576b96; // 连接用色
$color-link-press: lighten($color-link-normal, 10%);
$color-link-disable: lighten($color-link-normal, 20%);
$color-complete-normal: $color-primary; // 完成用色
$color-complete-press: lighten($color-complete-normal, 10%);
$color-complete-disable: lighten($color-complete-normal, 20%);
$color-success-normal: #09bb07; // 成功用色
$color-success-press: lighten($color-success-normal, 10%);
$color-success-disable: lighten($color-success-normal, 20%);
$color-error-normal: #e64340; // 出错用色
$color-error-press: lighten($color-error-normal, 10%);
$color-error-disable: lighten($color-error-normal, 20%);
复制代码复制代码
以后在样式文件中引用这个配置文件,使用相应变量,而不使用绝对的数值便可。
对了,Taro 中的 px
其实指的就是 rpx
;若是你想要真实的 px
,能够大写 PX
。
以上就是这些,没能在开发的同时作笔记致使可能也遗漏了很多点,争取之后补上吧。写这么多一方面是总结,一方面也是分享。谢谢各位看到这里。若是有任何地方说的不对,欢迎指正教导。我要学习的还有不少!