这段时间一直忙于公司业务中,不可自拔,好在通过零零星星的点滴时间,慢慢的还是完成了脚手架搭建。微信小程序发展到现在,已经不再像以前只是简简单单的应用,业务愈发的臃肿了起来,同时也催生出了许多框架,mpvue,wepy以及后起之秀taro等。
搭建这次脚手架的目的主要是为了满足后期小程序快速开发的需求,首先看看该脚手架搭建的简单的todo演示应用效果
最初选择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,欢迎加我咨询相关问题