小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

漸進(jìn)式入門教程 | Taro 文檔

 看見(jiàn)就非常 2020-08-13

我們將在這個(gè)教程開(kāi)發(fā)一個(gè)簡(jiǎn)單的 V2EX 論壇客戶端,你將在本節(jié)教程學(xué)到 Taro 的基礎(chǔ)知識(shí)、概念和部分優(yōu)化技巧。學(xué)習(xí)這些知識(shí)并不需要事先了解 Taro、小程序開(kāi)發(fā)或多端開(kāi)發(fā)。當(dāng)你掌握這些知識(shí)之后,你應(yīng)該可以快速高效地開(kāi)發(fā)多端應(yīng)用。

提示

這篇教程適合對(duì)喜歡邊學(xué)邊做或小程序開(kāi)發(fā)完全沒(méi)有了解的開(kāi)發(fā)者,如果你傾向于按部就班地學(xué)習(xí),請(qǐng)把 文檔 的內(nèi)容全部過(guò)一遍。當(dāng)你看完 文檔 就可以發(fā)現(xiàn):本篇教程就是文檔的漸近式索引。

這篇教程將分為四個(gè)章節(jié):

(如果你在大屏幕瀏覽時(shí))在本篇教程右邊的標(biāo)題目錄包含了所有章節(jié)的子目錄,你可以查看或?qū)Ш降侥愀信d趣的章節(jié)。

#前置知識(shí)

在本篇教程中我們假定你已經(jīng)對(duì) Web 前端開(kāi)發(fā)和 JavaScript 有一定的了解。我們將使用 React 和 Vue 兩個(gè)框架分別實(shí)現(xiàn)我們的應(yīng)用,在代碼實(shí)例中點(diǎn)擊 React 或 Vue 按鈕就可以切換不同的實(shí)現(xiàn)。如果你對(duì)兩個(gè)框架都不太熟悉,可以通過(guò) React 文檔Vue 文檔對(duì)照代碼實(shí)現(xiàn)。

在我們的代碼實(shí)現(xiàn)中還會(huì)使用部分 ES6 語(yǔ)法,你可以通過(guò) ES6 入門教程 查看或?qū)W習(xí) ES6 語(yǔ)法。

#環(huán)境準(zhǔn)備

目前 Taro 僅提供一種開(kāi)發(fā)方式:安裝 Taro 命令行工具(Taro CLI)進(jìn)行開(kāi)發(fā)。

Taro CLI 依賴于 Node.js 環(huán)境,所以在你的機(jī)器上必須安裝 Node.js 環(huán)境。安裝 Node.js 環(huán)境有很多種方法,如果你完全不了解 Node.js 可以訪問(wèn) Node.js 官網(wǎng) 下載一個(gè)可執(zhí)行程序進(jìn)行安裝。我們推薦安裝 LTS 版本的 Node.js(目前 LTS 版本是 v12)。

當(dāng)你的機(jī)器已經(jīng)存在了 Node.js 環(huán)境,可以通過(guò)在終端輸入命令 npm i -g @tarojs/cli 安裝 Taro CLI。安裝完畢之后,在終端輸入命令 taro,如果出現(xiàn)類似內(nèi)容就說(shuō)明安裝成功了:

?? Taro v3.0.0-beta.6
Usage: taro <command> [options]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init [projectName] Init a project with default templete
config <cmd> Taro config
create Create page for project
build Build a project with options
update Update packages of taro
convert Convert weapp to taro
info Diagnostics Taro env info
doctor Diagnose taro project
help [cmd] display help for [cmd]

#編輯器

我們推薦使用 VSCodeWebStorm(或其它支持 Web 開(kāi)發(fā)的 Jetbrains IDE)。

當(dāng)你使用 VSCode 時(shí),推薦安裝 ESLint 插件,如果你使用 TypeScript,別忘了配置 eslint.probe 參數(shù)。如果使用 Vue,推薦安裝 Vetur 插件。

如果你愿意花錢又懶得折騰可以選擇 WebStorm(或其它支持 Web 開(kāi)發(fā)的 Jetbrains IDE),基本不需要配置。

不管使用 VSCode 還是 WebStrom,安裝了上述插件之后使用 Taro 都實(shí)現(xiàn)自動(dòng)補(bǔ)全和代碼實(shí)時(shí)檢查(linting)的功能。

#終端

#macOS/Linux

在 *nix 系統(tǒng)中終端模擬器使用什么工具(Terminal/iTerm2/Konsole/Hyper/etc..)并不重要,但運(yùn)行 Taro CLI 的 shell 我們推薦使用 bashzsh。

#Windows

在 Windows 中我們推薦使用內(nèi)置的 cmdPowerShell。如果有條件推薦安裝 WSL 并使用 Linux 分發(fā)版的終端運(yùn)行 Taro CLI。由于 Taro 的開(kāi)發(fā)團(tuán)隊(duì)和 CI 都只運(yùn)行或測(cè)試 *nix 系統(tǒng),部分 Windows 極端情況或許沒(méi)能考慮到,導(dǎo)致出現(xiàn) Bug。

#尋求幫助

當(dāng)你在開(kāi)發(fā)過(guò)程中遇到問(wèn)題時(shí),你可以掃碼加入 微信開(kāi)發(fā)者群 提問(wèn),或者去 Taro 社區(qū) 提問(wèn)。當(dāng)你確信是 Taro 有 Bug 時(shí),請(qǐng)隨時(shí)向 Taro GitHub Issue 提出你的問(wèn)題。

#基礎(chǔ)教程

安裝好 Taro CLI 之后可以通過(guò) taro init 命令創(chuàng)建一個(gè)全新的項(xiàng)目,你可以根據(jù)你的項(xiàng)目需求填寫各個(gè)選項(xiàng),一個(gè)最小版本的 Taro 項(xiàng)目會(huì)包括以下文件:

├── babel.config.js # Babel 配置
├── .eslintrc.js # ESLint 配置
├── config # 編譯配置目錄
│ ├── dev.js # 開(kāi)發(fā)模式配置
│ ├── index.js # 默認(rèn)配置
│ └── prod.js # 生產(chǎn)模式配置
├── package.json # Node.js manifest
├── dist # 打包目錄
├── project.config.json # 小程序項(xiàng)目配置
├── src # 源碼目錄
│ ├── app.config.js # 全局配置
│ ├── app.css # 全局 CSS
│ ├── app.js # 入口組件
│ ├── index.html # H5 入口 HTML
│ └── pages # 頁(yè)面組件
│ └── index
│ ├── index.config.js # 頁(yè)面配置
│ ├── index.css # 頁(yè)面 CSS
│ └── index.jsx # 頁(yè)面組件,如果是 Vue 項(xiàng)目,此文件為 index.vue

我們以后將會(huì)講解每一個(gè)文件的作用,但現(xiàn)在,我們先把注意力聚焦在 src 文件夾,也就是源碼目錄:

#入口組件

每一個(gè) Taro 項(xiàng)目都有一個(gè)入口組件和一個(gè)入口配置,我們可以在入口組件中設(shè)置全局狀態(tài)/全局生命周期,一個(gè)最小化的入口組件會(huì)是這樣:

  • React

  • Vue

src/app.js
import React, { Component } from 'react'
import './app.css'
class App extends Component {
render () {
// this.props.children 是將要會(huì)渲染的頁(yè)面
return this.props.children
}
}
// 每一個(gè)入口組件都必須導(dǎo)出一個(gè) React 組件
export default App

每一個(gè)入口組件(例如 app.js)總是伴隨一個(gè)全局配置文件(例如 app.config.js),我們可以在全局配置文件中設(shè)置頁(yè)面組件的路徑、全局窗口、路由等信息,一個(gè)最簡(jiǎn)單的全局配置如下:

  • React

  • Vue

src/app.config.js
export default {
pages: [
'pages/index/index'
]
}

你可能會(huì)注意到,不管是 React 還是 Vue,兩者的全局配置是一樣的。這是在配置文件中,Taro 并不關(guān)心框架的區(qū)別,Taro CLI 會(huì)直接在編譯時(shí)在 Node.js 環(huán)境直接執(zhí)行全局配置的代碼,并把 export default 導(dǎo)出的對(duì)象序列化為一個(gè) JSON 文件。接下來(lái)我們要講到 頁(yè)面配置 也是同樣的執(zhí)行邏輯。

因此,我們必須保證配置文件是在 Node.js 環(huán)境中是可以執(zhí)行的,不能使用一些在 H5 環(huán)境或小程序環(huán)境才能運(yùn)行的包或者代碼,否則編譯將會(huì)失敗。

了解更多

Taro 的入口組件和全局配置規(guī)范是基于微信小程序而制定的,并對(duì)全平臺(tái)進(jìn)行統(tǒng)一。你可以通過(guò)訪問(wèn) React 入口組件Vue 入口組件,以及 全局配置 了解入口組件和全局配置的詳情。

#頁(yè)面組件

頁(yè)面組件是每一項(xiàng)路由將會(huì)渲染的頁(yè)面,Taro 的頁(yè)面默認(rèn)放在 src/pages 中,每一個(gè) Taro 項(xiàng)目至少有一個(gè)頁(yè)面組件。在我們生成的項(xiàng)目中有一個(gè)頁(yè)面組件:src/pages/index/index,細(xì)心的朋友可以發(fā)現(xiàn),這個(gè)路徑恰巧對(duì)應(yīng)的就是我們全局配置pages 字段當(dāng)中的值。一個(gè)簡(jiǎn)單的頁(yè)面組件如下:

  • React

  • Vue

src/pages/index/index.jsx
import { View } from '@tarojs/components'
class Index extends Component {
state = {
msg: 'Hello World!'
}
onReady () {
console.log('onReady')
}
render () {
return <View>{ this.state.msg }</View>
}
}
export default Index

這不正是我們熟悉的 React 和 Vue 組件嗎!但還是有兩點(diǎn)細(xì)微的差別:

  1. onReady 生命周期函數(shù)。這是來(lái)源于微信小程序規(guī)范的生命周期,表示組件首次渲染完畢,準(zhǔn)備好與視圖交互。Taro 在運(yùn)行時(shí)將大部分小程序規(guī)范頁(yè)面生命周期注入到了頁(yè)面組件中,同時(shí) React 或 Vue 自帶的生命周期也是完全可以正常使用的。

  2. View 組件。這是來(lái)源于 @tarojs/components 的跨平臺(tái)組件。相對(duì)于我們熟悉的 div、span 元素而言,在 Taro 中我們要全部使用這樣的跨平臺(tái)組件進(jìn)行開(kāi)發(fā)。

和入口組件一樣,每一個(gè)頁(yè)面組件(例如 index.vue)也會(huì)有一個(gè)頁(yè)面配置(例如 index.config.js),我們可以在頁(yè)面配置文件中設(shè)置頁(yè)面的導(dǎo)航欄、背景顏色等參數(shù),一個(gè)最簡(jiǎn)單的頁(yè)面配置如下:

src/pages/index/index.config.js
export default {
navigationBarTitleText: '首頁(yè)'
}
了解更多

Taro 的頁(yè)面鉤子函數(shù)和頁(yè)面配置規(guī)范是基于微信小程序而制定的,并對(duì)全平臺(tái)進(jìn)行統(tǒng)一。你可以通過(guò)訪問(wèn) React 頁(yè)面組件Vue 頁(yè)面組件 了解全部頁(yè)面鉤子函數(shù)和頁(yè)面配置規(guī)范。

#自定義組件

如果你看到這里,那不得不恭喜你,你已經(jīng)理解了 Taro 中最復(fù)雜的概念:入口組件和頁(yè)面組件,并了解了它們是如何(通過(guò)配置文件)交互的。接下來(lái)的內(nèi)容,如果你已經(jīng)熟悉了 React 或 Vue 以及 Web 開(kāi)發(fā)的話,那就太簡(jiǎn)單了:

我們先把首頁(yè)寫好,首頁(yè)的邏輯很簡(jiǎn)單:把論壇最新的帖子展示出來(lái)。

  • React

  • Vue

src/pages/index/index.jsx
import Taro from '@tarojs/taro'
import React from 'react'
import { View } from '@tarojs/components'
import { ThreadList } from '../../components/thread_list'
import api from '../../utils/api'
import './index.css'
class Index extends React.Component {
config = {
navigationBarTitleText: '首頁(yè)'
}
state = {
loading: true,
threads: []
}
async componentDidMount () {
try {
const res = await Taro.request({
url: api.getLatestTopic()
})
this.setState({
threads: res.data,
loading: false
})
} catch (error) {
Taro.showToast({
title: '載入遠(yuǎn)程數(shù)據(jù)錯(cuò)誤'
})
}
}
render () {
const { loading, threads } = this.state
return (
<View className='index'>
<ThreadList
threads={threads}
loading={loading}
/>
</View>
)
}
}
export default Index
了解更多

可能你會(huì)注意到在一個(gè) Taro 應(yīng)用中發(fā)送請(qǐng)求是 Taro.request() 完成的。和頁(yè)面配置、全局配置一樣,Taro 的 API 規(guī)范也是基于微信小程序而制定的,并對(duì)全平臺(tái)進(jìn)行統(tǒng)一。你可以通過(guò)在 API 文檔 找到所有 API。

在我們的首頁(yè)組件里,還引用了一個(gè) ThreadList 組件,我們現(xiàn)在來(lái)實(shí)現(xiàn)它:

  • React

  • Vue

src/components/thread_list.jsx
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Thread } from './thread'
import { Loading } from './loading'
import './thread.css'
class ThreadList extends React.Component {
static defaultProps = {
threads: [],
loading: true
}
render () {
const { loading, threads } = this.props
if (loading) {
return <Loading />
}
const element = threads.map((thread, index) => {
return (
<Thread
key={thread.id}
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
/>
)
})
return (
<View className='thread-list'>
{element}
</View>
)
}
}
export { ThreadList }
src/components/thread.jsx
import Taro, { eventCenter } from '@tarojs/taro'
import React from 'react'
import { View, Text, Navigator, Image } from '@tarojs/components'
import api from '../utils/api'
import { timeagoInst, Thread_DETAIL_NAVIGATE } from '../utils'
class Thread extends React.Component {
handleNavigate = () => {
const { tid, not_navi } = this.props
if (not_navi) {
return
}
eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.props)
// 跳轉(zhuǎn)到帖子詳情
Taro.navigateTo({
url: '/pages/thread_detail/thread_detail'
})
}
render () {
const { title, member, last_modified, replies, node, not_navi } = this.props
const time = timeagoInst.format(last_modified * 1000, 'zh')
const usernameCls = `author ${not_navi ? 'bold' : ''}`
return (
<View className='thread' onClick={this.handleNavigate}>
<View className='info'>
<View>
<Image src={member.avatar_large} className='avatar' />
</View>
<View className='middle'>
<View className={usernameCls}>
{member.username}
</View>
<View className='replies'>
<Text className='mr10'>
{time}
</Text>
<Text>
評(píng)論 {replies}
</Text>
</View>
</View>
<View className='node'>
<Text className='tag'>
{node.title}
</Text>
</View>
</View>
<Text className='title'>
{title}
</Text>
</View>
)
}
}
export { Thread }

這里可以發(fā)現(xiàn)我們把論壇帖子渲染邏輯拆成了兩個(gè)組件,并放在 src/components 文件中,因?yàn)檫@些組件是會(huì)在其它頁(yè)面中多次用到。拆分組件的力度是完全由開(kāi)發(fā)者決定的,Taro 并沒(méi)有規(guī)定組件一定要放在 components 文件夾,也沒(méi)有規(guī)定頁(yè)面一定要放在 pages 文件夾。

另外一個(gè)值得注意的點(diǎn)是:我們并沒(méi)有使用 div/span 這樣的 HTML 組件,而是使用了 View/Text 這樣的跨平臺(tái)組件。

了解更多

Taro 文檔的跨平臺(tái)組件庫(kù) 包含了所有組件參數(shù)和用法。但目前組件庫(kù)文檔中的參數(shù)和組件名都是針對(duì) React 的(除了 React 的點(diǎn)擊事件是 onClick 之外)。對(duì)于 Vue 而言,組件名和組件參數(shù)都采用短橫線風(fēng)格(kebab-case)的命名方式,例如:<picker-view indicator-class='myclass' />

#路由與 Tabbar

src/components/thread 組件中,我們通過(guò)

Taro.navigateTo({ url: '/pages/thread_detail/thread_detail' })

跳轉(zhuǎn)到帖子詳情,但這個(gè)頁(yè)面仍未實(shí)現(xiàn),現(xiàn)在我們?nèi)ト肟谖募渲靡粋€(gè)新的頁(yè)面:

src/app.config.js
export default {
pages: [
'pages/index/index',
'pages/thread_detail/thread_detail'
]
}

然后在路徑 src/pages/thread_detail/thread_detail 實(shí)現(xiàn)帖子詳情頁(yè)面,路由就可以跳轉(zhuǎn),我們整個(gè)流程就跑起來(lái)了:

  • React

  • Vue

src/pages/thread_detail/thread_detail
import Taro from '@tarojs/taro'
import React from 'react'
import { View, RichText, Image } from '@tarojs/components'
import { Thread } from '../../components/thread'
import { Loading } from '../../components/loading'
import api from '../../utils/api'
import { timeagoInst, GlobalState } from '../../utils'
import './index.css'
function prettyHTML (str) {
const lines = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
lines.forEach(line => {
const regex = new RegExp(`<${line}`, 'gi')
str = str.replace(regex, `<${line} class='line'`)
})
return str.replace(/<img/gi, '<img class='img'')
}
class ThreadDetail extends React.Component {
state = {
loading: true,
replies: [],
content: '',
thread: {}
} as IState
config = {
navigationBarTitleText: '話題'
}
componentWillMount () {
this.setState({
thread: GlobalState.thread
})
}
async componentDidMount () {
try {
const id = GlobalState.thread.tid
const [{ data }, { data: [ { content_rendered } ] } ] = await Promise.all([
Taro.request({
url: api.getReplies({
'topic_id': id
})
}),
Taro.request({
url: api.getTopics({
id
})
})
])
this.setState({
loading: false,
replies: data,
content: prettyHTML(content_rendered)
})
} catch (error) {
Taro.showToast({
title: '載入遠(yuǎn)程數(shù)據(jù)錯(cuò)誤'
})
}
}
render () {
const { loading, replies, thread, content } = this.state
const replieEl = replies.map((reply, index) => {
const time = timeagoInst.format(reply.last_modified * 1000, 'zh')
return (
<View className='reply' key={reply.id}>
<Image src={reply.member.avatar_large} className='avatar' />
<View className='main'>
<View className='author'>
{reply.member.username}
</View>
<View className='time'>
{time}
</View>
<RichText nodes={reply.content} className='content' />
<View className='floor'>
{index + 1} 樓
</View>
</View>
</View>
)
})
const contentEl = loading
? <Loading />
: (
<View>
<View className='main-content'>
<RichText nodes={content} />
</View>
<View className='replies'>
{replieEl}
</View>
</View>
)
return (
<View className='detail'>
<Thread
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
not_navi={true}
/>
{contentEl}
</View>
)
}
}
export default ThreadDetail

到目前為止,我們已經(jīng)實(shí)現(xiàn)了這個(gè)應(yīng)用的所有邏輯,除去「節(jié)點(diǎn)列表」頁(yè)面(在進(jìn)階指南我們會(huì)討論這個(gè)頁(yè)面組件)之外,剩下的頁(yè)面都可以通過(guò)我們已經(jīng)講解過(guò)的組件或頁(yè)面快速抽象完成。按照我們的計(jì)劃,這個(gè)應(yīng)用會(huì)有五個(gè)頁(yè)面,分別是:

  1. 首頁(yè),展示最新帖子(已完成)

  2. 節(jié)點(diǎn)列表

  3. 熱門帖子(可通過(guò)組件復(fù)用)

  4. 節(jié)點(diǎn)帖子 (可通過(guò)組件復(fù)用)

  5. 帖子詳情 (已完成)

其中前三個(gè)頁(yè)面我們可以把它們規(guī)劃在 tabBar 里,tabBar 是 Taro 內(nèi)置的導(dǎo)航欄,可以在 app.config.js 配置,配置完成之后處于的 tabBar 位置的頁(yè)面會(huì)顯示一個(gè)導(dǎo)航欄。最終我們的 app.config.js 會(huì)是這樣:

app.config.js
export default {
pages: [
'pages/index/index',
'pages/nodes/nodes',
'pages/hot/hot',
'pages/node_detail/node_detail',
'pages/thread_detail/thread_detail'
],
tabBar: {
list: [{
'iconPath': 'resource/latest.png',
'selectedIconPath': 'resource/lastest_on.png',
pagePath: 'pages/index/index',
text: '最新'
}, {
'iconPath': 'resource/hotest.png',
'selectedIconPath': 'resource/hotest_on.png',
pagePath: 'pages/hot/hot',
text: '熱門'
}, {
'iconPath': 'resource/node.png',
'selectedIconPath': 'resource/node_on.png',
pagePath: 'pages/nodes/nodes',
text: '節(jié)點(diǎn)'
}],
'color': '#000',
'selectedColor': '#56abe4',
'backgroundColor': '#fff',
'borderStyle': 'white'
},
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'V2EX',
navigationBarTextStyle: 'black'
}
}

#項(xiàng)目進(jìn)階與優(yōu)化

#狀態(tài)管理

在我們實(shí)現(xiàn)帖子組件(src/components/thread)時(shí),通過(guò) Taro 內(nèi)置的 eventCenter 發(fā)起了一個(gè)事件,把當(dāng)前帖子的數(shù)據(jù)注入到一個(gè)全局的 GlobalState 中,然后在帖子詳情頁(yè)面再?gòu)?GlobalState 取出當(dāng)前帖子的數(shù)據(jù)——這種簡(jiǎn)單的發(fā)布/訂閱模式在處理簡(jiǎn)單邏輯時(shí)非常有效且清晰。

一旦我們的業(yè)務(wù)邏輯變得復(fù)雜,一個(gè)簡(jiǎn)單的發(fā)布訂閱機(jī)制綁定到一個(gè)全局的 state 可能就會(huì)導(dǎo)致我們的數(shù)據(jù)流變得難以追蹤。好在這個(gè)問(wèn)題不管是在 React 還是 Vue 社區(qū)中都有很好的解決方案。我們會(huì)使用這兩個(gè)社區(qū)最熱門的狀態(tài)管理工具:ReduxVuex 來(lái)解決這個(gè)問(wèn)題。

  • Redux

  • Vuex

首先安裝 reduxreact-redux:

npm i redux react-redux

在入口文件使用 react-reduxProvider 注入 context 到我們的應(yīng)用:

src/app.js
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux';
import './app.css'
const reducers = combineReducers({
thread: (state = {}, action) => {
if (action.type === 'SET_CURRENT_THREAD') {
return {
...state,
...action.thread
}
}
return state
}
})
const store = createStore(reducers)
class App extends Component {
render () {
// this.props.children 是將要會(huì)渲染的頁(yè)面
return (
<Provider store={store}>
{this.props.children}
</Provider>
)
}
}
export default App

然后在帖子組件中我們就可以通過(guò) connect 一個(gè) dispatch 設(shè)置當(dāng)前的帖子:

src/components/thread.jsx
- eventCenter.trigger(Thread_DETAIL_NAVIGATE, this.props)
+ this.props.setThread(this.props)
- export default Thread
+ const mapDispatchToProps = dispatch => {
+ return {
+ setThread: thread => dispatch({ type: 'SET_CURRENT_THREAD', thread })
+ }
+ }
+ export default connect(null, mapDispatchToProps)(Thread)

在帖子詳情組件中通過(guò) connect 一個(gè) mapStateToProps 獲取當(dāng)前帖子的數(shù)據(jù):

src/components/thread_detail.jsx
- const id = GlobalState.thread.tid
+ const id = this.props.thread.tid
- export default ThreadDetail
+ function mapStateToProps(state) {
+ return { thread: state.thread }
+ }
+ export default connect(mapStateToProps)(ThreadDetail)
請(qǐng)注意

此教程演示的是 Redux 極簡(jiǎn)用法,而非最佳實(shí)踐。詳情請(qǐng)?jiān)L問(wèn) Redux 文檔react-redux 文檔。

#其它狀態(tài)管理工具

原理上來(lái)說(shuō),Taro 可以支持任何兼容 React 或 Vue 的狀態(tài)管理工具,使用這類工具通常都會(huì)要求在入口組件注入 context,而在 Taro 中入口文件是不能渲染 UI 的。只要注意這點(diǎn)即可。

在 Vue 生態(tài)圈我們推薦使用 Vuex。React 生態(tài)圈狀態(tài)管理工具百花齊放,考慮到使用 Taro 的開(kāi)發(fā)者很多應(yīng)用會(huì)編譯到小程序,我們推薦幾個(gè)在性能或體積上有優(yōu)勢(shì)的狀態(tài)管理工具:

  • mobx-react: 和 Vuex 一樣響應(yīng)式的狀態(tài)管理工具

  • unstaged: 基于 React Hooks 的極簡(jiǎn)狀態(tài)管理工具,壓縮體積只有 200 字節(jié)

  • Recoil: Facebook 推出的基于 React Hooks 的狀態(tài)管理工具

#CSS 工具

在 Taro 中,我們可以自由地使用 CSS 預(yù)處理器和后處理器,使用的方法也非常簡(jiǎn)單,只要在編譯配置添加相關(guān)的插件即可:

config/index.js
const config = {
projectName: 'v2ex',
date: '2018-8-3',
designWidth: 750,
sourceRoot: 'src',
outputRoot: 'dist',
plugins: [
'@tarojs/plugin-sass', // 使用 Sass
// '@tarojs/plugin-less', // 使用 Less
// '@tarojs/plugin-stylus', // 使用 Stylus
],
defineConstants: {
},
mini: {
},
h5: {
publicPath: '/',
staticDirectory: 'static',
module: {
postcss: {
autoprefixer: {
enable: true
}
}
}
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}
了解更多

除了 CSS 預(yù)處理器之外,Taro 還支持 CSS ModulesCSS-in-JS。原理上還支持更多 CSS 工具,我們將在 自定義編譯 繼續(xù)討論這個(gè)問(wèn)題。

#渲染 HTML

在帖子詳情組件(ThreadDetail)中,我們使用了內(nèi)置組件 RichText 來(lái)渲染 HTML,但這個(gè)組件的兼容性不好,無(wú)法在所有端都正常使用,某些特定的 HTML 元素也無(wú)法渲染。

幸運(yùn)的是,Taro 內(nèi)置了 HTML 渲染,使用方法也和 React/Vue 在 Web 開(kāi)發(fā)中沒(méi)什么區(qū)別:

  • React

  • Vue

src/pages/thread_detail/thread_detail.jsx
- <RichText nodes={reply.content} className='content' />
+ <View dangerouslySetInnerHTML={{ __html: reply.content }} className='content'></View>
了解更多

Taro 內(nèi)置的 HTML 渲染功能不僅可以按 Web 開(kāi)發(fā)的方式去使用,也支持自定義樣式、自定義渲染、自定義事件這樣的高級(jí)功能。你可以訪問(wèn) HTML 渲染文檔 了解更多。

#性能優(yōu)化

#虛擬列表

在帖子列表組件(ThreadList)中,我們直接渲染從遠(yuǎn)程得來(lái)的數(shù)據(jù)。這樣做沒(méi)有什么問(wèn)題,但如果我們的數(shù)據(jù)非常龐大,或者列表渲染的 DOM 結(jié)構(gòu)異常復(fù)雜,這就可能會(huì)產(chǎn)生性能問(wèn)題。

為了解決這一問(wèn)題,Taro 內(nèi)置了虛擬列表(VirtualList)功能,比起全量渲染所有列表數(shù)據(jù),我們只需要渲染當(dāng)前可視區(qū)域(visable viewport)的視圖:

  • React

  • Vue

src/pages/thread_detail/thread_list.jsx
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Thread } from './thread'
import { Loading } from './loading'
import VirtualList from `@tarojs/components/virtual-list`
import './thread.css'
const Row = React.memo(({ thread }) => {
return (
<Thread
key={thread.id}
node={thread.node}
title={thread.title}
last_modified={thread.last_modified}
replies={thread.replies}
tid={thread.id}
member={thread.member}
/>
)
})
class ThreadList extends React.Component {
static defaultProps = {
threads: [],
loading: true
}
render () {
const { loading, threads } = this.props
if (loading) {
return <Loading />
}
const element = (
<VirtualList
height={800} /* 列表的高度 */
width='100%' /* 列表的寬度 */
itemData={threads} /* 渲染列表的數(shù)據(jù) */
itemCount={threads.length} /* 渲染列表的長(zhǎng)度 */
itemSize={100} /* 列表單項(xiàng)的高度 */
>
{Row} /* 列表單項(xiàng)組件,這里只能傳入一個(gè)組件 */
</VirtualList>
)
return (
<View className='thread-list'>
{element}
</View>
)
}
}
export { ThreadList }
了解更多

在文檔虛擬列表 你可以找到虛擬列表的一些高級(jí)用法,例如:無(wú)限滾動(dòng)、滾動(dòng)偏移、滾動(dòng)事件等。

#預(yù)渲染

現(xiàn)在我們來(lái)實(shí)現(xiàn)最后一個(gè)頁(yè)面:節(jié)點(diǎn)列表頁(yè)面。這個(gè)頁(yè)面本質(zhì)說(shuō)就是渲染一個(gè)存在本地的巨大列表:

  • React

  • Vue

src/pages/nodes/nodes.jsx
import React from 'react'
import { View, Text, Navigator } from '@tarojs/components'
import allNodes from './all_node'
import api from '../../utils/api'
import './nodes.css'
function Nodes () {
const element = allNodes.map(item => {
return (
<View key={item.title} className='container'>
<View className='title'>
<Text style='margin-left: 5px'>{item.title}</Text>
</View>
<View className='nodes'>
{item.nodes.map(node => {
return (
<Navigator
className='tag'
url={`/pages/node_detail/node_detail${api.queryString(node)}`}
key={node.full_name}
>
<Text>{node.full_name}</Text>
</Navigator>
)
})}
</View>
</View>
)
})
return <View className='node-container'>{element}</View>
}
export default Nodes

這個(gè)時(shí)候我們整個(gè)應(yīng)用就完成了。但如果你把這個(gè)應(yīng)用放在真機(jī)小程序中,尤其是一些性能不高的真機(jī)中,切換到此頁(yè)面的時(shí)間可能會(huì)比較長(zhǎng),會(huì)有一段白屏?xí)r間。

這是由于 Taro 的渲染機(jī)制導(dǎo)致的:在頁(yè)面初始化時(shí),原生小程序可以從本地直接取數(shù)據(jù)渲染,但 Taro 會(huì)把初始數(shù)據(jù)通過(guò) React/Vue 渲染成一顆 DOM 樹(shù),然后將這顆 DOM 樹(shù)序列化之后交給小程序渲染。也就是說(shuō),比起原生小程序 Taro 會(huì)在頁(yè)面初始化時(shí)多一次調(diào)用 setData 函數(shù)的支出——而大部分小程序的性能問(wèn)題是 setData 數(shù)據(jù)過(guò)大導(dǎo)致的。

為了解決這個(gè)問(wèn)題,Taro 引入了一種名為預(yù)渲染(Prerender)的技術(shù),和服務(wù)端渲染一樣,在 Taro CLI 直接將要渲染的頁(yè)面轉(zhuǎn)換為 wxml 字符串,這樣就獲得了與原生小程序一致甚至更快的速度。

使用預(yù)渲染也非常簡(jiǎn)單,我們只要進(jìn)行簡(jiǎn)單的配置即可:

config/prod.js
const config = {
...
mini: {
prerender: {
include: ['pages/nodes/nodes'], // `pages/nodes/nodes` 也會(huì)參與 prerender
}
}
};
// 我們這里在編譯生產(chǎn)模式時(shí)才開(kāi)啟預(yù)渲染
// 如果需要開(kāi)發(fā)時(shí)也開(kāi)啟,那就把配置放在 `config/index` 或 `config/dev`
module.exports = config
了解更多

預(yù)渲染的配置支持條件渲染頁(yè)面、條件渲染邏輯、自定義渲染函數(shù)等功能,詳情可以訪問(wèn)預(yù)渲染文檔。

#打包體積

默認(rèn)而言使用生產(chǎn)模式打包,Taro 就會(huì)給你優(yōu)化打包體積。但值得注意,Taro 默認(rèn)的打包配置是為了讓多數(shù)項(xiàng)目和需求都可以運(yùn)行,而不是針對(duì)任何項(xiàng)目的最優(yōu)選擇。因此你可以在 Taro 配置的基礎(chǔ)之上再針對(duì)自己的項(xiàng)目進(jìn)行優(yōu)化。

#JavaScript

在 Taro 應(yīng)用中,所有 Java(Type)Script 都是通過(guò) babel.config.js 配置的,具體來(lái)說(shuō)是使用 babel-prest-taro 這個(gè) Babel 插件編譯的。

默認(rèn)而言 Taro 會(huì)兼容所有 @babel/preset-env 支持的語(yǔ)法,并兼容到 iOS 9Android 5,如果你不需要那么高的兼容性,或者不需要某些 ES2015+ 語(yǔ)法支持,可以自行配置 babel.config.js 達(dá)到縮小打包體積效果。

例如我們可以把兼容性提升到 iOS 12

babel.config.js
// babel.config.js
module.exports = {
presets: [
['taro', {
targets: {
ios: '12'
}
}]
]
}

你可以訪問(wèn) Babel 文檔 了解更多自定義配置的信息。

#打包體積分析

Taro 使用 Webpack 作為內(nèi)部的打包系統(tǒng),有時(shí)候當(dāng)我們的業(yè)務(wù)代碼使用了 require 語(yǔ)法或者 import default 語(yǔ)法,Webpack 并不能給我們提供 tree-shaking 的效果。在這樣的情況下我們通過(guò) webpack-bundle-analyzer 來(lái)分析我們依賴打包體積,這個(gè)插件會(huì)在瀏覽器打開(kāi)一個(gè)可視化的圖表頁(yè)面告訴我們引用各個(gè)包的體積。

首先安裝 webpack-bundle-analyzer 依賴:

npm install webpack-bundle-analyzer -D

然后在 mini.webpackChain 中添加如下配置:

config/index
const config = {
...
mini: {
webpackChain (chain, webpack) {
chain.plugin('analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
}
}
}

運(yùn)行編譯命令完成之后就可以看到各文件依賴關(guān)系及體積。

你可以訪問(wèn) webpack-bundle-analyzer 文檔了解詳細(xì)的用法。

#分包

在一些情況,我們希望我們的頁(yè)面只有當(dāng)用到時(shí)才按需進(jìn)行加載。這種情況在 Taro 應(yīng)用被稱為分包,分包的使用也非常簡(jiǎn)單,只需要通過(guò)配置入口文件 app.config.js 即可。

假設(shè)我們需要把剛剛實(shí)現(xiàn)預(yù)渲染的所有節(jié)點(diǎn)頁(yè)面進(jìn)行分包:

src/app.config.js
export default {
pages: [
'pages/index/index',
// 'pages/nodes/nodes', 把要分包的頁(yè)面從 `pages` 字段中刪除
'pages/hot/hot',
'pages/node_detail/node_detail',
'pages/thread_detail/thread_detail'
],
// 在 `subpackages` 字段添加分包
'subpackages': [
{
'root': 'pages',
'pages': [
'nodes/nodes'
]
}
]
tabBar: {
list: [{
'iconPath': 'resource/latest.png',
'selectedIconPath': 'resource/lastest_on.png',
pagePath: 'pages/index/index',
text: '最新'
}, {
'iconPath': 'resource/hotest.png',
'selectedIconPath': 'resource/hotest_on.png',
pagePath: 'pages/hot/hot',
text: '熱門'
}, {
'iconPath': 'resource/node.png',
'selectedIconPath': 'resource/node_on.png',
pagePath: 'pages/nodes/nodes',
text: '節(jié)點(diǎn)'
}],
'color': '#000',
'selectedColor': '#56abe4',
'backgroundColor': '#fff',
'borderStyle': 'white'
},
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'V2EX',
navigationBarTextStyle: 'black'
}
}

#自定義編譯

在特定的情況下,Taro 自帶的編譯系統(tǒng)沒(méi)有辦法滿足我們的編譯需求,這時(shí) Taro 提供了兩種拓展編譯的方案:

#使用 Webpack 進(jìn)行拓展

打包體積分析 中我們?cè)?mini.webpackChain 添加了一個(gè) Webpack 插件,達(dá)到了打包體積/依賴分析的效果。

事實(shí)上通過(guò) mini.webpackChain 這個(gè)配置我們可以幾乎使用任何 Webpack 生態(tài)的插件和 loader,例如我們想使用 CoffeeScript 來(lái)進(jìn)行開(kāi)發(fā):

config/index
const config = {
...
mini: {
webpackChain (chain, webpack) {
chain.merge({
module: {
rule: {
test: /\.coffee$/,
use: [ 'coffee-loader' ]
}
}
})
}
}
}

同樣,之前我們提到過(guò)的 CSS Modules 也可以通過(guò) Webpack 的形式進(jìn)行拓展支持。詳情可以訪問(wèn) webpack-chain 文檔了解詳細(xì)的用法。

#使用插件化系統(tǒng)進(jìn)行拓展

在 [CSS 工具](#CSS 工具) 我們已經(jīng)使用了名為 @tarojs/plugin-sass 的插件來(lái)實(shí)現(xiàn)對(duì) Sass 的支持。比起使用 Webpack 拓展編譯,Taro 的插件功能不用在每個(gè)端都對(duì) Webpack 進(jìn)行配置,只用使用插件即可。

除此之外,Taro 的插件化功能還可以拓展 Taro CLI 編譯命令,拓展編譯流程,拓展編譯平臺(tái),你可以訪問(wèn) 插件功能文檔 了解更多自定義配置的信息。

了解更多

除了以上兩種方式外,Taro 還提供大量的編譯相關(guān)選項(xiàng),你可以訪問(wèn) 編譯配置詳情 文檔了解更多。

#多端開(kāi)發(fā)

#跨平臺(tái)開(kāi)發(fā)

在某些情況下,不同平臺(tái)的表現(xiàn)或業(yè)務(wù)邏輯有質(zhì)的不同。在這樣的情況下我們是沒(méi)有辦法做到「一套代碼走天下」的。

例如我們正在實(shí)現(xiàn) V2EX 論壇應(yīng)用,當(dāng)前的 API 沒(méi)有辦法在瀏覽器中跨域調(diào)用,因此我們需要在 H5 端使用另一份 API。我們可以通過(guò)內(nèi)置環(huán)境變量來(lái)解決:

- import api from '../../utils/api'
// 我們可以根據(jù)不同的平臺(tái),引入不同的 API
+ let api
+ if (process.env.TARO_ENV === 'weapp') {
+ api = require('../../utils/api-weapp')
+ } else if (process.env.TARO_ENV === 'h5') {
+ api = require('../../utils/api-h5')
+ }

Taro 還提供了統(tǒng)一接口的多端文件,通過(guò)不同的命名方式尋找依賴,在這類情況下,我們可以保留:

import api from '../../utils/api'

語(yǔ)句原封不動(dòng),修改我們的文件結(jié)構(gòu),在文件名和后綴名之間加上平臺(tái)的名字:

.
└── utils
├── api.h5.js
├── api.weapp.js
└── index.js
了解更多

除了「內(nèi)置環(huán)境變量」和「統(tǒng)一接口的多端文件」之外,Taro 還提供了別的跨平臺(tái)開(kāi)發(fā)解決方案,你可以訪問(wèn)文檔 跨平臺(tái)開(kāi)發(fā) 了解更多。

#同步調(diào)試

默認(rèn)情況下,Taro 會(huì)把各端打包后的文件都放在 dist 目錄。如果你想要多端同步調(diào)試的話先編譯后的文件就會(huì)被后編譯好的文件覆蓋。

但我們可以通過(guò)修改編譯配置的 outputRoot 達(dá)到多端同步調(diào)試的目的:

config/index.js
const config = {
outputRoot: `dist/${process.env.TARO_ENV}`
}

在這樣的配置下,微信小程序編譯后的目錄就會(huì)是 dist/weapp,H5 編譯后目錄就會(huì)是 dist/h5。

#使用原生小程序組件

某些情況下我們需要復(fù)用小程序既有生態(tài),而小程序的組件/庫(kù)通常是針對(duì)特定小程序?qū)懙?,并不能直接?Taro 上使用,需要一些額外的操作。

例如我們的論壇應(yīng)用,在帖子詳情可能服務(wù)端返回的是 MarkDown 格式,我們就需要 towxml 來(lái)渲染的我們的帖子:

首先我們需要在帖子詳情頁(yè)面的配置文件中引用 towxml:

export default {
'usingComponents': {
'towxml':'../../towxml/towxml'
}
}

然后使用 towxml 組件,這里必須記住的是不管是 React 還是 Vue,原生小程序組件聲明需要是小寫的

  • React

  • Vue

src/pages/thread_detail/thread_detail.jsx
- <View dangerouslySetInnerHTML={{ __html: reply.content }} className='content'></View>
+ <towxml nodes='{{reply.content}}' />

最后按照 towxml 文檔調(diào)用即可。

請(qǐng)注意

一旦使用了原生小程序組件,Taro 應(yīng)用就失去了跨端的能力。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多