以下文件組織規(guī)范為最佳實踐的建議
所有項目源代碼請放在項目根目錄 src
目錄下,項目所需最基本的文件包括 入口文件 以及 頁面文件
app.js
src/pages
目錄下一個可靠的 Taro 項目可以按照如下方式進行組織
├── config 配置目錄
| ├── dev.js 開發(fā)時配置
| ├── index.js 默認配置
| └── prod.js 打包時配置
├── src 源碼目錄
| ├── components 公共組件目錄
| ├── pages 頁面文件目錄
| | ├── index index 頁面目錄
| | | ├── banner 頁面 index 私有組件
| | | ├── index.js index 頁面邏輯
| | | └── index.css index 頁面樣式
| ├── utils 公共方法庫
| ├── app.css 項目總通用樣式
| └── app.js 項目入口文件
└── package.json
文件命名
Taro 中普通 JS/TS 文件以小寫字母命名,多個單詞以下劃線連接,例如 util.js
、util_helper.js
Taro 組件文件命名遵循 Pascal 命名法,例如 ReservationCard.jsx
Taro 中普通 JS/TS 文件以 .js
或者 .ts
作為文件后綴
Taro 組件則以 .jsx
或者 .tsx
作為文件后綴,當(dāng)然這不是強制約束,只是作為一個實踐的建議,組件文件依然可以以 .js
或者 .ts
作為文件后綴
在 Taro 中書寫 JavaScript 請遵循以下規(guī)則
不要混合使用空格與制表符作為縮進
function hello (name) {
console.log('hi', name) // ? 正確
console.log('hello', name) // ? 錯誤
}
const id = 1234 // ? 錯誤
const id = 1234 // ? 正確
const a = 'a' // ? 正確
const a = 'a'; // ? 錯誤
console.log('hello there')
// 如果遇到需要轉(zhuǎn)義的情況,請按如下三種寫法書寫
const x = 'hello "world"'
const y = 'hello \'world\''
const z = `hello 'world'`
if (user) {
// ? 錯誤
const name = getName()
}
if (user) {
const name = getName() // ? 正確
}
if (condition) { ... } // ? 正確
if(condition) { ... } // ? 錯誤
function name (arg) { ... } // ? 正確
function name(arg) { ... } // ? 錯誤
run(function () { ... }) // ? 正確
run(function() { ... }) // ? 錯誤
fn(... args) // ? 錯誤
fn(...args) // ? 正確
for (let i = 0 ;i < items.length ;i++) {...} // ? 錯誤
for (let i = 0; i < items.length; i++) {...} // ? 正確
if (admin){...} // ? 錯誤
if (admin) {...} // ? 正確
getName( name ) // ? 錯誤
getName(name) // ? 正確
user .name // ? 錯誤
user.name // ? 正確
typeof!admin // ? 錯誤
typeof !admin // ? 正確
//comment // ? 錯誤
// comment // ? 正確
/*comment*/ // ? 錯誤
/* comment */ // ? 正確
const message = `Hello, ${ name }` // ? 錯誤
const message = `Hello, ${name}` // ? 正確
// ? 正確
const list = [1, 2, 3, 4]
function greet (name, options) { ... }
// ? 錯誤
const list = [1,2,3,4]
function greet (name,options) { ... }
// ? 正確
const value = 'hello world'
console.log(value)
// ? 錯誤
const value = 'hello world'
console.log(value)
function foo () {return true} // ? 錯誤
function foo () { return true } // ? 正確
if (condition) { return true } // ? 正確
function myFunc () /*<NBSP>*/{} // ? 錯誤
const obj = {
foo: 'foo'
,bar: 'bar' // ? 錯誤
}
const obj = {
foo: 'foo',
bar: 'bar' // ? 正確
}
console.log('hello') // ? 正確
console.
log('hello') // ? 錯誤
console
.log('hello') // ? 正確
console.log ('hello') // ? 錯誤
console.log('hello') // ? 正確
const obj = { 'key' : 'value' } // ? 錯誤
const obj = { 'key' :'value' } // ? 錯誤
const obj = { 'key':'value' } // ? 錯誤
const obj = { 'key': 'value' } // ? 正確
當(dāng)前作用域不需要改變的變量使用
const
,反之則使用let
const a = 'a'
a = 'b' // ? 錯誤,請使用 let 定義
let test = 'test'
var noVar = 'hello, world' // ? 錯誤,請使用 const/let 定義變量
// ? 正確
const silent = true
let verbose = true
// ? 錯誤
const silent = true, verbose = true
// ? 錯誤
let silent = true,
verbose = true
let name = 'John'
let name = 'Jane' // ? 錯誤
let name = 'John'
name = 'Jane' // ? 正確
let name = undefined // ? 錯誤
let name
name = 'value' // ? 正確
function my_function () { } // ? 錯誤
function myFunction () { } // ? 正確
const my_var = 'hello' // ? 錯誤
const myVar = 'hello' // ? 正確
function myFunction () {
const result = something() // ? 錯誤
}
name = name // ? 錯誤
const discount = .5 // ? 錯誤
const discount = 0.5 // ? 正確
// ? 正確
const x = 2
const message = 'hello, ' + name + '!'
// ? 錯誤
const x=2
const message = 'hello, '+name+'!'
const message = 'Hello \
world' // ? 錯誤
if (price === NaN) { } // ? 錯誤
if (isNaN(price)) { } // ? 正確
typeof name === undefined // ? 錯誤
typeof name === 'undefined' // ? 正確
const person = {
set name (value) { // ? 錯誤
this._name = value
}
}
const person = {
set name (value) {
this._name = value
},
get name () { // ? 正確
return this._name
}
}
const nums = new Array(1, 2, 3) // ? 錯誤
const nums = [1, 2, 3] // ? 正確
const { a: {} } = foo // ? 錯誤
const { a: { b } } = foo // ? 正確
const user = {
name: 'Jane Doe',
name: 'John Doe' // ? 錯誤
}
Object.prototype.age = 21 // ? 錯誤
let score = 100
function game () {
score: while (true) { // ? 錯誤
score -= 10
if (score > 0) continue score
break
}
}
const user = {
name: 'Jane Doe', age: 30,
username: 'jdoe86' // ? 錯誤
}
const user = { name: 'Jane Doe', age: 30, username: 'jdoe86' } // ? 正確
const user = {
name: 'Jane Doe',
age: 30,
username: 'jdoe86'
}
const user = { ['name']: 'John Doe' } // ? 錯誤
const user = { name: 'John Doe' } // ? 正確
function foo (n) {
if (n <= 0) return
arguments.callee(n - 1) // ? 錯誤
}
function foo (n) {
if (n <= 0) return
foo(n - 1)
}
function sum (a, b, a) { // ? 錯誤
// ...
}
function sum (a, b, c) { // ? 正確
// ...
}
const name = function () {
getName()
}.bind(user) // ? 錯誤
const name = function () {
this.getName()
}.bind(user) // ? 正確
eval( "var result = user." + propName ) // ? 錯誤
const result = user[propName] // ? 正確
const myFunc = (function () { }) // ? 錯誤
const myFunc = function () { } // ? 正確
function myFunc () { }
myFunc = myOtherFunc // ? 錯誤
setTimeout("alert('Hello world')") // ? 錯誤
setTimeout(function () { alert('Hello world') }) // ? 正確
if (authenticated) {
function setAuthUser () {} // ? 錯誤
}
const sum = new Function('a', 'b', 'return a + b') // ? 錯誤
let config = new Object() // ? 錯誤
const getName = function () { }() // ? 錯誤
const getName = (function () { }()) // ? 正確
const getName = (function () { })() // ? 正確
使用
Promise
或者async functions
來實現(xiàn)異步編程
function* helloWorldGenerator() { // ? 錯誤
yield 'hello';
yield 'world';
return 'ending';
}
const pattern = /\x1f/ // ? 錯誤
const pattern = /\x20/ // ? 正確
const regexp = /test value/ // ? 錯誤
const regexp = /test {3}value/ // ? 正確
const regexp = /test value/ // ? 正確
class animal {}
const dog = new animal() // ? 錯誤
class Animal {}
const dog = new Animal() // ? 正確
class Dog {}
Dog = 'Fido' // ? 錯誤
class Dog {
constructor () {
super() // ? 錯誤
}
}
class Dog extends Mammal {
constructor () {
super() // ? 正確
}
}
class Dog extends Animal {
constructor () {
this.legs = 4 // ? 錯誤
super()
}
}
class Car {
constructor () { // ? 錯誤
}
}
class Car {
constructor () { // ? 錯誤
super()
}
}
class Dog {
bark () {}
bark () {} // ? 錯誤
}
function Animal () {}
const dog = new Animal // ? 錯誤
const dog = new Animal() // ? 正確
new Character() // ? 錯誤
const character = new Character() // ? 正確
import { myFunc1 } from 'module'
import { myFunc2 } from 'module' // ? 錯誤
import { myFunc1, myFunc2 } from 'module' // ? 正確
import { config as config } from './config' // ? 錯誤
import { config } from './config' // ? 正確
function sum (a, b) {
return result = a + b // ? 錯誤
}
with (val) {...} // ? 錯誤
label:
while (true) {
break label // ? 錯誤
}
let undefined = 'value' // ? 錯誤
function doSomething () {
return true
console.log('never called') // ? 錯誤
}
例外: obj == null 可以用來檢查 null || undefined
if (name === 'John') // ? 正確
if (name == 'John') // ? 錯誤
if (name !== 'John') // ? 正確
if (name != 'John') // ? 錯誤
if (score === score) {} // ? 錯誤
// ? 正確
if (condition) {
// ...
} else {
// ...
}
// ? 錯誤
if (condition)
{
// ...
}
else
{
// ...
}
// ? 正確
if (options.quiet !== true) console.log('done')
// ? 正確
if (options.quiet !== true) {
console.log('done')
}
// ? 錯誤
if (options.quiet !== true)
console.log('done')
// ? 正確
const location = env.development ? 'localhost' : 'www.api.com'
// ? 正確
const location = env.development
? 'localhost'
: 'www.api.com'
// ? 錯誤
const location = env.development ?
'localhost' :
'www.api.com'
if (42 === age) { } // ? 錯誤
if (age === 42) { } // ? 正確
if (false) { // ? 錯誤
// ...
}
if (x === 0) { // ? 正確
// ...
}
while (true) { // ? 正確
// ...
}
for (let i = 0; i < items.length; j++) {...} // ? 錯誤
for (let i = 0; i < items.length; i++) {...} // ? 正確
let score = val ? val : 0 // ? 錯誤
let score = val || 0 // ? 正確
switch (id) {
case 1:
// ...
case 1: // ? 錯誤
}
switch (filter) {
case 1:
doSomething() // ? 錯誤
case 2:
doSomethingElse()
}
switch (filter) {
case 1:
doSomething()
break // ? 正確
case 2:
doSomethingElse()
}
switch (filter) {
case 1:
doSomething()
// fallthrough // ? 正確
case 2:
doSomethingElse()
}
const result = true
if (!!result) { // ? 錯誤
// ...
}
const result = true
if (result) { // ? 正確
// ...
}
if (doSomething(), !!test) {} // ? 錯誤
// ? 正確
run(function (err) {
if (err) throw err
window.alert('done')
})
// ? 錯誤
run(function (err) {
window.alert('done')
})
try {
// ...
} catch (e) {
e = 'new value' // ? 錯誤
}
try {
// ...
} catch (e) {
const newVal = 'new value' // ? 正確
}
throw 'error' // ? 錯誤
throw new Error('error') // ? 正確
try {
// ...
} catch (e) {
// ...
} finally {
return 42 // ? 錯誤
}
asyncTask('google.com').catch(err => console.log(err)) // ? 正確
Taro 中組件以類的形式進行創(chuàng)建,并且單個文件中只能存在單個組件
使用兩個空格進行縮進,不要混合使用空格與制表符作為縮進
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
class MyComponent extends Component {
render () {
return (
<View className='test'> // ? 正確
<Text>12</Text> // ? 錯誤
</View>
)
}
}
JSX 屬性均使用單引號
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
render () {
return (
<View className='test'> // ? 正確
<Text className="test_text">12</Text> // ? 錯誤
</View>
)
}
}
多個屬性,多行書寫,每個屬性占用一行,標(biāo)簽結(jié)束另起一行
// bad
<Foo superLongParam='bar'
anotherSuperLongParam='baz' />
// good
<Foo
superLongParam='bar'
anotherSuperLongParam='baz'
/>
// 如果組件的屬性可以放在一行就保持在當(dāng)前一行中
<Foo bar='bar' />
// 多行屬性采用縮進
<Foo
superLongParam='bar'
anotherSuperLongParam='baz'
>
<Quux />
</Foo>
終始在自閉合標(biāo)簽前面添加一個空格
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
屬性名稱始終使用駝峰命名法
// bad
<Foo
UserName='hello'
phone_number={12345678}
/>
// good
<Foo
userName='hello'
phoneNumber={12345678}
/>
用括號包裹多行 JSX 標(biāo)簽
// bad
render () {
return <MyComponent className='long body' foo='bar'>
<MyChild />
</MyComponent>
}
// good
render () {
return (
<MyComponent className='long body' foo='bar'>
<MyChild />
</MyComponent>
)
}
// good
render () {
const body = <div>hello</div>
return <MyComponent>{body}</MyComponent>
}
當(dāng)標(biāo)簽沒有子元素時,始終使用自閉合標(biāo)簽
// bad
<Foo className='stuff'></Foo>
// good
<Foo className='stuff' />
如果控件有多行屬性,關(guān)閉標(biāo)簽要另起一行
// bad
<Foo
bar='bar'
baz='baz' />
// good
<Foo
bar='bar'
baz='baz'
/>
在 Taro 組件中會包含類靜態(tài)屬性、類屬性、生命周期等的類成員,其書寫順序最好遵循以下約定(順序從上至下)
onClickSubmit()
或者 onChangeDescription()
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
class MyComponent extends Component {
render () {
return (
<View className='test'> // ? 正確
<Text>12</Text> // ? 錯誤
</View>
)
}
}
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>
)
}
}
<Hello class='foo' /> // ? 錯誤
<Hello id='foo' /> // ? 錯誤
<Hello style='foo' /> // ? 錯誤
<div className='foo'></div> // ? 錯誤
<span id='foo' /></span> // ? 錯誤
由于 this.setState 異步的緣故,這樣的做法會導(dǎo)致一些錯誤,可以通過給 this.setState 傳入函數(shù)來避免
this.setState({
value: this.state.value + 1
}) // ? 錯誤
this.setState(prevState => ({ value: prevState.value + 1 })) // ? 正確
list.map(item => {
return (
<View className='list_item' key={item.id}>{item.name}</View>
)
})
因為在
componentDidMount
中調(diào)用this.setState
會導(dǎo)致觸發(fā)更新
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>
)
}
}
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>
)
}
}
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
state = {
myTime: 12,
noUsed: true // ? 沒有用到
}
render () {
const { myTime } = this.state
return (
<View className='test'>
<Text className='test_text'>{myTime}</Text>
</View>
)
}
}
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
class MyComponent extends Component {
static defaultProps = {
isEnable: true
}
state = {
myTime: 12
}
render () {
const { isEnable } = this.props
const { myTime } = this.state
return (
<View className='test'>
{isEnable && <Text className='test_text'>{myTime}</Text>}
</View>
)
}
}
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
<View className='test'>
{isEnable && <Text className="test_text">{myTime}</Text>}
</View>
}
}
<Hello personal />
<Hello personal={false} />
屬性書寫不帶空格,如果屬性是一個對象,則對象括號旁邊需要帶上空格
<Hello name={ firstname } /> // ? 錯誤
<Hello name={ firstname} /> // ? 錯誤
<Hello name={firstname } /> // ? 錯誤
<Hello name={{ firstname: 'John', lastname: 'Doe' }} /> // ? 正確
在 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>
)
}
}
import Taro, { Component } from '@tarojs/taro'
import { View, Input } from '@tarojs/components'
import Tab from '../../components/Tab/Tab'
class MyComponent extends Component {
state = {
myTime: 12
}
clickHandler (e) {
console.log(e)
}
render () {
const { myTime } = this.state
return (
<View className='test'>
<Tab onChange={this.clickHandler} /> // ? 正確
<Text className='test_text'>{myTime}</Text>
</View>
)
}
}
Taro 在小程序端實際上把 JSX 轉(zhuǎn)換成了字符串模板,而一個原生 JSX 表達式實際上是一個 React/Nerv 元素(react-element)的構(gòu)造器,因此在原生 JSX 中你可以隨意地對一組 React 元素進行操作。但在 Taro 中你只能使用
map
方法,Taro 轉(zhuǎn)換成小程序中wx:for
以下代碼會被 ESLint 提示警告,同時在 Taro(小程序端)也不會有效:
test.push(<View />)
numbers.forEach(number => {
if (someCase) {
a = <View />
}
})
test.shift(<View />)
components.find(component => {
return component === <View />
})
components.some(component => component.constructor.__proto__ === <View />.constructor)
以下代碼不會被警告,也應(yīng)當(dāng)在 Taro 任意端中能夠運行:
numbers.filter(Boolean).map((number) => {
const element = <View />
return <View />
})
解決方案
先處理好需要遍歷的數(shù)組,然后再用處理好的數(shù)組調(diào)用 map
方法。
numbers.filter(isOdd).map((number) => <View />)
for (let index = 0; index < array.length; index++) {
// do you thing with array
}
const element = array.map(item => {
return <View />
})
更多建議: