微信小程序及h5,基于taro,zoro最佳实践探索

这段时间一直忙于公司业务中,不可自拔,好在通过零零星星的点滴时间,慢慢的还是完成了脚手架搭建。微信小程序发展到现在,已经不再像以前只是简简单单的应用,业务愈发的臃肿了起来,同时也催生出了许多框架,mpvue,wepy以及后起之秀taro等。

搭建这次脚手架的目的主要是为了满足后期小程序快速开发的需求,首先看看该脚手架搭建的简单的todo演示应用效果


打包生成的微信小程序演示.gif

打包生成的h5应用演示.gif

最初选择taro,主要的原因是习惯于react开发方式,在taro还没发布正式版之前,上一个项目选择了wepy作为开发框架,深陷苦扰,我们不得不游走切换于wepy语法及原生小程序组件语法之间

特性

  • 简化redux的引入和配置,只需简单几步即可快速开发
  • 简易的环境配置,支持配置多环境开发
  • 对于业务错误进行了全局捕获,即可统一提示,又可定制性处理
  • 引入资源自动上传阿里云oss服务器,并自动替换成最终服务器路径
  • 封装request,支持restful api

快速开始

我们使用yarn工具代替npm进行依赖管理,没有安装yarn的,请先安装,实在不想安装,可以用npm代替

绑定oss配置信息

首先我们克隆脚手架到本地服务器

$ git clone git@github.com:FaureWu/ztaro.git

安装依赖包

$ cd ./ztaro
$ yarn # 如果没有安装yarn 则可以使用npm install

接下来我们需要配置阿里云oss服务器,打开文件./ztaro/config/config.js

module.exports = {
  // 阿里云oss插件配置
  oss: {
    dev: {
      accessKeyId: '************',
      accessKeySecret: '***************',
      endpoint: 'https://************.aliyuncs.com',
      region: '*************',
      bucket: '*********',
    },
    prod: {
      accessKeyId: '************',
      accessKeySecret: '***************',
      endpoint: 'https://************.aliyuncs.com',
      region: '*************',
      bucket: '*********',
    },
    path: 'src/assets/',
    prefix: '@oss',
    formats: ['png', 'jpeg', 'jpg', 'svg'],
  },
}

可以看到oss配置项,该配置项支持区分编译环境BUILD_ENV,dev为开发环境下的配置,prod为线上环境的配置,要是没有区分,那就都配置成一样的嘛,其中accessKeyId,accessKeySecret,endpoint,region,bucket都是阿里云oss基本配置,这里就不多说了,说一下其他参数吧

  • path 这个路径用于指定需要上传到阿里云oss资源的搜索路径,这个路径下的资源并不会所有的全部都上传到阿里云,只会上传在代码中使用的并且以prefix开头的资源
  • prefix 需要上传到阿里云oss的前缀,也类似path的路径别名
  • formats 需要上传资源格式

以下代码仅用于展示如何使用,需要把资源放入上面配置的path路径下,无法使用require, import导入,无法在tabbar中配置

<Image src="@oss/logo.jpeg" /> // 可以这样用

或者

 const activeHomeIcon = '@oss/home-active.png' // 或者这样用

或者在样式文件中

.app {
  background: url('@oss/logo.jpeg') // 还可以这样用
}

又或者在json文件中

{
  "logo": "@oss/logo.jpeg" // 又或者这样用
}

让微信小程序跑起来

执行如下命令,该命令会做三件事

  • 编译taro语法为微信小程序语法,并启动监听文件修改
  • 启动本地mock服务器
  • 启动gulp任务,上传图片资源,并监听文件修改
$ yarn mock:weapp

等待命令执行完成,会在根目录下生成dist目录,打开微信开发者工具,选择编译后dist预览最终效果

编写todo应用

当我们准备开始编写一个功能前,我们希望能数据驱动开发,所以首先我们编写模拟api请求,该脚手架主要是采用express搭建一个简易的node api服务器,通过faker进行模拟数据,如果习惯其他生成模拟数据的库,可以替换faker

编写获取todo列表的接口,新建文件ztaro/mock/todos.js

const faker = require('faker')

function createTodos(number) {
  const todos = []
  for (let i = 0; i < number; i += 1) {
    todos.push({
      id: faker.random.uuid(),
      text: faker.random.words(10),
    })
  }

  return todos
}

let todos = createTodos(faker.random.number({ min: 3, max: 6 }))

function getTodos(req, res) {
  res.status(200).json({
    code: 'success',
    message: '获取待办列表成功',
    data: todos,
  })
}

module.exports = {
  'GET /v1/todos': getTodos,
}

编写getTodos的request请求,新建/ztaro/src/requests/todos.js

import request from '../utils/request'

export function getTodos() {
  return request({
    url: '/v1/todos',
  })
}

编写用于todos model,在ztaro/src/models/todos.js

import { getTodos } from '../requests/todos'

export default {
  namespace: 'todos',
  state: {
    lists: [],
  },
  // 这里的配置用于扩展model,model之前共用逻辑
  // common mixins定义于ztaro/src/mixins/common.js
  // 主要提供共用的update action
  mixins: ['common'],
  effects: {
    async getTodos(action, { put }) {
      const { data } = await getTodos()
      // 这里的update是由于上面引入了common mixins
      // 如果没有引入打开下方的reducers注释
      put({ type: 'update', payload: { lists: data } })
    },
  },
  // reducers: {
  //  update({ payload }, state) {
  //    return { ...state, ...payload }
  //  },
  // },
  },

将todos model注入到应用中,引入到ztaro/src/models/index.js

import todos from './todos'

export default [todos]

该文件会被引入到到app.js中

数据准备已经完成,我们可以开始编写界面,ztaro/src/pages/todos/*

import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { connect } from '@tarojs/redux'
import { dispatcher } from '@opcjs/zoro'

import ComponentSpin from '../../components/spin/spin'

import './todos.scss'

// 从redux中绑定数据到界面
@connect(({ todos }) => ({
  todos: todos.lists,
}))
class PageTodos extends Component {
  config = {
    navigationBarTitleText: '待办事项',
  }

  state = {
    loading: false,
  }

  componentWillMount() {
    this.showLoading()
    dispatcher.todos
      .getTodos()
      // 获取成功之后执行.then
      .then(this.hideLoading)
      // 获取失败之后执行.catch
      .catch(this.hideLoading)
  }

  showLoading = () => this.setState({ loading: true })

  hideLoading = () => this.setState({ loading: false })

  render() {
    const { todos } = this.props
    const { value, loading } = this.state

    return (
      <View className="todos">
        <ComponentSpin loading={loading} />
        <View className="logo" />
        {todos.map(todo => (
          <View className="todo" key={todo.id}>
            <Text>{todo.text}</Text>
          </View>
        ))}
      </View>
    )
  }
}

export default PageTodos

最后我们需要编写我们的样式

.todos {
  position: relative;
  counter-reset: count;

  .logo {
    display: block;
    height: 160px;
    // 这里以@oss前坠开头,因此编译时会被上传至阿里云oss,并替换成最终路径
    background-image: url("@oss/logo.jpeg");
    background-size: auto 160px;
    background-repeat: no-repeat;
    background-origin: center;
  }

  .todo {
    font-size: 28px;
    word-break: break-all;
    counter-increment: count;
    padding: 10px;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: flex-start;

    &::before {
      content: counter(count);
      display: inline-block;
      border: 2px solid #e9e9e9;
      padding: 0 10px;
      margin: 0 10px 0 0;
      border-radius: 10px;
    }
  }
}

全局错误提示

在zoro框架中,可以注册一个全局错误函数onError,该函数注册于ztaro/src/app.js

import zoro from '@opcjs/zoro'

const app = zoro({
  onError(error) {
    if (error.message) {
      Taro.showToast({
        icon: 'none',
        title: error.message,
        duration: 2000,
      })
    }
  },
})

zoro框架会对于每一个effect外层做了try/catch,捕获在执行effect过程中抛出的一切错误,并回调到onError函数里

那我们如何捕获异步请求的错误呢,我们可以移步ztaro/src/utils/request.js

export default function request(options) {
  const { url } = options
  return Taro.request(
    resolveParams({
      ...options,
      url: `${CONFIG.SERVER}${url}`,
      mode: 'cors',
      header: {
        'content-type': 'application/json',
        ...options.header,
      },
    }),
  )
  .then(checkHttpStatus)
  .then(checkSuccess)
  .catch(throwError)
}

封装的request函数中有checkHttpStatus,checkSuccess两个函数,我们分别看下它们做了什么

function checkHttpStatus(response) {
  // 当http状态在200到300之间时,说明请求是成功的
  // 我们只需返回响应的数据即可
  if (response.statusCode >= 200 && response.statusCode < 300) {
    return response.data
  }
  
  // 当状态出现错误时,我们需要抛出相关信息
  // 这样错误被一层层往上抛出,最终被zoro捕获,回调onError函数
  const message =
    HTTP_ERROR[response.statusCode] || `ERROR CODE: ${response.statusCode}`
  const error = new Error(message)
  error.response = response
  throw error
}

function checkSuccess(data) {
  // 当数据是个字符串,或者是ArrayBuffer时,我们认为业务是成功的
  // 返回数据即可
  if (typeof data === 'string' && data instanceof ArrayBuffer) {
    return data
  }

  // 当业务响应数据中的code值返回SUCCESS时,我们认为业务是成功的
  // 这里主要根据与后端约定好的格式,你可以根据实际情况进行更改
  if (
    typeof data.code === 'string' &&
    data.code.toLocaleUpperCase() === 'SUCCESS'
  ) {
    return data
  }
  
  // 当业务出现错误时,我们依旧需要获取后台抛出的错误,
  // 像上一层抛出错误,最终被zoro捕获,回调onError
  const error = new Error(data.message)
  error.data = data
  throw error
}

这样处理过后,当后台接口报错,或者业务错误时,就会看到弹出toast错误提示了,无需额外的处理

那当我们想要屏蔽某些不那么重要的接口,错误提示,我们该怎么办呢?就那getTodos来举例,我们只需修改它

async getTodos(action, { put }) {
  try {
    const { data } = await getTodos()
    put({ type: 'update', payload: { lists: data } })
  } catch (error) {
    // 这里仅仅是为了可以知道该接口是否错误
    return { isError: true, error }
  }
}

这样即使是getTodos接口抛出错误也不会触发全局错误提示了

接下来我们可以自定义该接口的错误逻辑了

dispatcher.todos.getTodos().then((data = {}) => {
  if (data.isError) {
    // 执行一些相关的错误
  }
})

假如我们想要在dispatcher.todos.getTodos()的.then函数中获取到某些数据,我们又该如何呢?依旧那getTodos举例

async getTodos(action, { put }) {
  const { data } = await getTodos()
  put({ type: 'update', payload: { lists: data } })
  return data
}

然后我们在使用的时候

dispatcher.todos.getTodos().then(data => console.log(data))

与服务器进行接口联调

在进行接口联调之前,我们首先需要配置开发环境下的api地址,打开./ztaro/config/config.js

module.exports = {
  server: {
    // 修改下面这一行为你的开发环境下的api服务器
    dev: 'https://devapiserver',
  },
}

配置完成后执行如下命令

yarn dev:weapp

这个所有的api便会指向开发环境下的api服务器地址了

打包测试包

执行如下命令即可

yarn build:weapp-dev

打包线上包

首先配置生产环境下的api地址,打开./ztaro/config/config.js

module.exports = {
  server: {
    // 修改下面这一行为你的生产环境下的api服务器
    prod: 'https://devapiserver',
  },
}

配置完成后执行

yarn build:weapp

以上教程仅列出了微信小程序端,h5端也基本是一致的,只需执行的命令中,将weapp替换成h5即可

其他更详细的使用方式,请查看对应github仓库

欢迎star,欢迎加我咨询相关问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 相信做过微信小程序的都知道,官方给出的微信web开发工具上根本就无法加载node_modules包,即使可以加载,...
    萧玄辞阅读 1,320评论 0 2
  • 许村漫记~001(2017.7.15) 出发 许村漫记~002(2017.7.15) 到了 许村漫记~003(20...
    王轶琼阅读 1,087评论 0 1
  • 深夜 憧憬着 我爱的你 每一次遇见 想轻抚你脸颊 拨弄你秀发 触摸你的唇 傻傻的我不敢 惊醒梦中你 终有一天 你走...
    草中藏朱阅读 376评论 7 13
  • P199–214 1. 把奖励当作学习的诱饵提出来,是一种成人要求儿童以成绩回报自己的行贿手段,他让孩子对学习不再...
    冰淇淋cathy阅读 143评论 0 0