前言
在Taro 0.x
的時候就寫過一個小玩意,雖然沒什麼人玩. 最近正好看到Taro 1.2.x
已經發布了
類React
風格的小程式框架,部分地方針對小程式做了妥協.找點東西試試水,看看改進如何了.
剛好公司有個需求做個手機端的舉報管理的程式, 開會上攬了過來;
對於這個框架,你除了需要有React
的基本功外, 還需要這兩方面的知識;
雖這個專案(僅微信端)不是很大,梳理下還是有挺多東東的,其他廢話不多說,直入主題
問題彙總
生命週期及JSX
的{}
不支援箭頭函式
用箭頭函式會出現不可控的結果;
最常見的就是報錯;
JSX
裡面僅支援onClick={this.xxxx.bind(this,args)
這種寫法- 生命週期用了會導致,
store
亦或者render
的結果異常(比如執行順序和值的錯誤) - 至於其他常規函式則支援箭頭函式的寫法
動態樣式
雖說Taro官方支援CSS Module
,若是你考慮多平臺的話..還是建議用常規的命名規劃來維護好一些
至於className
的動態css class
處理..我傾向於使用classnames
這個庫
classname: 最普通的用法如下
// 在Taro的用法跟在React的用法差不多..Taro上需要放在{} ,不然會直接報錯(就是= classnames物件這種寫法)
import classnames from 'classnames'
<View className={classnames({
"tab-operator": true,
"show": userIdList.length>0,
"hide": userIdList.length === 0
})}>
<View className="tab-content-top-counter">
<View className="left-text">{userIdList.length === 0 ?'如需操作條目請勾選下面條目!':`選中了${userIdList.length}條`}</View>
{userIdList.length === 0 ? null : <View className="unselect" onClick={this.unselect.bind(this)}>取消</View>}
</View>
複製程式碼
自己封裝的元件提示型別缺失的(TS)
比如你封裝的元件裡面依賴了Taro
封裝的一些元件,這時候暴露這個元件,
就會缺失你自己加進去的特性,導致編輯器會提示有錯誤資訊..
最簡便的就是用type
或者interface
,這樣就不會報錯了.比如下面
//方式一
type staticPropsSearchPanel={
open: boolean,
onClose?: () => void
}
// 也可以用interface ,與上面的區別,比較明顯的是這個可以繼承其他的
// 方式二
interface staticPropsSearchPanel {
open: boolean,
onClose?: () => void
}
class SearchPanel extends Component<staticPropsSearchPanel>{}
複製程式碼
元件支援程度
-
不支援函式式元件:具體看官方說法 截止
1.2.x
依舊不支援,只能寫成class xx extends Component
這種 -
不支援同個檔案內直接多個
class xx extends
且被引用
允許幾種狀態管理器的接入?
dva
,mobx
,redux
都有對應taro
接入方案,後兩者是taro
官方維護
是否支援alias
最新版是支援的(可用),在config
目錄暴露了配置檔案,當然很多其他webpack
的配置也有一些直接暴露
至於eslint
不識別alias
符號的,這個暫時無解,我試過社群的一些方案,好像沒啥用!
路由跳轉註意點
- 中劃線的坑 跳轉的路由不支援中劃線(目前),以後未知
開發模式和真機除錯可以正常編譯,打包上傳就不能識別了...浪費我很多時間..
- 路徑的坑
跳轉的url
必須全路徑!!!!!,比如
// 重定向,會提供返回按鈕
Taro.redirectTo({ url: '/pages/list/index' })
// 過載整個程式,關閉其他所有頁面(清除堆疊的頁面),然後開啟你指定的頁面
// 這個非常適合鑑權失敗或者過期的時候.只開啟註冊頁面
Taro.reLaunch({ url:'/pages/login/index'})
//還有其他`navigate`這些,基本都是微信文件寫到的東西,taro封裝了下
複製程式碼
鑑權頁面渲染突兀的改善姿勢!
若是你在第一個頁面做鑑權跳轉,很容易就遇到渲染部分再跳轉的
給人的視覺反饋不是很好,對於此,寫一箇中間鑑權頁面作為第一頁,跳轉會改善很多(視覺上)
因為效果可以定製,而不渲染很多沒必要的元件
比如我的,我的入口頁面就是auth
import './index.less';
import { View } from '@tarojs/components';
import Taro, { Component, Config } from '@tarojs/taro';
class Auth extends Component {
/**
* 指定config的型別宣告為: Taro.Config
*
* 由於 typescript 對於 object 型別推導只能推出 Key 的基本型別
* 對於像 navigationBarTextStyle: 'black' 這樣的推匯出的型別是 string
* 提示和宣告 navigationBarTextStyle: 'black' | 'white' 型別衝突, 需要顯示宣告型別
*/
config: Config = {
navigationBarTitleText: 'xx小助手'
}
static options = {
addGlobalClass: true
}
// 有token就可以進入內容區域,至於token是否有效是在裡面去判斷的;
// 沒有token乖乖去登入
componentDidShow() {
const token = Taro.getStorageSync('token');
if (!!token) {
Taro.redirectTo({ url: '/pages/list/index' })
return
}
Taro.redirectTo({ url: '/pages/login/index' })
}
render() {
return (
<View className='auth-page'>loading....</View>
)
}
}
export default Auth
複製程式碼
componentDidShow
的注意點
previewImage
(圖片的點選全屏預覽),在關掉後會再次觸發該生命週期..
所以把請求放這裡的需要自己權衡下..比如我的列表展開後,點選圖片關閉後導致列表重刷;
挪到了componentWillMount
就不會受previewImage
的影響
mobx
的接入及資料觀察?
mobx
的接入和常規的接入差不多,用法基本也一致..
就是從mobx-react
變成@tarojsw/mobx
,由taro
封裝來提供
至於devtools
這種.小程式目前只能從開發者工具看到,
雖然沒專業的devtools
那麼清晰,但是總體上可以看到資料的組織和響應,如圖
結合mobx
在跳轉前預請求?
比如詳情頁,展示類的頁面,我們一般都是通過typeId
去拿到具體的詳情,再來展示
常規做法都是進到頁面後在componentDidMount
去觸發請求,然後把結果集渲染到頁面,
但這樣一進去就會展示預設資料再替換,有點突兀;我們肯定想改善使用者體驗,那就把資料預請求
我們可以根據實際場景在跳轉之前的生命週期入手,比如redirecTo
可以在componentDidHide
內呼叫函式dispatch
reLuanch
可以在componentWillUnmount
內觸發;
跳轉過去的頁面,可以直接從props
拿到渲染,不會那麼突兀
時間戳及常見日期格式轉換
對於日期的處理,我們最常用的是兩種姿勢的傳遞的時候用時間戳,展示的時候用可讀性較強的YYYY-MM-DD
這種
所以就沒必要引入moment
這個大庫了用的是dayjs
,很小功能比較全面的庫,api
類moment
,用過都說好.
當然,你自己用函式封裝一個轉換也行,就不用引入多一個庫了,見仁見智了.
獲取結點資訊注意點
若是要指定元件自身內的結點,this
必須為this.$scope
微信小程式官方的this
代表例項,在taro
中this.$scope
代表元件自身(例項)
componentDidMount() {
const query = Taro.createSelectorQuery().in(this.$scope);
query.select('#list-card').boundingClientRect((res) => {
console.log('res: ', res);
}).exec()
}
複製程式碼
變動專案基礎資訊(微信小程式)
直接在開發者工具的選項裡面勾選不會儲存到專案內,比如基礎庫的切換;
有效的是直接操作根目錄下的project.config.json
// 這份配置的引數可以具體看微信官方給出的解釋,會更加全面
// https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html?search-key=%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE
{
"miniprogramRoot": "打包輸出路徑",
"projectname": "專案名稱",
"description": "聲兮管理後臺小程式",
"appid": "xxxx",
"setting": {
"urlCheck": true, // 是否檢查安全域名和 TLS 版本
"es6": false, // 是否啟用es6轉換
"postcss": true, // 啟用postcss的支援
"minified": false, // 是否壓縮程式碼
"newFeature": true // 是否啟用新特性的支援
},
"compileType": "miniprogram", // 編譯型別
"libVersion": "2.5.0", // 基礎庫版本的指定
"condition": {}
}
複製程式碼
其他小程式有對應的配置檔案,看官方連結
封裝的一些小玩意
請求封裝(TS)
request.tsx
- 支援路由
prefix
header
的合併- 響應的攔截
- 支援路由
/*
* @Author: CRPER
* @LastEditors: CRPER
* @Github: https://github.com/crper
* @Motto: 折騰是一種樂趣,求知是一種追求。不懂就學,懂則分享。
* @Description:請求介面封裝
*/
import Taro from '@tarojs/taro';
import '@tarojs/async-await';
interface options {
header: any,
method?: string,
dataType?: string,
responseType?: string,
success?: Function,
fail?: Function,
complete?:Function
}
/**
*
* @param url : 介面路徑
* @param method : 請求方法(RESTFUL,但是沒有PATCH,看微信文件支援)
* @param data : 傳遞的資料
* @param options : 可以覆蓋header這些
* @param prefix : 介面額外的字首
*/
export default async function(url: string, method?:string,data?: string | [any] | Object, options?: options, prefix?: string){
// 不支援patch!!!!!微信自家的請求本身就不支援patch!!!
// 微信端自己快取token
const wxToken:string|void =await Taro.getStorage({ key: 'token' })
.then(res => res.data).catch(err => {
if(err) return
} )
// 預設值
const defaultOtions: options = {
method: 'GET',
header:{}
}
// 若是存在token則賦予
if (wxToken) {
defaultOtions.header.Authorization = wxToken
}
const baseUrl: string = process.env.NODE_ENV === 'development' ? 'https://xxx.com/api/web' : 'https://xxx.com/api/web';
const newUrl = prefix ? `${baseUrl}${prefix}${url}` : `${baseUrl}${url}`
const requestObject: any = {
url: newUrl,
...defaultOtions,
...options,
method,
data
}
const codeMessage: Object = {
200: '伺服器成功返回請求的資料。',
201: '新建或修改資料成功。',
202: '一個請求已經進入後臺排隊(非同步任務)。',
204: '刪除資料成功。',
400: '發出的請求有錯誤,伺服器沒有進行新建或修改資料的操作。',
401: '使用者沒有許可權(令牌、使用者名稱、密碼錯誤)。',
403: '使用者得到授權,但是訪問是被禁止的。',
404: '發出的請求針對的是不存在的記錄,伺服器沒有進行操作。',
406: '請求的格式不可得。',
410: '請求的資源被永久刪除,且不會再得到的。',
412: '訪問被拒絕,請重新登入',
422: '當建立一個物件時,發生一個驗證錯誤。',
500: '伺服器發生錯誤,請檢查伺服器。',
502: '閘道器錯誤。',
503: '服務不可用,伺服器暫時過載或維護。',
504: '閘道器超時。',
};
// 檢測請求狀態
const checkStatusAndFilter = (response):Promise<any> | undefined => {
if (response.statusCode >= 200 && response.statusCode < 300) {
if (response.statusCode === 200 || response.statusCode === 304) {
return response.data
}
return response;
}
// 除此之外的錯所有遍歷上面的錯誤資訊丟擲異常
const errortext = codeMessage[response.statusCode] || response.errMsg;
Taro.showToast({
title: errortext,
mask: true,
icon: 'none',
duration: 2000
})
return Promise.reject(response)
};
try {
return await Taro.request(requestObject)
.then(checkStatusAndFilter)
.then(res => {
// 這一塊是我和後端協商的,介面內部為1則出錯的,為0才有資料回來
if (res.code === 1) {
const errMsg = res.msg ? res.msg : '介面錯誤了';
Taro.showToast({
title: errMsg,
mask: true,
icon: 'none',
duration: 2000
})
Promise.reject(errMsg)
}
if (res.code === 0) {
if (res.data) {
return res.data
}
return null
}
return res
}).catch(errRes => {
if (errRes.statusCode === 412) {
Taro.reLaunch({ url:'/pages/login/index'})
}
})
} catch (err) {
Taro.showToast({
title: '程式碼執行異常',
mask: true,
icon: 'none',
duration: 2000
})
}
}
複製程式碼
- 用法
// 我配置了alias
import wxfetch from '@utils/request';
// 比如我程式碼中的其中一個請求,處理行為
// 切割列表資料
spliceList = (dataIdArr: Array<string | number> = []) => {
const {list, paginate: {total}} = this.state;
// 若是隻有一條,幹掉後嘗試請求列表判斷是否還有新的資料
if (list.length <= 1) {
this.getList()
}
let tempArr: Array<Object> = list.filter((item) => {
for (let i = 0; i < dataIdArr.length; i++) {
let innerItemId = Number(dataIdArr[i]);
if (item.id !== innerItemId) {
return item
}
}
})
this.setState({
list: tempArr,
paginate: {
total: total - dataIdArr.length
},
dataIdArr: []
})
}
// 處理行為
handleActionSheetClick = async (e: number): Promise<any> => {
try {
const actionParam = {operationType: e};
const {dataIdArr, operationNote} = this.state;
const isActionNoValid: boolean = !e || e === 0 || (Array.isArray(dataIdArr) && dataIdArr.length === 0);
if (isActionNoValid) {
Taro.atMessage({
'message': '請再次您的行為是否正常,比如勾選資料!',
'type': 'error',
'duration': 1000
})
return false;
}
await wxfetch('/suspiciousDatas', 'POST', {
dataIdArr,
operationNote,
...actionParam
});
// 切割陣列且關閉遮罩層
this.spliceList(dataIdArr);
this.handleActionSheetClose();
} catch (err) {
console.log(err);
}
}
複製程式碼
簡化版的節流器(TS)
throttle.tsx
/*
* @Author: CRPER
* @LastEditors: CRPER
* @Github: https://github.com/crper
* @Motto: 折騰是一種樂趣,求知是一種追求。不懂就學,懂則分享。
* @Description: 簡易版的節流函式
*/
/**
* @param fn : 回撥函式
* @param threshold : 時間,單位毫秒
*/
export default function throttle(fn: Function, threshold: number = 1500) {
if (threshold === null) {
threshold = 1500
}
let _lastExecTime: null | number = null;
let context = this
return function (...args: any[]): void {
let _nowTime: number = new Date().getTime();
if (_nowTime - Number(_lastExecTime) > threshold || !_lastExecTime) {
fn.apply(context, args);
_lastExecTime = _nowTime
}
}
}
複製程式碼
- 用法
在this.xxx.bind
的基礎上
import throttle from '@utils/throttle';
// 滾動到頂部觸發
onScrolltoupper = throttle(() => {
console.log('1111');
},3000)
複製程式碼
下拉重新整理顯示內建的loading
.
就是微信自家的三個小點, 這個需要配置下頁面的一些自有屬性.
Taro
只要引入Config
,即可在元件內宣告頁面屬性
import Taro, { Component, Config } from '@tarojs/taro';
class ReportList extends Component {
/**
* 指定config的型別宣告為: Taro.Config
*
* 由於 typescript 對於 object 型別推導只能推出 Key 的基本型別
* 對於像 navigationBarTextStyle: 'black' 這樣的推匯出的型別是 string
* 提示和宣告 navigationBarTextStyle: 'black' | 'white' 型別衝突, 需要顯示宣告型別
*/
config: Config = {
navigationBarTitleText: '可疑資料彙總',
enablePullDownRefresh: true, // 這個是啟用下拉重新整理特性
backgroundTextStyle: "dark", // 把顯示的文字顏色改成暗色調,亮色的話.你背景不改看不到,因為同色
backgroundColor:'#f7f7f7' // 頁面的背景色
}
}
// 啟用後,記得加對應的條件關閉,不然會一直顯示
// 下拉重新整理
onPullDownRefresh = () :void => {
// 這個loading是 導航欄,頁面標題那塊顯示一個loading , 微信內建的
Taro.showLoading({
title: 'loading....'
})
// 因為我的介面請求都是 async await的姿勢,所以可以佇列執行
this.getList();
this.unselect();
// 介面請求完畢後隱藏兩個loading , 標題和下拉區域
Taro.hideLoading();
Taro.stopPullDownRefresh();
}
複製程式碼
實現元件樣式過渡?
實現一個元件過渡可以一定程度上增強體驗,本質就是CSS3
來寫過渡,
比如看我這邊實現的一個效果,自己感覺還看得過去
- 樣式
//若是要產生視覺效應,那元素有偏移才能看出來,所以一般被作用的元素都不會在預設位置
// 這個專案用了less ,主要過渡
.testdiv{
opacity: 0;
transform: translateY(100vh) rotate(270deg) scale(0.5);
&.fadeIn{
opacity: 1;
transform: translateY(0) rotate(0deg);
transition:all 0.3s ease-in-out;
}
&.fadeOut{
opacity: 0;
transform: rotate(-270deg) scale(0.2) translateX(-100vw);
transition:all 0.3s ease-in-out;
}
}
複製程式碼
- 作用區域
這邊用了classnames
來動態追加class
<View className={classnames({ "search-panel": true, 'fadeIn': open, 'fadeOut': !open})} >
</View>
複製程式碼
節點元素高度的過渡(CSS3)
就是讓展開和收起有個過渡效果,
經過N多次的嘗試(不能給元素設定height
!!), 把元素初始化的高度設定max-height:0
,
其他過渡設定合適的max-height
即可解決
Taro裡面對事件的支援
有些文件沒說到,只能去翻原始碼...看common.d.ts
一目瞭然,比如長按事件這些
css3 loading 引入
其實跟在普通開發模式上寫法差不,基本還是CSS3的功能,DIV
換成能識別的節點而已..比如Taro
// 樣式部分
.no-data-text {
background-color: rgba(233, 228, 228, 0.726);
color: #333;
height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 50px;
font-weight: 700;
.loading-text{
font-size:28px;
color:#555;
}
}
.spinner {
width: 200px;
height: 70px;
text-align: center;
font-size: 10px;
}
.spinner .rect {
background-color: rgb(123, 176, 225);
height: 100%;
width: 10px;
margin:0 5px;
display: inline-block;
-webkit-animation: stretchdelay 1.2s infinite ease-in-out;
animation: stretchdelay 1.2s infinite ease-in-out;
}
.spinner .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.spinner .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
.spinner .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.spinner .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
@-webkit-keyframes stretchdelay {
0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
20% { -webkit-transform: scaleY(1.0) }
}
@keyframes stretchdelay {
0%, 40%, 100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
} 20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
複製程式碼
<!--節點部分-->
<View className="no-data-text">
<View className="spinner">
<View className="rect rect1"></View>
<View className="rect rect2"></View>
<View className="rect rect3"></View>
<View className="rect rect4"></View>
<View className="rect rect5"></View>
</View>
<View className="loading-text">正在載入中......</View>
</View>
複製程式碼
總結
截止該文章輸出的時候,Taro
的版本
? Taro v1.2.7
Taro CLI 1.2.7 environment info:
System:
OS: macOS 10.14.2
Shell: 5.3 - /bin/zsh
Binaries:
Node: 10.14.2 - /usr/local/bin/node
Yarn: 1.13.0 - /usr/local/bin/yarn
npm: 6.5.0 - /usr/local/bin/npm
npmPackages:
@tarojs/async-await: 1.2.7 => 1.2.7
@tarojs/components: 1.2.7 => 1.2.7
@tarojs/mobx: 1.2.7 => 1.2.7
@tarojs/mobx-h5: 1.2.7 => 1.2.7
@tarojs/mobx-rn: 1.2.7 => 1.2.7
@tarojs/plugin-babel: 1.2.7 => 1.2.7
@tarojs/plugin-csso: 1.2.7 => 1.2.7
@tarojs/plugin-less: 1.2.7 => 1.2.7
@tarojs/plugin-sass: 1.2.7 => 1.2.7
@tarojs/plugin-uglifyjs: 1.2.7 => 1.2.7
@tarojs/rn-runner: 1.2.7 => 1.2.7
@tarojs/router: 1.2.7 => 1.2.7
@tarojs/taro: 1.2.7 => 1.2.7
@tarojs/taro-alipay: 1.2.7 => 1.2.7
@tarojs/taro-h5: 1.2.7 => 1.2.7
@tarojs/taro-swan: 1.2.7 => 1.2.7
@tarojs/taro-tt: 1.2.7 => 1.2.7
@tarojs/taro-weapp: 1.2.7 => 1.2.7
@tarojs/webpack-runner: 1.2.7 => 1.2.7
eslint-config-taro: 1.2.7 => 1.2.7
eslint-plugin-taro: 1.2.7 => 1.2.7
複製程式碼
Taro
和Taro UI
目前版本對ts
的支援還有待提高,會偶爾碰到缺少types
的
若是專案不大,對於想省心的,建議直接擼JS
版本;
Taro
社群目前還是很活躍的, 照這樣的情況下去,再迭代兩三個X.Y.Z
(Y位)版本應該會好用很多.
ts
的好處很明顯,編輯器可以直接懸浮顯示推斷的型別,很多錯誤可以在開發過程避免了;
水文到此結束,有不對之處請留言,會及時修正,謝謝閱讀.