Taro开发总结

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(/]+>/g, function () {
     
        return ``})}render(){
     const{
      content }=this.state
    return(<View className='html-parse'><RichText className='rich-text' nodes={
     content}/></View>)}}exportdefault 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.jsprod.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}
 */
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

未完待续…

你可能感兴趣的:(react,微信小程序,taro)