因此閒話少敘,咱們說回 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
。
以上就是這些,沒能在開發的同時作筆記致使可能也遺漏了很多點,爭取之後補上吧。寫這麼多一方面是總結,一方面也是分享。謝謝各位看到這裏。若是有任何地方說的不對,歡迎指正教導。我要學習的還有不少!