我們將在這個(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)用。 提示這篇教程將分為四個(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ò)在終端輸入命令 ?? 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] #編輯器我們推薦使用 VSCode 或 WebStorm(或其它支持 Web 開(kāi)發(fā)的 Jetbrains IDE)。 當(dāng)你使用 VSCode 時(shí),推薦安裝 ESLint 插件,如果你使用 TypeScript,別忘了配置 如果你愿意花錢又懶得折騰可以選擇 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 我們推薦使用 #Windows在 Windows 中我們推薦使用內(nèi)置的 #尋求幫助當(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ò) ├── 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)在,我們先把注意力聚焦在 #入口組件每一個(gè) Taro 項(xiàng)目都有一個(gè)入口組件和一個(gè)入口配置,我們可以在入口組件中設(shè)置全局狀態(tài)/全局生命周期,一個(gè)最小化的入口組件會(huì)是這樣:
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è)入口組件(例如
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í)行全局配置的代碼,并把 因此,我們必須保證配置文件是在 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/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ì)微的差別:
和入口組件一樣,每一個(gè)頁(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)。
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)求是 在我們的首頁(yè)組件里,還引用了一個(gè)
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è)組件,并放在 另外一個(gè)值得注意的點(diǎn)是:我們并沒(méi)有使用 了解更多Taro 文檔的跨平臺(tái)組件庫(kù) 包含了所有組件參數(shù)和用法。但目前組件庫(kù)文檔中的參數(shù)和組件名都是針對(duì) React 的(除了 React 的點(diǎn)擊事件是 #路由與 Tabbar在 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 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è)面,分別是:
其中前三個(gè)頁(yè)面我們可以把它們規(guī)劃在 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)帖子組件( 一旦我們的業(yè)務(wù)邏輯變得復(fù)雜,一個(gè)簡(jiǎn)單的發(fā)布訂閱機(jī)制綁定到一個(gè)全局的
首先安裝 npm i redux react-redux 在入口文件使用 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ò) 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ò) 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)注意此教程演示的是 #其它狀態(tài)管理工具原理上來(lái)說(shuō),Taro 可以支持任何兼容 React 或 Vue 的狀態(tài)管理工具,使用這類工具通常都會(huì)要求在入口組件注入 在 Vue 生態(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 Modules 和 CSS-in-JS。原理上還支持更多 CSS 工具,我們將在 自定義編譯 繼續(xù)討論這個(gè)問(wèn)題。 #渲染 HTML在帖子詳情組件( 幸運(yùn)的是,Taro 內(nèi)置了 HTML 渲染,使用方法也和 React/Vue 在 Web 開(kāi)發(fā)中沒(méi)什么區(qū)別:
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)化#虛擬列表在帖子列表組件( 為了解決這一問(wèn)題,Taro 內(nèi)置了虛擬列表(
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è)存在本地的巨大列表:
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)用 為了解決這個(gè)問(wèn)題,Taro 引入了一種名為預(yù)渲染(Prerender)的技術(shù),和服務(wù)端渲染一樣,在 Taro CLI 直接將要渲染的頁(yè)面轉(zhuǎn)換為 使用預(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ò) 默認(rèn)而言 Taro 會(huì)兼容所有 例如我們可以把兼容性提升到 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ù)代碼使用了 首先安裝 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ò)配置入口文件 假設(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ò) config/index const config = { ... mini: { webpackChain (chain, webpack) { chain.merge({ module: { rule: { test: /\.coffee$/, use: [ 'coffee-loader' ] } } }) } } } 同樣,之前我們提到過(guò)的 #使用插件化系統(tǒng)進(jìn)行拓展在 [CSS 工具](#CSS 工具) 我們已經(jīng)使用了名為 除此之外,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ì)把各端打包后的文件都放在 但我們可以通過(guò)修改編譯配置的 config/index.js const config = { outputRoot: `dist/${process.env.TARO_ENV}` } 在這樣的配置下,微信小程序編譯后的目錄就會(huì)是 #使用原生小程序組件某些情況下我們需要復(fù)用小程序既有生態(tài),而小程序的組件/庫(kù)通常是針對(duì)特定小程序?qū)懙?,并不能直接?Taro 上使用,需要一些額外的操作。 例如我們的論壇應(yīng)用,在帖子詳情可能服務(wù)端返回的是 MarkDown 格式,我們就需要 首先我們需要在帖子詳情頁(yè)面的配置文件中引用 export default { 'usingComponents': { 'towxml':'../../towxml/towxml' } } 然后使用
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)用就失去了跨端的能力。 |
|
來(lái)自: 看見(jiàn)就非常 > 《待分類》