一.什么是taro?
Taro 是一套遵循 React 語法規(guī)范的 多端開發(fā) 解決方案煌寇。通過一套react的代碼焕蹄,就可以分別編譯出微信小程序、H5唧席、支付寶小程序擦盾。。淌哟。
我根據(jù)taro redux模板創(chuàng)建了一套自己寫的H5種子項(xiàng)目迹卢,大家可以一起學(xué)習(xí),項(xiàng)目地址:https://github.com/WangxinsHub/taro-seed
二.如何安裝taro徒仓?
1.首先全局安裝taro命令行:
$ npm install -g @tarojs/cli
$ yarn global add @tarojs/cli
2.創(chuàng)建taro種子項(xiàng)目
$ taro init myApp
3.編譯taro腐碱,在創(chuàng)建的種子項(xiàng)目中,package文件如下:
"scripts": {
"build:weapp": "taro build --type weapp",//打包小程序
"build:h5": "taro build --type h5", //打包H5
"dev:weapp": "npm run build:weapp -- --watch",//編譯小程序
"dev:h5": "npm run build:h5 -- --watch",//編譯H5
},
三、開發(fā)前注意
小程序工具:
需要設(shè)置關(guān)閉 ES6 轉(zhuǎn) ES5 功能症见,開啟可能報(bào)錯(cuò)
需要設(shè)置關(guān)閉上傳代碼時(shí)樣式自動(dòng)補(bǔ)全喂走,開啟可能報(bào)錯(cuò)
需要設(shè)置關(guān)閉代碼壓縮上傳,開啟可能報(bào)錯(cuò)
四谋作、項(xiàng)目說明
1.dist是編譯(dev/build)結(jié)果目錄
2.config配置目錄
index.js(默認(rèn)配置)
const path = require('path')
const config = {
projectName: 'Taro-time-bus',
date: '2019-1-8',
designWidth: 750,//設(shè)計(jì)稿以 iPhone6 750px 作為設(shè)計(jì)尺寸標(biāo)準(zhǔn)芋肠。
//目前 Taro 支持 750、 640 遵蚜、 828 三種尺寸設(shè)計(jì)稿帖池,他們的換算規(guī)則如下:
deviceRatio: {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
},
// 項(xiàng)目源碼目錄
sourceRoot: 'src',
// 項(xiàng)目產(chǎn)出目錄
outputRoot: 'dist',
// 通用插件配置
plugins: {
//plugins 用來設(shè)置一些各個(gè)端通用的編譯過程配置,例如 babel 配置吭净,JS/CSS 壓縮配置等睡汹。
babel: {
sourceMap: true,
presets: ['env'],
plugins: ['transform-decorators-legacy', 'transform-class-properties', 'transform-object-rest-spread']
}
/*
//設(shè)置打包過程中的 JS 代碼壓縮
uglify: {
enable: true,
config: {
// 配置項(xiàng)同 https://github.com/mishoo/UglifyJS2#minify-options
}
},
//設(shè)置打包過程中的 CSS 代碼壓縮
csso: {
enable: true,
config: {
// 配置項(xiàng)同 https://github.com/css/csso#minifysource-options
}
}*/
},
// 全局變量設(shè)置
defineConstants: {
context:{
iconPath:'xxx'
}
},
alias: {
'@components': path.resolve(__dirname,'../src/components'),
'@icons': path.resolve(__dirname,'../src/icons'),
'@src': path.resolve(__dirname,'../src/'),
'@utils': path.resolve(__dirname, '..', 'src/utils')
},
weapp: {
//小程序編譯過程的相關(guān)配置。
compile: {
compressTemplate: true,//決定小程序打包時(shí)是否需要壓縮 wxml
},
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
pxtransform: {
enable: true,
config: {
onePxTransform: true, //設(shè)置 1px 是否需要被轉(zhuǎn)換
unitPrecision: 5,//REM 單位允許的小數(shù)位寂殉。
selectorBlackList: [],//黑名單里的選擇器將會(huì)被忽略囚巴。
replace: true,//直接替換而不是追加一條進(jìn)行覆蓋。
mediaQuery: false,//允許媒體查詢里的 px 單位轉(zhuǎn)換
minPixelValue: 0//設(shè)置一個(gè)可被轉(zhuǎn)換的最小 px 值
}
},
url: {
enable: true,
config: {
limit: 10240 // 設(shè)定轉(zhuǎn)換尺寸上限
}
},
cssModules: {
enable: false, // 默認(rèn)為 false友扰,如需使用 css modules 功能彤叉,則設(shè)為 true
config: {
namingPattern: 'module', // 轉(zhuǎn)換模式,取值為 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
},
h5: {
devServer: {
port: 10086
},
publicPath: '/', //設(shè)置輸出解析文件的目錄焕檬。
staticDirectory: 'static',//h5 編譯后的靜態(tài)文件目錄姆坚。
esnextModules: ['taro-ui'],//配置需要額外的編譯的源碼模塊,比如taro-ui:
miniCssExtractPluginOption: {
filename: 'css/[name]/[hash].css',
chunkFilename: 'css/[name]/[hash].css'
},
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
cssModules: {
enable: false, // 默認(rèn)為 false实愚,如需使用 css modules 功能兼呵,則設(shè)為 true
config: {
namingPattern: 'module', // 轉(zhuǎn)換模式,取值為 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}
dev.js
module.exports = {
env: {
NODE_ENV: '"development"',
API_HOSTNAME:JSON.stringify('https://appdev.ibuscloud.com'),//test環(huán)境地址
},
defineConstants: {
},
weapp: {
},
h5: {}
}
3.入口文件為 app.js
import '@tarojs/async-await'
import Taro, { Component } from '@tarojs/taro'
import { Provider } from '@tarojs/redux'
import 'taro-ui/dist/style/index.scss' // 全局引入一次即可
import Index from './pages/index'
import configStore from './store'
import './app.less'
// 如果需要在 h5 環(huán)境中開啟 React Devtools
// 取消以下注釋:
// if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5') {
// require('nerv-devtools')
// }
const store = configStore()
class App extends Component {
config = {
pages: [
'pages/index/index',
'pages/search/index',
'pages/lineDetail/index',
'pages/page2/index'
],
"permission": {
"scope.userLocation": {
"desc": "你的位置信息將用于小程序位置接口的效果展示"
}
},
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
}
componentDidMount () {}
componentDidShow () {
//獲取用戶位置腊敲,TODO:H5
Taro.getLocation().then(data=>{
console.log(data)
const {latitude,longitude} = data ;
Taro.setStorageSync('userLat', latitude);
Taro.setStorageSync('userLng', longitude);
})
}
componentDidHide () {}
componentCatchError () {}
componentDidCatchError () {}
// 在 App 類中的 render() 函數(shù)沒有實(shí)際作用
// 請(qǐng)勿修改此函數(shù)
render () {
return (
<Provider store={store}>
<Index />
</Provider>
)
}
}
Taro.render(<App />, document.getElementById('app'))
1??其中config主要參考微信小程序的全局配置而來击喂,在編譯成小程序時(shí),這一部分配置將會(huì)被抽離成 app.json
碰辅,而編譯成其他端懂昂,亦會(huì)有其他作用。
頁面路由在此配置没宾,頁面背景色導(dǎo)航欄等也在此設(shè)置
2??app.js的生命周期凌彬、頁面與組件的生命周期:
而且由于入口文件繼承自 Component 組件基類,它同樣擁有組件生命周期循衰,但因?yàn)槿肟谖募奶厥庑圆玻纳芷诓?em>不完整,如下
3??普通頁面生命周期與component一致
小程序?qū)S械姆椒ǎ海℉5中暫不支持)
4??組件Taro 的組件同樣是繼承自 Component 組件基類会钝,與頁面類似伐蒋,組件也必須包含一個(gè) render 函數(shù),返回 JSX 代碼。 比頁面多了一個(gè)componentWillReceiveProps先鱼。
注意 : 組件的 constructor 與 render 提前調(diào)用俭正,所以componentWillMount這個(gè)生命周期有一個(gè)滯后性,不可以直接在render中用路由得來的數(shù)據(jù)
render () {
// 在 willMount 之前無法拿到路由參數(shù)
const abc = this.$router.params.abc
return <Custom adc={abc} />
}
?
// 正確寫法
componentWillMount () {
const abc = this.$router.params.abc
this.setState({
abc
})
}
render () {
// 增加一個(gè)兼容判斷
return this.state.abc && <Custom adc={abc} />
}
?
由于微信小程序里頁面在 onLoad 時(shí)才能拿到頁面的路由參數(shù)焙畔,而頁面 onLoad 前組件都已經(jīng) attached 了掸读。因此頁面的 componentWillMount 可能會(huì)與預(yù)期不太一致。
五闹蒜、開發(fā)中的問題
1.靜態(tài)資源的引入
1??通過ES6的import引用圖片寺枉、js抑淫、等文件(暫不支持svg)绷落,而且不需要安裝任何 loader。
2??可以先上傳到服務(wù)器始苇,然后引用服務(wù)器的地址(在less中background用的多一點(diǎn))
全局原始app.less 只會(huì)影響到頁面級(jí)別的文件砌烁,組件的獲取不到全局的樣式
可以同過@import 讓組件獲得app.less全局樣式
@import "../../app";
2.jsx的支持程度:
[不能在 JSX 參數(shù)中使用匿名函數(shù)](自 v1.2.9 開始支持注意:在各小程序端,使用匿名函數(shù)催式,尤其是在 循環(huán)中 使用匿名函數(shù)函喉,比使用 bind 進(jìn)行事件傳參用更大的內(nèi)存,速度也會(huì)更慢荣月。)(https://github.com/NervJS/taro/blob/master/packages/eslint-plugin-taro/docs/no-anonymous-function-in-props.md)
<View {...this.props} />
<View {...props} />
<Custom {...props} />
以上是錯(cuò)誤寫法
- 不支持無狀態(tài)組件(必須return)
3.組件化 & props &state
1??使用 PropTypes 檢查類型
Greeting.propTypes = {
name: PropTypes.string
};
2??給組件設(shè)置 defaultProps
3??組件傳遞函數(shù)屬性名以 on 開頭
4??當(dāng)組件傳入jsx的時(shí)候必須用render開頭,在小程序中其實(shí)是通過slot插槽來實(shí)現(xiàn)的,所以和this.props.children一樣斋攀, this.props.children && this.props.children到腥、this.props.children[0] 在 Taro 中都是非法的。且組合只能傳入單個(gè) JSX 元素萌业,不能傳入其它任何類型坷襟。當(dāng)你需要進(jìn)行一些條件判斷或復(fù)雜邏輯操作的時(shí)候,可以使用一個(gè) Block 元素包裹住生年,然后在 Block 元素的里面填充其它復(fù)雜的邏輯婴程。
state:
5??state this.state 和 props 一定是異步更新的,所以你不能在 setState 馬上拿到 state 的值
6??不要在 state
與 props
上用同名的字段抱婉,因?yàn)檫@些被字段在微信小程序中都會(huì)掛在 data
上
7??盡量避免在 componentDidMount 中調(diào)用 this.setState 因?yàn)樵?componentDidMount 中調(diào)用 this.setState 會(huì)導(dǎo)致觸發(fā)更新 (盡量避免档叔,可以componentWillMount 中處理)
不要在 componentWillUpdate/componentDidUpdate/render 中調(diào)用 this.setState
5.事件
事件類型參照微信小程序https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html
6.路由
1??傳參:
Taro.navigateTo({
url: '/pages/page/path/name?id=2&type=test'
})
接收:this.$router.params
2??預(yù)加載傳參
在微信小程序中,從調(diào)用 Taro.navigateTo蒸绩、Taro.redirectTo 或 Taro.switchTab 后衙四,到頁面觸發(fā) componentWillMount 會(huì)有一定延時(shí)。因此一些網(wǎng)絡(luò)請(qǐng)求可以提前到發(fā)起跳轉(zhuǎn)前一刻去請(qǐng)求侵贵。
傳參:
this.$preload({
x: 1,
y: 2
})
Taro.navigateTo({ url: '/pages/B/B' })
(也能夠繞過 componentWillMount 延時(shí))
接收componentWillMount () {
console.log('preload: ', this.$router.preload)
}
7異步編程 以及 接口請(qǐng)求
$ yarn add @tarojs/async-await
import '@tarojs/async-await'
異步dispatch届搁,action.js:
import '@tarojs/async-await'
import {
ADD,
MINUS,
ASYNC,
ASYNC_BEFORE
} from './action-type';
import Http from '../../api/Server'
import Url from '../../api/url';
export const add = () => {
return {
type: ADD
}
}
export const minus = () => {
return {
type: MINUS
}
}
export const asyncAdd = (params) => {
// 返回函數(shù),異步dispatch
return async dispatch => {
try{
dispatch({
type: ASYNC_BEFORE,
})
let result = await Http.request('post',Url.lineRecommendQuery,params);
// 如果不成功,則將不成功的信息打印出來
if(result){
if(!result.success) console.error(result.message);
dispatch({
type: ASYNC,
response: result,
})
}
}catch(err){
console.error(err);
}
}
}
Http 請(qǐng)求工具類:api.js
import Taro from '@tarojs/taro'
class Http {
constructor(){
const HOSTNAME = process.env.API_HOSTNAME
this.url={
}
}
request(method = 'post', url, params) {
console.log(process.env.TARO_ENV)
Taro.showNavigationBarLoading();
return new Promise((resolve, reject) => {
Taro.request({
url,
method,
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': '*/*'
}
}).then(res => {
Taro.hideNavigationBarLoading()
resolve(typeof res.data === 'object' ? res.data : JSON.parse(res.data))
}, err => {
Taro.hideNavigationBarLoading()
reject(err)
})
})
}
}
export default new Http();