Taro 小程序開發大型實戰(一):熟悉的 React,熟悉的 Hooks

正當移動互聯網進入白熱化階段時,以微信小程序爲表明的一類「輕應用」異軍突起。它們無需下載,使用方便,「用完即走」,同時功能也較爲完備,一經推出即獲得了各大平臺和及用戶的熱烈追捧。可是問題也隨之而來——開發者們要同時維護 Web 端、移動端、微信小程序、支付寶小程序等等多套用戶界面,其維護成本能夠想象。做爲一個優秀的多端統一開發解決方案,Taro 的出現則改變了這一狀況。正值 Taro 2.x 進入 beta 階段,讓咱們沏上一杯茶,開始咱們的 Taro 多端小程序開發之旅吧。javascript

若是您以爲咱們寫得還不錯,記得 點贊 + 關注 + 評論 三連,鼓勵咱們寫出更好的教程💪

起步

對於國內 React 開發者來講,Taro 的出現無疑是福音——它可以讓咱們用熟悉的 React 代碼去搭建各種小程序,而且一份代碼能夠編譯成多個平臺的應用(目前包括微信小程序、支付寶小程序、React Native、H5 等等)。隨着 Taro 的不斷進化,它對 React 代碼的支持程度愈來愈好,所支持的目標平臺也愈來愈多,學習的價值天然沒必要多言。正值 Taro 進入 2.0.0 版本的 beta 階段,咱們在這一篇教程將手把手帶你實現一個可以部署到多端的小程序,讓你感覺 Taro 的強大與魅力!css

在這一系列教程中,咱們將構建一個多端小程序應用——奧特曼俱樂部(Ultraman Club,簡稱 UltraClub),一個支持多端登陸(微信和支付寶)的相似貼吧的小程序。咱們還提供了項目倉庫的 GitHub 地址項目目前還在開發階段,您能夠跳轉到任意一次 commit 查看當前步驟的全部代碼哦。html

咱們將構建什麼?

在完成這篇教程後,項目的 GIF 動圖展現以下:前端

前提條件

在閱讀這篇教程以前,咱們但願你已經具有如下知識:java

  • 瞭解 HTML、CSS、JavaScript 的基礎知識,若是瞭解 Sass 就更好了
  • 瞭解 React 框架的基礎知識,能夠參考這篇教程進行學習;若是接觸過 React Native 以及 Hooks 則更好了
  • 瞭解並已經安裝好 Node 與 npm,能夠參考這篇教程進行學習

除此以外,你還須要下載並安裝微信開發者工具,這裏是下載地址react

本文所涉及的源代碼都放在了 Github 上,若是您以爲咱們寫得還不錯,但願您能給❤️這篇文章點贊+Github倉庫加星❤️哦~

用 Taro 腳手架初始化項目

首先安裝 Taro CLI:git

npm install -g @tarojs/cli

而後建立咱們的項目:github

taro init ultra-club

以後會出現一系列選項,按照下圖所示進行選擇便可(CSS 預處理器選擇 Sass,模板選擇「默認模板」,老司機可自行選擇使用 TS):npm

提示

本項目使用 Sass 主要是爲了兼容 taro-ui 的樣式,並無使用到 Sass 的高級特性,若是你不熟悉的話也不用擔憂哦,就當成是常規的 CSS 代碼。json

進入到咱們的項目目錄 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 函數中咱們用的再也不是 divp 標籤,而是 Taro 爲咱們準備好的 ViewText 組件。爲何 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.jsxindex.scssindex.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.jsxindex.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;
}

爲了方便在頁面組件中使用 PostCardPostForm 組件,咱們把 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:當前全部的帖子,每一個帖子是一個包含 titlecontent 的對象
  • 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倉庫加星❤️哦~

相關文章
相關標籤/搜索