优质广告供应商

广告是为了更好地支持作者创作

微信小程序及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,欢迎加我咨询相关问题

优质广告供应商

广告是为了更好地支持作者创作

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 131,290评论 18 138
  • 相信做过微信小程序的都知道,官方给出的微信web开发工具上根本就无法加载node_modules包,即使可以加载,...
    萧玄辞阅读 1,091评论 0 2
  • 优质广告供应商

    广告是为了更好地支持作者创作

  • 许村漫记~001(2017.7.15) 出发 许村漫记~002(2017.7.15) 到了 许村漫记~003(20...
    王轶琼阅读 917评论 0 1
  • 深夜 憧憬着 我爱的你 每一次遇见 想轻抚你脸颊 拨弄你秀发 触摸你的唇 傻傻的我不敢 惊醒梦中你 终有一天 你走...
    池塘柳阅读 312评论 7 13
  • P199–214 1. 把奖励当作学习的诱饵提出来,是一种成人要求儿童以成绩回报自己的行贿手段,他让孩子对学习不再...
    冰淇淋cathy阅读 80评论 0 0