Taro入门及踩坑之旅

1,329 阅读8分钟

前言

因最近开始准备毕设,我的毕设是要做一个小程序,便开始了学习如何开发小程序之旅。

Taro是一个开放式跨端跨框架的解决方案,支持使用React/Vue/Nerv等框架来开发微信、京东、百度、支付宝、字节跳动、QQ小程序 / H5等应用。

所以,使用Taro的话,只需编写一套代码就能够拥有适配到多端的能力。Taro目前在github上 有27.9k个star,这也是我选择学习它的一个原因。(虽然整个做下来之后发现它的教程坑实有点点多......)

那就开始吧。

官方文档:Taro官方文档


入门

因为有一定前端开发经验,所以我直接从渐进式入门教程着手,通过实操一步步上手。

渐进式入门教程

渐进式入门教程源码

渐进式入门教程是使用Taro开发一个简单的V2EX论坛客户端,整个教程分为4个部分:

  • 环境准备
  • 基础教程
  • 项目进阶与优化
  • 多端开发

这里就展开一些教程中没有提到的点和总结其中比较关键的点。

环境准备

使用Taro开发需要安装:

  • Taro CLI
  • Node.js

Taro CLI安装指令:

npm i -g @tarojs/cli

安装完毕后在终端输入taro,如果出现类似内容说明安装成功:

👽 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]

基础教程

组件

在小程序中,组件分为页面组件和基础组件。页面组件就是那些可以通过路由跳转到指定页面的组件,具有生命周期,基础组件就是一些普通组件,不构成页面,是页面或者组件的一部分。

页面组件

页面组件一般在pages文件夹下定义:

thread_detail组件举例,该文件夹下有三个文件:

  • index.css:样式文件
  • thread_detail.tsx:组件
  • thread_detail.config.ts:页面组件配置文件

配置文件主要是配置一些基础样式和文字,页面配置,比如:

backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'

thread_detail.config.ts中是这样:

export default {
  navigationBarTitleText: '话题'
}

这个配置文件是必须要写的,否则在项目打包编译就会报如下错误:

Module not found: Can‘t resolve ‘./pages/xxx/xxx.config‘ in ‘xxxx‘

这里要注意一下。

类组件:

import React, { Component } from 'react'
import { View } from '@tarojs/components'

class Index extends Component {
  // 可以使用所有的 React 组件方法
  componentDidMount () {}

  // onLoad
  onLoad () {}

  // onReady
  onReady () {}

  // 对应 onShow
  componentDidShow () {}

  // 对应 onHide
  componentDidHide () {}

  // 对应 onPullDownRefresh,除了 componentDidShow/componentDidHide 之外,
  // 所有页面生命周期函数名都与小程序相对应
  onPullDownRefresh () {}

  render () {
    return (
      <View className='index' />
    )
  }
}

export default Index

函数组件:

import React, { useEffect } from 'react'
import { View } from '@tarojs/components'
import {
  useReady,
  useDidShow,
  useDidHide,
  usePullDownRefresh
} from '@tarojs/taro'

function Index () {
  // 可以使用所有的 React Hooks
  useEffect(() => {})

  // 对应 onReady
  useReady(() => {})

  // 对应 onShow
  useDidShow(() => {})

  // 对应 onHide
  useDidHide(() => {})

  // Taro 对所有小程序页面生命周期都实现了对应的自定义 React Hooks 进行支持
  // 详情可查阅:【Taro 文档】-> 【进阶指南】->【Hooks】
  usePullDownRefresh(() => {})

  return (
    <View className='index' />
  )
}

export default Index

基础组件

基础组件一般在components文件夹下定义:

这里就不需要为每个组件设置配置文件。

入口配置

app.config.ts就是入口配置文件,我们可以在其中配置页面路径列表、全局的默认窗口表现等属性。Taro的配置规范是基于微信小程序的全局配置进行规定的:微信小程序全局配置

在V2EX项目中,全局配置为:

// app.config.ts
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: '节点'
    }],
    'color': '#000',
    'selectedColor': '#56abe4',
    'backgroundColor': '#fff',
    'borderStyle': 'white'
  },
  window: {
    backgroundTextStyle: 'light',
    navigationBarBackgroundColor: '#fff',
    navigationBarTitleText: 'WeChat',
    navigationBarTextStyle: 'black'
  }
}

路由

路由跳转

Taro中,可以使用TaronavigateTo方法进行路由跳转:

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

这个用法类似React中的props.history.push('/xxx')

路由参数

在页面组件中,可以通过 getCurrentInstance().router 或者Current.router?.params获取当前页面的路由参数:

import Taro, { Current } from '@tarojs/taro';

// 在组件内
const { full_name } = Current.router?.params;

生命周期

React组件的生命周期方法和小程序的生命周期方法在Taro中均支持,但需要注意它们的触发时机和顺序。见官方文档:生命周期触发机制

内置组件

Taro中使用小程序规范的内置组件进行开发,如<View /><Text /><Button />等,这些是跨平台组件。

React中使用这些内置组件前,必须从@tarojs/components进行引入,组件的props遵从大驼峰式命名规范:

// 小程序写法
<view hover-class='test' />
// Taro写法
import { View } from '@tarojs/components'

<View hoverClass='test' />

事件

在 Taro 中事件遵从小驼峰式(camelCase)命名规范,所有内置事件名以 on 开头。

在事件回调函数中,第一个参数是事件本身,回调中调用 stopPropagation 可以阻止冒泡。

function Comp () {
  function clickHandler (e) {
    e.stopPropagation() // 阻止冒泡
  }

  function scrollHandler () {}
  
  // 只有小程序的 bindtap 对应 Taro 的 onClick
  // 其余小程序事件名把 bind 换成 on 即是 Taro 事件名(支付宝小程序除外,它的事件就是以 on 开头)
  return <ScrollView onClick={clickHandler} onScroll={scrollHandler} />
}

项目进阶与优化

项目在开发完毕之后,我们想要看项目的效果是怎么样的,要怎么做呢?当时我有点疑惑,于是找了Taro官方的一个5分钟上手小程序的视频来看,便找到了答案。

首先,先打包编译。使用Taro CLI生成的项目,在package.json中已经配置好了各种小程序的打包编译指令:

比如,我想打包微信小程序,则在终端输入:

npm run dev:weapp

调试的话就执行npm run dev,打包构建到生产环境则执行npm run build

如果想要多端编译,在config文件夹下的index.js里修改outputRoot属性:

outputRoot: `dist/${process.env.TARO_ENV}`

这样的话,各个端打包好的文件就可以同时存放在dist文件夹内,比如微信小程序的路径是dist/weapph5的路径是dist/h5

然后,打开微信开发者工具,导入打包编译好的项目,就有效果啦:

但此时,我的项目遇到了一个bug,在微信开发者工具中报错了:

当时没有仔细看报错信息,其实第二行Thread.render中点击进去有提示报错位置。心想:这没法调试。。。也没办法console.log。(实际上是可以的)。

于是便想,那是不是可以打包成h5,在浏览器里调试,这样不就和平时的前端开发一样了么!于是便转向了另一个方向。但是在浏览器端,会有跨域问题,localhosthttps://www.v2ex.com/api/发起请求是违反了浏览器的同源策略。小程序开发工具并没有同源策略的限制,于是不会有这个问题。

Taro h5 跨域问题解决

首先,要在config/dev.js中添加h5配置:

h5: {
    devServer: {
        proxy: {
            '/api/': {
                target: "https://www.v2ex.com",
                    changeOrigin: true
                // pathRewrite: {
                //   '^/api/': '/'
                // },
            }
        }
    }
}

其中的配置就是webpackdevServer的配置,详情可参考博客:devServer之proxy跨域

第二步,修改api.ts里的HOST

const HOST_URI = 'https://www.v2ex.com/api/'

// 修改为
const HOST_URI = '/api/'

为什么要这么改呢?因为在proxy中,我们会检查请求的开头是否为/api/,如果是以http开头,则检查不匹配,不走代理,跨域失效。所以去掉前面的http部分,相当于遇到/api/才做代理,则会把默认域名http://localhost:xxxx改成target,也就是https://www.v2ex.com地址。不过在浏览器F12下,Network->Headers中看到的还是http://localhost:xxxx/api/xxx,但是真正的请求地址为:https://www.v2ex.com/api/xxx

需要添加pathRewrite属性的场景是:相当于遇见/api/才做代理,但真实的请求中没有/api/,所以在pathRewrite中把/api/去掉, 这样既有了标识, 又能在请求接口中把/api/去掉。

运行npm run dev:h5,可以在浏览器端查看到效果:

最后问题是由于将生命周期函数componentWillMount写成componentWillUnmount导致的,找了好久,以至于想锤死自己。

不过React已经不建议使用componentWillMount这个生命周期函数了,所以我们应该将数据获取放在componentDidMount中:

async componentDidMount() {
    this.setState({
      thread: this.props.thread
    })
}

渲染时加一层判断:

// 组件内
// 在加载到thread的数据后再渲染,防止出现property of undefined的报错
const attrLen = Object.keys(thread).length;

{attrLen ? <Thread
    node={thread.node}
    title={thread.title}
    last_modified={thread.last_modified}
    replies={thread.replies}
    tid={thread.id}
    member={thread.member}
    not_navi={true}
/> : null}

其实微信开发者工具也有完善的调试器,能够看到console.log等的结果,所以我们直接在vscode中修改代码,ctrl s之后,就能够在微信开发者工具中看到相应的渲染结果,也可以通过看console来调试。不过微信开发者工具似乎不能够使用Redux的调试工具,这个之后再研究研究。

后续用hooks对这个demo进行重构,代码仓库:taro-mini-program


学习资料

Taro学习优秀资源:awesome-taro