Taro开发总结
之前做小程序一直用的mpvue,用了一段时间发现mpvue有一些诟病,而且现在官方的维护力度显得力不从心。相比之下Taro做的就相当不错。现总结一下在使用Taro中各种奇技淫巧。
目前使用Taro版本1.3.4
数据请求库封装
新建src/utils/request.js
文件
import Taro from '@tarojs/taro'
import configStore from '../redux/store'
import actions from '../redux/actions'
import { loadData, ACCESS_TOKEN } from './cache'
import { isUrl } from './utils'
const store = configStore()
const baseURL = BASE_URL
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
}
/**
* 状态检查
* @param response
* @returns {*}
*/
function checkStatus (response) {
const { statusCode, data } = response
if (statusCode >= 200 && statusCode < 300) {
return data
}
const errorText = codeMessage[statusCode]
const error = new Error(errorText)
error.name = statusCode
error.response = response
throw error
}
export default function request (options) {
const accessToken = loadData(ACCESS_TOKEN)
const { url, method = 'get', data } = options
let header = {}
if (accessToken) {
header.Authorization = `JWT ${accessToken}`
}
return Taro.request({
url: isUrl(url) ? url : `${baseURL}${url}`,
method: method.toUpperCase(),
data,
header: { ...header, ...options.header }
}).then(checkStatus)
.then(res => {
const { status, data, errCode, errMsg, } = res
if (status === 1) {
return data
} else if (status === 0 && (errCode === 401 || errCode === 403)) {
store.dispatch(actions.setLoginStatus(false))
store.dispatch(actions.setUserInfo(''))
store.dispatch(actions.setAccessToken(''))
//跳转到首页
Taro.reLaunch({
url: '/pages/index/index'
})
return Promise.reject(res)
} else {
Taro.hideLoading()
if (errMsg) {
Taro.showToast({
title: errMsg,
icon: 'none',
duration: 3000
})
}
return Promise.reject(res)
}
})
.catch(err => {
return Promise.reject(err)
})
}
使用request.js
,新建src/api/index.js
文件
import request from '../utils/request'
export function getBannerList () {
let url = `/cms_content/content/`
return request({
url,
method: 'get'
})
}
利用Decorator快速实现小程序分享
新建src/utils/withShare.js
文件
import Taro from '@tarojs/taro'
function withShare (opts = {}) {
// 设置默认
const defalutPath = '/pages/index/index'
const defalutTitle = '首页'
const defaultImageUrl = 'http://thumb10.jfcdns.com/2018-06/bce5b10ae530f530.png'
return function demoComponent (Component) {
class WithShare extends Component {
componentDidMount () {
if (super.componentDidMount) {
super.componentDidMount()
}
}
// 点击分享的那一刻会进行调用
onShareAppMessage () {
let { title, imageUrl, path = null } = opts
// 从继承的组件获取配置
if (this.$setSharePath && typeof this.$setSharePath === 'function') {
path = this.$setSharePath()
}
// 从继承的组件获取配置
if (this.$setShareTitle && typeof this.$setShareTitle === 'function') {
title = this.$setShareTitle()
}
// 从继承的组件获取配置
if (this.$setShareImageUrl && typeof this.$setShareImageUrl === 'function') {
imageUrl = this.$setShareImageUrl()
}
if (!path) {
path = defalutPath
}
console.log(path)
return {
title: title || defalutTitle,
path: path || defalutPath,
imageUrl: imageUrl || defaultImageUrl
}
}
render () {
return super.render()
}
}
return WithShare
}
}
export default withShare
使用src/pages/xxx/xxx.js
import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import withShare from '@/utils/withShare';
@withShare({
title: '可设置分享标题',
imageUrl: '可设置分享图片路径',
path: '可设置分享路径'
})
class Index extends Component {
// $setSharePath = () => '可设置分享路径(优先级最高)'
// $setShareTitle = () => '可设置分享标题(优先级最高)'
// $setShareImageUrl = () => '可设置分享图片路径(优先级最高)'
render() {
return <View />
}
}
封装UIcon组件
封装该组件可以使用iconfont
等字体图标,新建src/components/uIcon/index.js
import Taro, { Component } from '@tarojs/taro'
import PropTypes from 'prop-types'
import { Text } from '@tarojs/components'
class UIcon extends Component {
static options = {
addGlobalClass: true
}
static externalClasses = ['u-class']
static propTypes = {
icon: PropTypes.string,
prefixClass: PropTypes.string,
color: PropTypes.string,
size: PropTypes.number,
onClick: PropTypes.func,
}
static defaultProps = {
icon: '',
prefixClass: 'iconfont',
color: '#373737',
size: 26,
onClick: () => {}
}
render () {
const { icon, onClick, prefixClass, color, size } = this.props
return (
<Text
className={`u-class u-icon ${prefixClass} ${icon}`}
onClick={onClick}
style={{ color: `${color}`, fontSize: `${size}rpx` }}
/>
)
}
}
export default UIcon
使用src/pages/xxx/xxx.js
import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import UIcon from '@/components/uIcon'
class Index extends Component {
render() {
return <View >
<UIcon icon='icon-home'/>
</View>
}
}
封装HtmlParse组件
该组件可显示html代码片段src/components/htmlParse/index.js
import Taro, { Component } from '@tarojs/taro'
import PropTypes from 'prop-types'
import { View, RichText } from '@tarojs/components'
import { isUrl } from '@/utils/utils'
import CommonServer from '@/api/common'
import './index.scss'
class HtmlParse extends Component {
static propTypes = {
url: PropTypes.string,
onHtmlLoad: PropTypes.func,
}
static defaultProps = {
url: '',
onHtmlLoad: () => {}
}
state = {
content: '数据加载中...'
}
componentDidMount () {
this.getHtmlContent()
}
getHtmlContent = () => {
const { url, onHtmlLoad } = this.props
if (isUrl(url)) {
CommonServer.getHtmlContent(url).then(res => {
this.setState({
content: this.parseHtmlContent(res.data) || '暂无数据'
})
onHtmlLoad()
})
} else {
this.setState({
content: this.parseHtmlContent(url) || '暂无数据'
})
onHtmlLoad()
}
}
parseHtmlContent = (html) => {
return html
.replace(/section/g, 'div')
.replace(/[外链图片转存失败(img-2NQLZftX-1562226911276)(undefined)]]+>/g, function (all, group1, group2) {
return `![在这里插入图片描述]()`
})
.replace(/<table([\s\S]*?)[^>]+>/g, function () {
return `<table width="100%"/>`
})
}
render () {
const { content } = this.state
return (
<View className='html-parse'>
<RichText className='rich-text' nodes={content}/>
</View>
)
}
}
export default HtmlParse
使用src/pages/xxx/xxx.js
import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import HtmlParse from '@/components/htmlParse'
class Index extends Component {
render() {
const { content } = this.state
return <View >
<View className='content'>
{
content && <HtmlParse
url={content}
onHtmlLoad={() => {
console.log('html片段加载完毕')
}}
/>
}
</View>
</View>
}
}
分环境打包项目
修改config下dev.js
和prod.js
//defineConstants用来配置一些全局变量供代码中进行使用
//dev.js
module.exports = {
env: {
NODE_ENV: '"development"',
},
defineConstants: {
BASE_URL: '"https://api.test.com"',
},
weapp: {},
h5: {}
}
//prod.js
const target = process.env.npm_lifecycle_event
const TEST = 'test'
const BUILD = 'build'
let defineConstants
if (target.indexOf(TEST) >= 0) {
defineConstants = {
BASE_URL: '"https://api.test.com"',
}
} else if (target.indexOf(BUILD) >= 0) {
defineConstants = {
BASE_URL: '"https://api.build.com"',
}
}
module.exports = {
env: {
NODE_ENV: '"production"'
},
defineConstants,
weapp: {},
h5: {}
}
package.json
修改
"scripts": {
...
"dev": "npm run build:weapp -- --watch",
"test": "taro build --type weapp",
"build": "taro build --type weapp"
},
打包命令
//打包测试版本
yarn test||npm run test
//打包正式版本
yarn build||npm run build
打包压缩配置
修改config下index.js
plugins: {
...
//压缩js
uglify: {
enable: true,
config: {
warnings: false,
// 配置项同 https://github.com/mishoo/UglifyJS2#minify-options
compress: {
drop_debugger: true,
drop_console: true,
},
}
},
//压缩css
csso: {
enable: true,
config: {
// 配置项同 https://github.com/css/csso#minifysource-options
}
}
},
weapp:{
compile: {
compressTemplate: true,//打包时是否需要压缩 wxml
},
}
alias配置
修改config下index.js
const path = require('path')
function resolve (dir) {
return path.resolve(__dirname, '..', dir)
}
// 目录别名设置
alias: {
'@/api': resolve('src/api'),
'@/assets': resolve('src/assets'),
'@/components': resolve('src/components'),
'@/redux': resolve('src/redux'),
'@/utils': resolve('src/utils'),
},
图片裁剪封装
本地安装we-cropper
yarn add we-cropper
新建cropper.js
文件
import Taro, { Component } from '@tarojs/taro'
import { Canvas, CoverView, View } from '@tarojs/components'
import WeCropper from 'we-cropper'
import { saveData, VOTER_IMAGE, VOTER_AVATAR } from '@/utils/cache'
import './index.scss'
const device = Taro.getSystemInfoSync()
const devicePixelRatio = device.pixelRatio
const windowWidth = device.windowWidth
const windowHeight = device.windowHeight
const cropper = {
width: `${windowWidth}px`,
height: `${windowHeight}px`
}
const target = {
width: `${devicePixelRatio * windowWidth}px`,
height: `${devicePixelRatio * windowHeight}px`
}
class Cropper extends Component {
config = {
navigationBarTitleText: '裁剪图片'
}
constructor () {
super()
this.state = {
cropperOpt: {
id: 'cropper',
targetId: 'target',
pixelRatio: devicePixelRatio,
width: windowWidth,
height: windowHeight,
scale: 2.5,
zoom: 1,
cut: {
x: (windowWidth - 400) / 2,
y: (windowHeight - 615) / 2,
width: 400,
height: 615
}
},
weCropper: null,
imageType: 'image'
}
}
componentDidMount () {
this.initCropper(this.$router.params)
}
// 初始化cropper
initCropper = (params) => {
const width = Number(params.width) || 100
const height = Number(params.height) || 100
const cut = {
x: (windowWidth - width) / 2,
y: (windowHeight - height) / 2,
width: width,
height: height
}
this.setState({
cropperOpt: { ...this.state.cropperOpt, cut },
imageType: params.imageType
}, () => {
const weCropperObj = new WeCropper(this.state.cropperOpt)
.on('ready', (ctx) => {
// console.log(`weCropper准备工作`)
})
.on('beforeImageLoad', (ctx) => {
// console.log(`在图片加载之前,我可以做一些事情`)
// console.log(`当前画布上下文:`, ctx)
Taro.showToast({
title: '上传中',
icon: 'loading',
duration: 20000
})
})
.on('imageLoad', (ctx) => {
// console.log(`图片加载...`)
// console.log(`当前画布上下文:`, ctx)
Taro.hideToast()
})
.on('beforeDraw', (ctx, instance) => {
// console.log(`在画布画之前,我可以做点什么`)
// console.log(`当前画布上下文:`, ctx)
})
this.setState({
weCropper: weCropperObj
}, () => {
this.uploadTap()
})
})
}
// 上传图片
uploadTap () {
Taro.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: res => {
let src = res.tempFilePaths[0]
// 获取裁剪图片资源后,给data添加src属性及其值
this.state.weCropper.pushOrign(src)
}
})
}
// 生成图片
getCropperImage () {
const { imageType } = this.state
Taro.showLoading({
title: '生成中',
})
this.state.weCropper.getCropperImage({
original: false, // 是否使用原图模式(默认值 false)
quality: 0.8, // 图片的质量,目前仅对jpg有效。取值范围为 (0,1],不在范围内时当作1.0处理
fileType: String // 目标文件的类型
}).then(src => {
Taro.hideLoading()
Taro.navigateBack({
delta: 1
})
})
}
touchStart = e => {
this.state.weCropper.touchStart(e)
}
touchMove = e => {
this.state.weCropper.touchMove(e)
}
touchEnd = e => {
this.state.weCropper.touchEnd(e)
}
render () {
return (
<View className='cropper'>
<Canvas
className='canvas'
canvas-id='cropper'
disable-scroll='true'
onTouchStart={this.touchStart}
onTouchMove={this.touchMove}
onTouchEnd={this.touchEnd}
style={cropper}
/>
<Canvas
className='target'
canvas-id='target'
style={target}
/>
<CoverView className='cropper-buttons'>
<CoverView className='uploadImg' onClick={this.uploadTap}>
重新选择
</CoverView>
<CoverView className='getCropperImage' onClick={this.getCropperImage}>
确定
</CoverView>
</CoverView>
</View>
)
}
}
export default Cropper
新建index.scss
.cropper {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
.target {
position: absolute;
top: 0;
left: 0;
transform: translateX(-200%);
}
.cropper-buttons {
position: absolute;
bottom: 0;
left: 0;
z-index: 10;
display: flex;
width: 100%;
height: 100px;
color: #ccc;
font-size: 30px;
background-color: #1a1a1a;
//font-weight: 900;
.uploadImg,
.getCropperImage {
flex: 1;
height: 100px;
line-height: 100px;
text-align: center;
}
}
}
腾讯云cos使用
导入cos-wx-sdk-v5.js
到项目src目录下
配置config/index.js文件
weapp:{
complie:{
exclude: ['src/assets/js/cos-wx-sdk-v5.js']// 不编译此文件
}
}
新建cos.js文件
import dayjs from 'dayjs'
import CommonServer from '@/api/common'
const COS = require('../assets/js/cos-wx-sdk-v5')
export const Bucket = Bucket_Name
export const Region = Region_Name
/**
* 文件扩展名提取
* @param fileName
* @returns {string}
*/
export function fileType (fileName) {
return fileName.substring(fileName.lastIndexOf('.') + 1)
}
/**
* cos路径定义
* @param path
* @param userUuid
* @param fileType
* @returns {string}
*/
export function cosPath (path = 'images', userUuid = 'test', fileType = 'png') {
const day = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS')
let name = `${day}.${fileType}`
return `applets/${userUuid}/${path}/${name}`
}
export const cos = new COS({
getAuthorization: (options, callback) => {
let data = {
method: (options.Method || 'get').toLowerCase(),
pathname: '/' + (options.Key || '')
}
CommonServer.getAuthorization(data).then(res => {
callback(res.authorization)
}).catch(err => {
console.log(err)
})
}
})
/**
* cos上传
* @param FilePath
* @param path
* @param userUuid
* @param fileType
* @returns {Promise<any>}
*/
export function cosUpload (FilePath, path = 'images', userUuid = 'test', fileType = 'png') {
return new Promise((resolve, reject) => {
let Key = cosPath(path, userUuid, fileType)
if (fileType === 'png') {
cos.postObject({
Bucket,
Region,
Key,
FilePath,
}, (err, res) => {
if (res.statusCode === 200) {
const { Location } = res
let src = `https://${Location}`
resolve(src)
} else {
reject(err)
}
})
} else if (fileType === 'html') {
cos.putObject({
Bucket,
Region,
Key,
Body: FilePath,
}, (err, res) => {
if (res.statusCode === 200) {
let src = `https://${Bucket}.cos.${Region}.myqcloud.com/${Key}`
resolve(src)
} else {
reject(err)
}
})
}
})
}
github地址:taro-template
未完待续…