前言
因最近开始准备毕设,我的毕设是要做一个小程序,便开始了学习如何开发小程序之旅。
Taro是一个开放式跨端跨框架的解决方案,支持使用React/Vue/Nerv等框架来开发微信、京东、百度、支付宝、字节跳动、QQ小程序 / H5等应用。
所以,使用Taro的话,只需编写一套代码就能够拥有适配到多端的能力。Taro目前在github上 有27.9k个star,这也是我选择学习它的一个原因。(虽然整个做下来之后发现它的教程坑实有点点多......)
那就开始吧。
官方文档:Taro官方文档
入门
因为有一定前端开发经验,所以我直接从渐进式入门教程着手,通过实操一步步上手。
渐进式入门教程是使用Taro开发一个简单的V2EX论坛客户端,整个教程分为4个部分:
- 环境准备
- 基础教程
- 项目进阶与优化
- 多端开发
这里就展开一些教程中没有提到的点和总结其中比较关键的点。
环境准备
使用Taro开发需要安装:
Taro CLINode.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中,可以使用Taro的navigateTo方法进行路由跳转:
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/weapp,h5的路径是dist/h5。
然后,打开微信开发者工具,导入打包编译好的项目,就有效果啦:
但此时,我的项目遇到了一个bug,在微信开发者工具中报错了:
当时没有仔细看报错信息,其实第二行Thread.render中点击进去有提示报错位置。心想:这没法调试。。。也没办法console.log。(实际上是可以的)。
于是便想,那是不是可以打包成h5,在浏览器里调试,这样不就和平时的前端开发一样了么!于是便转向了另一个方向。但是在浏览器端,会有跨域问题,localhost向https://www.v2ex.com/api/发起请求是违反了浏览器的同源策略。小程序开发工具并没有同源策略的限制,于是不会有这个问题。
Taro h5 跨域问题解决
首先,要在config/dev.js中添加h5配置:
h5: {
devServer: {
proxy: {
'/api/': {
target: "https://www.v2ex.com",
changeOrigin: true
// pathRewrite: {
// '^/api/': '/'
// },
}
}
}
}
其中的配置就是webpack的devServer的配置,详情可参考博客: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