tua-mp
是一个用于开发微信小程序的渐进式框架,它与其他小程序框架不同的是,tua-mp
可以由浅入深地用于你的小程序项目。
1.1.最基础的使用方式 -- examples/basic/
下载 https://github.com/tuateam/tua-mp/blob/master/packages/tua-mp/examples/basic/utils/tua-mp.js 文件到你的小程序项目中,例如保存为 utils/tua-mp.js
。
代码片段地址为:wechatide:https://minicode/2n17t5mU752Z
可以尝试复制以上片段地址到浏览器地址栏中打开
接着在入口的 js
代码中:
- 在页面中使用
TuaPage
替代小程序提供的Page
- 在组件中使用
TuaComp
替代小程序提供的Component
替换后即可使用开发 Vue
的方式来开发小程序。
// pages/index/index.js
import { TuaPage } from '../../utils/tua-mp'
// Page -> TuaPage
TuaPage({ ... })
// comps/foobar/foobar.js
import { TuaComp } from '../../utils/tua-mp'
// Component -> TuaComp
TuaComp({ ... })
采用这种侵入性最小的方式,可以用于改写优化已有的小程序项目,即在部分页面中使用 tua-mp
。
在这部分我们将使用 webpack 来打包我们的源码,但其中 webpack 繁琐的配置已预先封装在 @tua-mp/service 里了。
因此很自然地,日常前端开发中的各位“老朋友们”又回来了~
- npm
- babel
- eslint
- less/scss/stylus
- ...
推荐使用 vue-cli 或 @tua-mp/cli 一键生成项目:
$ vue init tua-mp-templates/simple my-project
# OR
$ tuamp init tua-mp-templates/simple my-project
- 开发时运行
npm start
,webpack
就会开启监听 - 发布时运行
npm run build
,webpack
会先删除dist/
然后将源码压缩生成到其中
在这个例子中我们将源码放在了 src/
下,利用 webpack
将其打包生成到 dist/
目录下。
此外还对于样式的编写加入了预处理器的功能
- wxss: 会被拷贝到 dist/ 下的对应路径
- css: 需要在 js 中引入,生成对应的 wxss
- less: 需要在 js 中引入,生成对应的 wxss
- scss/sass: 需要在 js 中引入,生成对应的 wxss
- stylus: 需要在 js 中引入,生成对应的 wxss
推荐使用 vue-cli 或 @tua-mp/cli 一键生成项目:
$ vue init tua-mp-templates/vue my-project
# OR
$ tuamp init tua-mp-templates/vue my-project
在这个例子中我们添加了 vue-loader
,让我们能够使用文件扩展名为 .vue
的 single-file components
(单文件组件) 。
单文件组件就是将模板(template)、脚本(script)、样式(style)写在一个文件中。
但在这个例子中的单文件组件和一般 web 端的单文件组件有所不同:
1.页面的模板需要添加 lang="wxml"
<template lang="wxml">
<!-- 小程序模板代码 -->
</template>
2.原本的 .json
文件变成了 <config>
<!-- 默认 json -->
<config>
{
"navigationBarTitleText": "tua-mp todos",
"usingComponents": {
"Todo": "./comps/Todo/Todo",
"Filter": "/comps/Filter/Filter"
}
}
</config>
<!-- yaml 或者 yml 也支持 -->
<config lang="yml">
navigationBarTitleText: 'tua-mp todos'
usingComponents:
Todo: ./comps/Todo/Todo
Filter: /comps/Filter/Filter
</config>
以上两个例子中的 /pages/todos/todos
页面都实现了 todos 应用。
使用方式上和 Vue 对齐,对 Vue 还不熟悉?
- 实现相同的组件配置(data、computed、methods、watch)
- 实现赋值改变数据和界面,而不是调用小程序原生的
this.setData
- 实现完整
computed
功能 - 实现完整
watch
功能 - 实现异步
setData
功能,即假如在一个事件循环周期内多次对于同一个属性赋值,只会触发一次小程序原生的setData
函数以及相关的watch
函数(详见下面例子中的onLoad
函数) - 实现生命周期钩子的适配
- 实现小程序原生组件的适配
- 可以传递 Vue 风格的 props
- 可以使用 computed、watch
- 并使用 $emit 封装了原生的 triggerEvent 方法
import { TuaComp, TuaPage } from 'tua-mp'
// 在组件中使用 TuaComp 替代小程序提供的 Component
TuaComp({ ... })
// 使用 TuaPage 替代小程序提供的 Page
TuaPage({
// data 可以是类似 Vue 的函数形式(推荐),也可以是类似小程序的对象形式
// 注意:需要绑定的数据必须要先在 data 里声明!
// 因为 ES5 的 getter/setter 方法无法感知添加新的属性
data () {
return {
a: { b: 'b' },
c: [{ d: { e: 'e' } }],
f: 'f',
g: 'hello world',
// 注意:因为小程序会使用类似 /^__.*__$/
// 这样的属性保存内部状态,例如:
// __webviewId__、__route__、__wxWebviewId__
// 所以这样的前后两个下划线起名的属性
// 在初始化观察数据时会被略过,即不会生成 getter/setter
__foo__: 'bar',
}
},
// 计算属性
computed: {
// 注意这里是函数
reversedG () {
return this.g.split('').reverse().join('')
},
// 多个依赖也没问题
gAndAB () {
return this.g + ' + ' + this.a.b
},
// 还可以由 computed 继续派生新的数据
dataAndComputed () {
return this.g + ' + ' + this.reversedG
},
},
// 小程序原本的生命周期方法也能使用
// 建议不要放在 methods 里,
// 因为就像 Vue 中的 created、mounted 等生命周期方法一样
onLoad () {
for (let i = 100; i > 90; i--) {
// 只会触发一次 setData
this.g = i
}
},
// Vue 生命周期的适配
created () {},
mounted () {},
beforeUpdate () {},
updated () {},
// 侦听器
watch: {
// 监听 data
g (newVal, oldVal) {
console.log(`g: ${oldVal} -> ${newVal}`)
// 异步操作
setTimeout(() => {
this.a.b = 'new a.b from watch'
}, 1000)
},
// 监听嵌套属性
'a.b' (newVal, oldVal) {
console.log(`a.b: ${oldVal} -> ${newVal}`)
// 异步操作
setTimeout(() => {
this.msg = 'new msg from watch'
}, 1000)
}
// 监听 computed
reversedG (newVal, oldVal) {
// ...
},
// 数组、deep、immediate
a: [
{ deep: true, immediate: true, handler () {} },
// 调用 methods 中的 aFn 方法
'aFn',
// 同样调用 methods 中的 aFn 方法
{ immediate: true, handler: 'aFn' }
],
},
// 方法建议都挂在 methods 下
methods: {
aFn () {},
onTap () {
// 类似 Vue 的操作方式
this.f = 'onTap'
this.a.b = 'onTap'
this.c[0].d.e = 'onTap'
// 劫持了数组的以下方法: pop, push, sort, shift, splice, unshift, reverse
this.c.push('onTap')
// 对于不改变原数组的以下方法: map, filter, concat, slice...
// 建议采取直接替换原数组的方式
this.c = this.c.map(x => x + 1)
// 注意:请在 data 中先声明 x!否则无法响应 x 的变化...
this.x = 'x'
},
},
})
框架开发过程中的坑和心得记录:
项目 | 状态 | 介绍 |
---|---|---|
@tua-mp/cli | 命令行工具 | |
@tua-mp/service | 项目构建工具 | |
tua-api | api 生成工具 | |
tua-storage | 通用存储层 |
详见 issues
Copyright (c) 2018-present, StEve Young