正當移動網際網路進入白熱化階段時,以微信小程式為代表的一類“輕應用”異軍突起。它們無需下載,使用方便,“用完即走”,同時功能也較為完備,一經推出即得到了各大平臺和及使用者的熱烈追捧。但是問題也隨之而來——開發者們要同時維護 Web 端、移動端、微信小程式、支付寶小程式等等多套使用者介面,其維護成本可以想象。作為一個優秀的多端統一開發解決方案,Taro 的出現則改變了這一情況。正值 Taro 2.x 進入 beta 階段,讓我們沏上一杯茶,開始我們的 Taro 多端小程式開發之旅吧。
起步
對於國內 React 開發者來說,Taro 的出現無疑是福音——它能夠讓我們用熟悉的 React 程式碼去搭建各類小程式,並且一份程式碼可以編譯成多個平臺的應用(目前包括微信小程式、支付寶小程式、React Native、H5 等等)。隨著 Taro 的不斷進化,它對 React 程式碼的支援程度越來越好,所支援的目標平臺也越來越多,學習的價值自然不必多言。正值 Taro 進入 2.0.0 版本的 beta 階段,我們在這一篇教程將手把手帶你實現一個能夠部署到多端的小程式,讓你感受 Taro 的強大與魅力!
在這一系列教程中,我們將構建一個多端小程式應用——奧特曼俱樂部(Ultraman Club,簡稱 UltraClub),一個支援多端登入(微信和支付寶)的類似貼吧的小程式。我們還提供了專案倉庫的 GitHub 地址。專案目前還在開發階段,您可以跳轉到任意一次 commit 檢視當前步驟的所有程式碼哦。
我們將構建什麼?
在完成這篇教程後,專案的 GIF 動圖展示如下:
前提條件
在閱讀這篇教程之前,我們希望你已經具備以下知識:
- 瞭解 HTML、CSS、JavaScript 的基礎知識,如果瞭解 Sass 就更好了
- 瞭解 React 框架的基礎知識,可以參考這篇教程進行學習;如果接觸過 React Native 以及 Hooks 則更好了
- 瞭解並已經安裝好 Node 與 npm,可以參考這篇教程進行學習
除此之外,你還需要下載並安裝微信開發者工具,這裡是下載地址。
本文所涉及的原始碼都放在了 Github 上,如果您覺得我們寫得還不錯,希望您能給❤️這篇文章點贊+Github倉庫加星❤️哦~
用 Taro 腳手架初始化專案
首先安裝 Taro CLI:
npm install -g @tarojs/cli
複製程式碼
然後建立我們的專案:
taro init ultra-club
複製程式碼
之後會出現一系列選項,按照下圖所示進行選擇即可(CSS 前處理器選擇 Sass,模板選擇“預設模板”,老司機可自行選擇使用 TS):
提示
本專案使用 Sass 主要是為了相容 taro-ui 的樣式,並沒有使用到 Sass 的高階特性,如果你不熟悉的話也不用擔心哦,就當成是常規的 CSS 程式碼。
進入到我們的專案目錄 ultra-club 之後,可以看到專案模板包括以下檔案:
.
├── config # 專案配置
│ ├── dev.js # 開發環境配置檔案
│ ├── index.js # 主配置檔案
│ └── prod.js # 生產環境配置檔案
├── package.json
├── project.config.json # 微信小程式專案配置
└── src # 專案原始碼目錄
├── app.scss # 根元件樣式
├── app.jsx # 根元件 app
├── index.html # 等待被嵌入程式碼的 HTML 文件
└── pages # 頁面目錄
└── index # index 頁面模組
├── index.scss # index 頁面樣式
└── index.jsx # index 頁面元件
複製程式碼
我們主要看一下兩個程式碼檔案:src/app.jsx
以及 src/pages/index/index.jsx
。
初探腳手架程式碼
src/app.jsx
定義了專案的根元件 App
,它的程式碼如下:
import Taro, { Component } from '@tarojs/taro'
import Index from './pages/index'
import './app.scss'
// 如果需要在 h5 環境中開啟 React Devtools
// 取消以下注釋:
// if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5') {
// require('nerv-devtools')
// }
class App extends Component {
config = {
pages: ['pages/index/index'],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black',
},
}
// 在 App 類中的 render() 函式沒有實際作用
// 請勿修改此函式
render() {
return <Index />
}
}
Taro.render(<App />, document.getElementById('app'))
複製程式碼
如果你熟悉 React 的話,那麼上面這段程式碼一定不難理解,只不過是把相應的地方(導包、渲染)從之前的 React
以及 ReactDOM
改成 Taro
。
注意
可以看到這個元件還多了一個
config
屬性,這個屬性是小程式應用專屬的。其中要重點關注的是pages
陣列,列出了所有的頁面模組,例如這裡的pages/index/index
就對應 src/pages/index/index.jsx。後面在實現路由時還會用到pages
屬性。
我們再看看 src/pages/index/index.jsx
。按照最佳實踐,Taro 專案中一般把頁面元件放到 src/pages
目錄中,src/pages/index
就是 index
頁面元件模組,其中 index.jsx 的程式碼如下:
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.scss'
export default class Index extends Component {
config = {
navigationBarTitleText: '首頁',
}
render() {
return (
<View className="index">
<Text>Hello world!</Text>
</View>
)
}
}
複製程式碼
依舊是熟悉的 React 元件風格,只不過與普通的 React 相比,在 render
函式中我們用的不再是 div
和 p
標籤,而是 Taro 為我們準備好的 View
和 Text
元件。為什麼 Taro 要自己搞一套元件庫呢?因為 Taro 的目標是星辰大海……sorry,是能夠編譯到各個平臺。只有通過制訂 Taro 自己的元件庫,才能在各個平臺的原生元件庫上蓋了一層抽象層,進而實現跨平臺的目標。
提示
如果你有過 React Native 的開發經驗,那麼一定對 Taro 元件庫不陌生。
執行小程式
Taro 提供的模板程式碼直接可以執行。開啟終端,執行以下命令:
npm run dev:weapp
複製程式碼
會出現以下提示資訊:
當看到“監聽檔案修改中...”的提示後,我們就可以開啟微信開發者工具,用微信掃碼登入後介面如下:
點選那個碩大的➕號,開始匯入我們剛才建立的 ultra-club 專案:
如上圖所示,首先切換到”匯入專案“一欄,然後點選”目錄“輸入欄右側的按鈕選擇剛才建立的 ultra-club 資料夾,最後點選右下角的”匯入“按鈕即可。
匯入成功後,微信開發者工具的介面如下圖所示:
在模擬器頁面中,看到了我們 index
頁面渲染的 Hello world;編輯器能夠檢視所有程式碼,不過通常我們用自己習慣的程式碼編輯器來開發(VSCode 真香!);偵錯程式則是類似 Chrome 的開發者工具。
一切就緒,讓我們開始動工吧!
提示
從這一步開始,我們的主要開發目標將是微信小程式,但是不要擔心,我們會在下一篇教程中演示怎麼編譯到其他平臺哦。
React 程式碼,熟悉的味道
從這一步開始,我們就來實現”奧特曼俱樂部“小程式。按照 React 中”萬物皆元件“的思想,我們抽象出兩個元件:
PostCard
:用於展示一篇帖子,包括標題title
和內容content
PostForm
:用於釋出新帖子的表單
實現 PostCard 元件
首先建立 src/components
目錄,我們的通用元件都會放在這裡面。然後建立 src/components/PostCard
元件目錄,在其中分別建立 index.jsx
和 index.scss
。index.jsx
程式碼如下:
import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import './index.scss'
export default function PostCard(props) {
return (
<View className="postcard">
<View className="post-title">{props.title}</View>
<View className="post-content">{props.content}</View>
</View>
)
}
複製程式碼
正如之前所說,PostCard
元件包含兩個 props:標題 title
和內容 content
。
PostCard 元件的樣式 index.scss
程式碼如下:
.postcard {
margin: 30px;
padding: 20px;
border: 1px solid #ddd;
}
.post-title {
font-weight: bolder;
margin-bottom: 10px;
}
.post-content {
font-size: medium;
color: #666;
}
複製程式碼
實現 PostForm 元件
接著我們實現用於建立新帖子的 PostForm 元件。在 src/components
中建立 PostForm
目錄,並在其中新增 index.jsx
和 index.scss
檔案。index.jsx
程式碼如下:
import Taro from '@tarojs/taro'
import { View, Form, Input, Textarea, Button } from '@tarojs/components'
import './index.scss'
export default function PostForm(props) {
return (
<View className="post-form">
<View>新增新的帖子</View>
<Form onSubmit={props.handleSubmit}>
<View>
<View className="form-hint">標題</View>
<Input
className="input-title"
type="text"
placeholder="點選輸入標題"
value={props.formTitle}
onInput={props.handleTitleInput}
/>
<View className="form-hint">正文</View>
<Textarea
placeholder="點選輸入正文"
className="input-content"
value={props.formContent}
onInput={props.handleContentInput}
/>
<Button className="form-button" formType="submit" type="primary">
提交
</Button>
</View>
</Form>
</View>
)
}
複製程式碼
PostForm
元件一共定義了五個 props,分別如下:
formTitle
:當前編輯中帖子的標題formContent
:當前編輯中帖子的內容handleSubmit
:處理提交表單的回撥函式handleTitleInput
:處理標題接收到使用者輸入時的回撥函式handleContentInput
:處理內容接收到使用者輸入時的回撥函式
提示
如果你不熟悉 React,可能會對上面編寫表單的方式有點困惑。實際上,React 推薦用”受控元件“的方式編寫表單,可參考這篇文件。
PostForm 的樣式檔案 index.scss
的程式碼如下:
.post-form {
border: 1px solid #ddd;
margin: 30px;
padding: 30px;
}
.input-title {
border: 1px solid #eee;
padding: 10px;
font-size: medium;
}
.input-content {
border: 1px solid #eee;
padding: 10px;
height: 200px;
font-size: medium;
}
.form-hint {
font-size: small;
color: gray;
margin-top: 20px;
margin-bottom: 10px;
}
.form-button {
margin-top: 40px;
}
複製程式碼
為了方便在頁面元件中使用 PostCard
和 PostForm
元件,我們把 src/components
變成一個模組。具體地,建立 src/components/index.jsx
,程式碼如下:
import PostCard from './PostCard'
import PostForm from './PostForm'
export { PostCard, PostForm }
複製程式碼
在 index 頁面中接入 PostCard 和 PostForm
最後在 src/pages/index/index.jsx
檔案中加入之前寫好的 PostCard 和 PostForm 元件,程式碼如下:
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { PostCard, PostForm } from '../../components'
import './index.scss'
export default class Index extends Component {
state = {
posts: [
{
title: '泰羅奧特曼',
content: '泰羅是奧特之父和奧特之母唯一的親生兒子。',
},
],
formTitle: '',
formContent: '',
}
config = {
navigationBarTitleText: '首頁',
}
handleSubmit(e) {
e.preventDefault()
const { formTitle: title, formContent: content } = this.state
const newPosts = this.state.posts.concat({ title, content })
this.setState({
posts: newPosts,
formTitle: '',
formContent: '',
})
}
handleTitleInput(e) {
this.setState({
formTitle: e.target.value,
})
}
handleContentInput(e) {
this.setState({
formContent: e.target.value,
})
}
render() {
return (
<View className="index">
{this.state.posts.map((post, index) => (
<PostCard key={index} title={post.title} content={post.content} />
))}
<PostForm
formTitle={this.state.formTitle}
formContent={this.state.formContent}
handleSubmit={e => this.handleSubmit(e)}
handleTitleInput={e => this.handleTitleInput(e)}
handleContentInput={e => this.handleContentInput(e)}
/>
</View>
)
}
}
複製程式碼
可以看到,除了接入之前定義的兩個元件外,我們還加入了一些狀態:
posts
:當前所有的帖子,每個帖子是一個包含title
和content
的物件formTitle
:當前正在編輯的帖子的標題formContent
:當前正在編輯的帖子的內容
以及定義了 PostForm
元件中所需要的三個回撥函式。
檢視效果
如果之前的開發伺服器還開啟著,那麼微信開發者工具應該就能直接看到效果了(如果剛才關了,可以執行 npm run dev:weapp
再次開啟哦):
注意
有時候 Taro 可能會出現樣式載入失敗的問題。如果你遇到了,可以關閉開發伺服器,重新執行
npm run dev:weapp
。
Hooks 輕裝上陣
自從 React 團隊在 2018 年的 React Conf 引入了 Hooks 之後,前端圈無疑是經歷了一場地震。僅僅只需幾個 API,就輕鬆地用純函式的方式搞定了元件的狀態管理和資料流,這是何等的神仙操作?
幸運的是,Taro 團隊也在 v1.3.0 版本中新增了對 Hooks 的支援。因此,我們也將在本專案中用 Hooks 解決狀態管理和資料流的問題。
Hooks 之 useState 快速複習
本文在這裡簡單地過一遍 useState
Hook,如果你已經很熟悉了,請直接移步下面的動手環節。
比如我們之前有這麼一個類元件 ClickMe
,它會抱怨你點了它多少次:
class ClickMe extends Component {
state = { count: 0 }
render() {
return (
<div>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
你點了我 {this.state.count} 次!
</button>
</div>
)
}
}
複製程式碼
用 Hooks 改寫之後,就變成了一個函式式元件:
// 記得匯入 useState 函式
import Taro, { useState } from '@tarojs/taro'
function ClickMe() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(count + 1)}>你點了我 {count} 次!</button>
</div>
)
}
複製程式碼
可以看到,useState
函式返回了兩個值:
- 狀態(也就是上面的
count
):可以在渲染時直接使用 - 修改狀態的函式(也就是上面的
setCount
):用於在處理相應事件時,通過傳入新的狀態來更新狀態
還注意到 useState
接受一個引數,即狀態的初始值。這裡我們取了一個 Number
型別,事實上還可以是字串、陣列、物件等等。
動手環節
到了動手環節,我們用 useState
來重構我們的 index 頁面。具體地,我們將整個 Index
元件轉換成函式式元件,然後之前的三個狀態都用 useState
來建立,程式碼如下:
import Taro, { useState } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { PostCard, PostForm } from '../../components'
import './index.scss'
export default function Index() {
const [posts, setPosts] = useState([
{
title: '泰羅奧特曼',
content: '泰羅是奧特之父和奧特之母唯一的親生兒子。',
},
])
const [formTitle, setFormTitle] = useState('')
const [formContent, setFormContent] = useState('')
function handleSubmit(e) {
e.preventDefault()
const newPosts = posts.concat({ title: formTitle, content: formContent })
setPosts(newPosts)
setFormTitle('')
setFormContent('')
}
return (
<View className="index">
{posts.map((post, index) => (
<PostCard key={index} title={post.title} content={post.content} />
))}
<PostForm
formTitle={formTitle}
formContent={formContent}
handleSubmit={e => handleSubmit(e)}
handleTitleInput={e => setFormTitle(e.target.value)}
handleContentInput={e => setFormContent(e.target.value)}
/>
</View>
)
}
Index.config = {
navigationBarTitleText: '首頁',
}
複製程式碼
注意
由於我們把
Index
從類元件改造成了函式元件,所以掛載config
要在Index
元件定義之後直接掛載在Index
上面。
你儘可以開啟模擬器試一下重構之後效果,看看功能是否與上一步完全一致哦!在接下來的第二篇中,我們將進一步實現多頁面跳轉,並用 Taro UI 元件庫升級我們的介面。
想要學習更多精彩的實戰技術教程?來圖雀社群逛逛吧。
本文所涉及的原始碼都放在了 Github 上,如果您覺得我們寫得還不錯,希望您能給❤️這篇文章點贊+Github倉庫加星❤️哦~