最近一直在學習taro,網上搜的重點知識總結很少,所以想著自己寫一篇我覺得比較重要的知識點總結一下。
1.檔案組織形式
以下檔案組織規範為最佳實踐的建議
所有專案原始碼請放在專案根目錄 src
目錄下,專案所需最基本的檔案包括 入口檔案 以及 頁面檔案
- 入口檔案為
app.js
- 頁面檔案建議放置在
src/pages
目錄下
一個可靠的 Taro 專案可以按照如下方式進行組織
├── 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複製程式碼
2.檔案命名
Taro 中普通 JS/TS 檔案以小寫字母命名,多個單詞以下劃線連線,例如 util.js
、util_helper.js
Taro 元件檔案命名遵循 Pascal 命名法,例如 ReservationCard.jsx
3.JavaScript 書寫規範
taro的書寫規範大概和Eslint的規範類似,具體可參考官網連結:taro-docs.jd.com/taro/docs/s…
4.書寫順序
在 Taro 元件中會包含類靜態屬性、類屬性、生命週期等的類成員,其書寫順序最好遵循以下約定(順序從上至下)
- static 靜態方法
- constructor
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
- 點選回撥或者事件回撥 比如
onClickSubmit()
或者onChangeDescription()
- render
5.推薦使用物件解構的方式來使用 state、props
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
render () {
const { isEnable } = this.props // ✓ 正確
const { myTime } = this.state // ✓ 正確
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}複製程式碼
6.不要在呼叫 this.setState 時使用 this.state
由於 this.setState 非同步的緣故,這樣的做法會導致一些錯誤,可以通過給 this.setState 傳入函式來避免
this.setState({
value: this.state.value + 1
}) // ✗ 錯誤
this.setState(prevState => ({ value: prevState.value + 1 })) // ✓ 正確複製程式碼
7.map 迴圈時請給元素加上 key 屬性
list.map(item => {
return (
<View className='list_item' key={item.id}>{item.name}</View>
)
})複製程式碼
8.儘量避免在 componentDidMount 中呼叫 this.setState
因為在
componentDidMount
中呼叫this.setState
會導致觸發更新
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
componentDidMount () {
this.setState({ // ✗ 儘量避免,可以在 componentWillMount 中處理
name: 1
})
}
render () {
const { isEnable } = this.props
const { myTime } = this.state
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}
複製程式碼
不要在 componentWillUpdate/componentDidUpdate/render 中呼叫 this.setState
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
componentWillUpdate () {
this.setState({ // ✗ 錯誤
name: 1
})
}
componentDidUpdate () {
this.setState({ // ✗ 錯誤
name: 1
})
}
render () {
const { isEnable } = this.props
const { myTime } = this.state
this.setState({ // ✗ 錯誤
name: 11
})
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}複製程式碼
9.事件繫結均以 on 開頭
在 Taro 中所有預設事件如
onClick
、onTouchStart
等等,均以on
開頭
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12
}
clickHandler (e) {
console.log(e)
}
render () {
const { myTime } = this.state
return (
<View className='test' onClick={this.clickHandler}> // ✓ 正確
<Text className='test_text'>{myTime}</Text>
</View>
)
}
}複製程式碼
10.不能使用 Array#map 之外的方法操作 JSX 陣列
Taro 在小程式端實際上把 JSX 轉換成了字串模板,而一個原生 JSX 表示式實際上是一個 React/Nerv 元素(react-element)的構造器,因此在原生 JSX 中你可以隨意地對一組 React 元素進行操作。但在 Taro 中你只能使用
map
方法,Taro 轉換成小程式中wx:for
11.設計稿及尺寸單位
在 Taro 中尺寸單位建議使用 px
、 百分比 %
,Taro 預設會對所有單位進行轉換。在 Taro 中書寫尺寸按照 1:1 的關係來進行書寫,即從設計稿上量的長度 100px
,那麼尺寸書寫就是 100px
,當轉成微信小程式的時候,尺寸將預設轉換為 100rpx
,當轉成 H5 時將預設轉換為以 rem
為單位的值。
如果你希望部分 px
單位不被轉換成 rpx
或者 rem
,最簡單的做法就是在 px 單位中增加一個大寫字母,例如 Px
或者 PX
這樣,則會被轉換外掛忽略。
結合過往的開發經驗,Taro 預設以 750px
作為換算尺寸標準,如果設計稿不是以 750px
為標準,則需要在專案配置 config/index.js
中進行設定,例如設計稿尺寸是 640px
,則需要修改專案配置 config/index.js
中的 designWidth
配置為 640
12.元件化 & Props
元件可以將 UI 切分成一些的獨立的、可複用的部件,這樣你就只需專注於構建每一個單獨的部件。
元件從概念上看就像是函式,它可以接收任意的輸入值(稱之為 props
),並返回一個需要在頁面上展示的 Taro 元素。
Props 的只讀性
一個宣告的元件決不能修改它自己的 props
。
使用 PropTypes 檢查型別
隨著應用日漸龐大,你可以通過型別檢查捕獲大量錯誤。要檢查元件的屬性,你需要配置特殊的 propTypes
屬性:
import PropTypes from 'prop-types';
class Greeting extends Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};複製程式碼
13.正確地使用 State
關於 setState() 這裡有三件事情需要知道:
1.不要直接更新狀態
例如,此程式碼不會重新渲染元件:
// Wrong
this.state.comment = 'Hello'
複製程式碼
應當使用 setState()
:
// Correct
this.setState({ comment: 'Hello' })複製程式碼
2.狀態更新一定是非同步的
Taro 可以將多個 setState()
呼叫合併成一個呼叫來提高效能。
因為 this.state
和 props
一定是非同步更新的,所以你不能在 setState
馬上拿到 state
的值,例如:
// 假設我們之前設定了 this.state.counter = 0
updateCounter () {
this.setState({
counter: 1
})
console.log(this.state.counter) // 這裡 counter 還是 0
}
複製程式碼
正確的做法是這樣,在 setState
的第二個引數傳入一個 callback:
// 假設我們之前設定了 this.state.counter = 0
updateCounter () {
this.setState({
counter: 1
}, () => {
// 在這個函式內你可以拿到 setState 之後的值
})
}複製程式碼
3.state 更新會被合併
當你呼叫 setState()
,Taro 將合併你提供的物件到當前的狀態中。
例如,你的狀態可能包含幾個獨立的變數:
constructor(props) {
super(props)
this.state = {
posts: [],
comments: []
}
}
複製程式碼
然後通過呼叫獨立的 setState()
呼叫分別更新它們:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
})
})
}
複製程式碼
合併是淺合併,所以 this.setState({comments})
不會改變 this.state.posts
的值,但會完全替換 this.state.comments
的值。
14.條件渲染
列舉條件渲染
有時渲染的條件非常多,不管是 if-else
還是 switch-case
來做條件渲染都會顯得太麻煩。這時我們可以使用「表驅動法」:列舉渲染。
function Loading (props) {
const { loadingText, LOADING_STATUS, loadingStatus, onRetry } = props
return (
<View className='loading-status'>
{
{
'loading': loadingText,
'fail': <View onClick={onRetry}> 載入失敗, 點選重試 </View>,
'no-more': '沒有更多了'
}[loadingStatus] /** loadingStatus 是 `loading`、`fail`、`no-more` 其中一種狀態 **/
}
</View>
)
}複製程式碼
15.渲染多個元件
下面,我們使用 JavaScript 中的 map()
方法遍歷 numbers
陣列。對陣列中的每個元素返回 <Text>
標籤,最後我們得到一個陣列 listItems
:
const numbers = [...Array(100).keys()] // [0, 1, 2, ..., 98, 99]
const listItems = numbers.map((number) => {
return <Text className='li'> 我是第 {number + 1} 個數字</Text>
})
複製程式碼
這段程式碼生成了一個 1 到 100 的數字列表。
Keys
但是在上面的程式碼,你會得到一個報錯:提醒你當迴圈一個陣列時應該提供 keys。Keys 可以在 DOM 中的某些元素被增加或刪除的時候幫助 Nerv/小程式 識別哪些元素髮生了變化。因此你應當給陣列中的每一個元素賦予一個確定的標識。
taroKeys
taroKey
適用於迴圈渲染原生小程式元件,賦予每個元素唯一確定標識,轉換為小程式的 wx:key
。
元素的 key 在他的兄弟元素之間應該唯一
陣列元素中使用的 key 在其兄弟之間應該是獨一無二的。然而,它們不需要是全域性唯一的。當我們生成兩個不同的陣列時,我們可以使用相同的 key
16.類函式式元件
自
v1.3.0-beta.0
起支援
由於一個檔案不能定義兩個元件,但有時候我們需要元件內部的抽象元件,這時類函式式元件就是你想要答案。假設我們有一個 Class 元件,它包括了一個 Header
一個 Footer
,我們可以這樣定義:
class SomePage extends Taro.Component {
renderHeader () {
const { header } = this.state
return <View>{header}</View>
}
renderFooter (footer) {
return <View>{footer}</View>
}
render () {
return (
<View>
{this.renderHeader()}
{...}
{this.renderFooter('footer')}
</View>
)
}
}
複製程式碼
在 renderHeader
或 renderFooter
函式中,我們可以訪問類的 this
,也可以傳入不限量的引數,這型別的函式也可以呼叫無限次數。但這樣的寫法也存在一些限制:
- 函式的命名必須以
render
開頭,render
後的第一個字母需要大寫 - 函式的引數不得傳入 JSX 元素或 JSX 元素引用
- 函式不能遞迴地呼叫自身
17.在我們設計元件時,有些元件通常不知道自己的子元件會有什麼內容,例如 Sidebar
和 Dialog
這樣的容器元件。
我們建議在這樣的情況使用 this.props.children
來傳遞子元素:
class Dialog extends Component {
render () {
return (
<View className='dialog'>
<View className='header'>Welcome!</View>
<View className='body'>
{this.props.children}
</View>
<View className='footer'>-- divider --</View>
</View>
)
}
}
複製程式碼
這樣就能允許其它元件在 JSX 中巢狀任意子元件傳遞給 Dialog
:
class App extends Component {
render () {
return (
<View className='container'>
<Dialog>
<View className="dialog-message">
Thank you for using Taro.
</View>
</Dialog>
</View>
)
}
}
複製程式碼
在 <Dialog />
JSX 標籤內的任何內容都會作為它的子元素(Children)都會傳遞到它的元件。
請不要對 this.props.children
進行任何操作。Taro 在小程式中實現這個功能使用的是小程式的 slot
功能,也就是說你可以把 this.props.children
理解為 slot
的語法糖,this.props.children
在 Taro 中並不是 React 的 ReactElement
物件,因此形如 this.props.children && this.props.children
、this.props.children[0]
在 Taro 中都是非法的。
this.props.children
無法用 defaultProps
設定預設內容。由於小程式的限制,Taro 也無法知道元件的消費者是否傳入內容,所以無法應用預設內容。
不能把 this.props.children
分解為變數再使用。由於普通的 props
有一個確切的值,所以當你把它們分解為變數執行時可以處理,this.props.children
則不能這樣操作,你必須顯性地把 this.props.children
全部都寫完整才能實現它的功能。
組合
自
1.1.9
開始支援
有些情況你不僅僅需要只傳遞一個子元件,可能會需要很多個「佔位符」。例如在這個 Dialog
元件中,你不僅需要自定義它的 body
,你希望它的 header
和 footer
都是給 Dialog
元件的使用者自由定製。這種情況可以這樣做:
class Dialog extends Component {
render () {
return (
<View className='dialog'>
<View className='header'>
{this.props.renderHeader}
</View>
<View className='body'>
{this.props.children}
</View>
<View className='footer'>
{this.props.renderFooter}
</View>
</View>
)
}
}
class App extends Component {
render () {
return (
<View className='container'>
<Dialog
renderHeader={
<View className='welcome-message'>Welcome!</View>
}
renderFooter={
<Button className='close'>Close</Button>
}
>
<View className="dialog-message">
Thank you for using Taro.
</View>
</Dialog>
</View>
)
}
}
複製程式碼
在我們宣告 Dialog
元件時,header
和 footer
部分我們分別增加了 this.props.renderHeader
和 this.props.renderFooter
作為佔位符。相應地,我們在使用 Dialog
元件時,就可以給 renderHeader
和 renderFooter
傳入 JSX 元素,這兩個分別傳入的 JSX 元素將會填充它們在 Dialog
元件中的位置——就像在 Dialog
JSX 標籤裡寫入的內容,會填充到 this.props.children
的位置一樣。
注意事項
元件的組合需要遵守 this.props.children
的所有規則。組合這個功能和 this.props.children
一樣是通過 slot
實現的,也就是說 this.props.children
的限制對於元件組合也都同樣適用。
所有組合都必須用 render
開頭,且遵守駝峰式命名法。和我們的事件規範以 on
開頭一樣,元件組合使用 render
開頭。
組合只能傳入單個 JSX 元素,不能傳入其它任何型別。當你需要進行一些條件判斷或複雜邏輯操作的時候,可以使用一個 Block
元素包裹住,然後在 Block
元素的裡面填充其它複雜的邏輯。