Labrador 是一个专为微信小程序开发的模块化的前端开发框架

微信小程序开发三宗罪和解决方案一文中我向大家阐述了微信小程序开发的三个弊端,并提供了Labrador框架来解决这些弊端。

在上一个版本的Labrador中,组件重用部分功能不完善,今天Labrador发布了0.3版本,相对上一个版本,提供了更强大的组件化功能,并更改了一些模块接口。

下面是Labrador 0.3.x版本的入门手册,如果你已经基于老版本Labrador构建了项目,请参照下面的说明对应升级项目,并升级一下全局的 labrador-cli 库到0.3版本。


QQ交流群 282140496


特性

  • 使用Labrador框架可以使微信开发者工具支持加载海量NPM包

  • 支持ES6/ES7标准代码,使用async/await能够有效避免回调地狱

  • 组件重用,对微信小程序框架进行了二次封装,实现了组件重用和嵌套

  • 使用Editor Config及ESLint标准化代码风格,方便团队协作

安装

npm install -g labrador-cli

初始化项目

mkdir demo
cd demo
npm init
labrador init

项目目录结构

demo                 # 项目根目录
├── .babelrc         # babel配置文件
├── .editorconfig    # Editor Config
├── .eslintignore    # ESLint 忽略配置
├── .eslintrc        # ESLint 语法检查配置
├── package.json
├── dist/            # 目标目录
├── node_modules/
└── src/             # 源码目录
    ├── app.js
    ├── app.json
    ├── app.less
    ├── components/  # 通用组件目录
    ├── pages/       # 页面目录
    └── utils/

注意 dist目录中的所有文件是由labrador命令生成,请勿直接修改

配置开发工具

项目初始化后使用WebStorm或Sublime等你习惯的IDE打开项目根目录。然后打开 微信web开发者工具 新建项目,本地开发目录选择 dist 目标目录。

开发流程

在WebStorm或Sublime等IDE中编辑 src 目录下的源码,然后在项目根目录中运行labrador build 命令构建项目,然后在 微信web开发者工具 的调试界面中点击左侧菜单的 重启 按钮即可查看效果。

我们在开发中, 微信web开发者工具 仅仅用来做调试和预览,不要在 微信web开发者工具 的编辑界面修改代码。

微信web开发者工具 会偶尔出错,表现为点击 重启 按钮没有反应,调试控制台输出大量的无法require文件的错误,编辑 界面中代码文件不显示。这是因为 labrador build 命令会更新整个 dist 目录,而 微信web开发者工具 在监测代码改变时会出现异常,遇到这种情况只需要关掉 微信web开发者工具 再启动即可。

我们还可以使用 labrador watch 命令来监控 src 目录下的代码,当发生改变后自动构建,不用每一次编辑代码后手动运行 labrador build

所以最佳的姿势是:

  1. 在项目中运行 labrador watch

  2. 在WebStorm中编码,保存

  3. 切换到 微信web开发者工具 中调试、预览

  4. 再回到WebStorm中编码

  5. ...

labrador 库

labrador 库对全局的 wx 变量进行了封装,将大部分 wx 对象中的方法进行了Promise支持, 除了以 on* 开头或以 *Sync 结尾的方法。在如下代码中使用 labrador 库。

import wx from 'labrador';

我们建议不要再使用 wx.getStorageSync() 等同步阻塞方法,而在 async 函数中使用 await wx.getStorage() 异步非阻塞方法提高性能,除非特殊情况。

app.js

src/app.js 示例代码如下:

import wx from 'labrador';
import { sleep } from './utils/util';

export default class {
  globalData = {
    userInfo: null
  };

  async onLaunch() {
    //调用API从本地缓存中获取数据
    let logs = await wx.getStorage({ key: 'logs' }) || [];
    logs.unshift(Date.now());
    await wx.setStorage({ key: 'logs', data: logs });
    this.timer();
  }

  async timer() {
    while (true) {
      console.log('hello');
      await sleep(10000);
    }
  }

  async getUserInfo() {
    if (this.globalData.userInfo) {
      return this.globalData.userInfo;
    }
    await wx.login();
    let res = await wx.getUserInfo();
    this.globalData.userInfo = res.userInfo;
    return res.userInfo;
  }
}

代码中全部使用ES6/ES7标准语法。代码不必声明 use strict ,因为在编译时,所有代码都会强制使用严格模式。

代码中并未调用全局的 App() 方法,而是使用 export 语法默认导出了一个类,在编译后,Labrador会自动增加 App() 方法调用,所有请勿手动调用 App() 方法。

自定义组件

Labrador的自定义组件,是基于微信小程序框架的组件之上,进一步自定义组合,拥有逻辑处理和样式。这样做的目的请参见 微信小程序开发三宗罪和解决方案

项目中通用自定义组件存放在 src/compontents 目录,一个组件一般由三个文件组成,*.js*.xml*.less 分别对应微信小程序框架的 jswxmlwxss 文件。在Labardor项目源码中,我们特意采用了 xmlless 后缀以示区别。

自定义组件示例

下面是一个简单的自定义组件代码实例:

逻辑 src/compontents/title/title.js

import wx from 'labrador';
import randomColor  from '../../utils/random-color';

export default class Title extends wx.Component {
  data = {
    text: '',
    color: randomColor()
  };

  handleTap() {
    this.setData({
      color: randomColor()
    });
  }
}

布局 src/compontents/title/title.js

<view class="text-view">
  <text class="title-text" catchtap="handleTap" style="color:{{color}};">{{text}}</text>
</view>

样式 src/compontents/title/title.less

.title-text {
  font-weight: bold;
  font-size: 2em;
}

代码和微信小程序框架中的page很相似。最大的区别是在js逻辑代码中,没有调用全局的Page()函数声明页面,而是用 export 语法导出了一个默认的类,这个类需要继承与 labrador.Component 组件基类。

注意 组件中事件响应方法必须以 handle 开头!例如上文中的 handleTap

页面

我们要求所有的页面必须存放在 pages 目录中,每个页面的子目录中的文件格式和自定义组件一致,只是可以多出一个 *.json 配置文件。

页面示例

下面是默认首页的示例代码:

逻辑 src/pages/index/index.js

import wx from 'labrador';
import List from '../../components/list/list';
import Title from '../../components/title/title';

export default class Index extends wx.Component {
  data = {
    userInfo: {}
  };
  children = {
    list: new List(),
    motto: new Title({ text: 'Hello world' })
  };

  //事件处理函数
  handleViewTap() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  }

  async onLoad() {
    //调用应用实例的方法获取全局数据
    let userInfo = await wx.app.getUserInfo();
    //更新数据
    this.setData({
      userInfo: userInfo
    });
    this.update();
  }
}

布局 src/pages/index/index.xml

<view class="container">
  <view bindtap="handleViewTap" class="userinfo">
    <image class="userinfo-avatar" src="{{ userInfo.avatarUrl }}" background-size="cover"/>
    <text class="userinfo-nickname">{{ userInfo.nickName }}</text>
  </view>
  <view class="usermotto">
    <component key="motto" name="title"/>
  </view>
  <component key="list"/>
</view>

样式

@import 'list';
@import 'title';

.motto-title-text {
  font-size: 3em;
  padding-bottom: 1rem;
}

/* ... */

页面代码的格式和自定义组件的格式一模一样,我们的思想是 页面也是组件,页面和自定义组件的唯一差别是页面的代码存放在 pages 目录中。

js逻辑代码中同样使用 export 语句导出了一个默认类,也不能手动调用 Page() 方法,因为在编译后,pages 目录下的所有js文件全部会自动调用 Page() 方法声明页面。

我们看到组件类中,有一个对象属性 children ,这个属性定义了该组件依赖、包含的其他自定义组件,在上面的代码中页面包含了两个自定义组件 listtitle ,这个两个自定义组件的 key 分别为 listmotto

xml布局代码中,使用了Labrador提供的 <component/> 标签,此标签的作用是导入一个自定义子组件的布局文件,标签有两个属性,分别为 key (必选)和 name (可选,默认为key的值)。key 与js逻辑代码中的组件 key 对应,name 用来在src/componetsnode_modules 目录中寻找子组件模板。运行时,key对应的子组件逻辑代码类中的 data 将渲染至子组件模板中。

less样式文件中,我们使用了两条 @import 语句加载子组件样式,这里的 @import 'list' 语句按照LESS的语法,会首先寻找当前目录 src/pages/index/ 中的 list.less 文件,如果找不到就会尝试寻找 src/componetsnode_modules 目录中的组件样式。

接下来,我们定义了 .motto-title-text 样式,这样做是因为 motto key 代表的title组件的模板中有一个view 属于 title-text 类,编译时,Labrador将自动为其增加一个前缀 motto- ,所以编译后这个view所属的类为 title-text motto-title-text 那么我们就可以在父组件的样式代码中使用 .motto-title-text 重新定义子组件的样式。

注意 虽然我们采用了LESS文件,但是由于微信小程序框架的限制,不能使用LESS的层级选择及嵌套语法。但是我们可以使用LESS的变量、mixin、函数等功能方便开发。

另外Labrador支持多层组件嵌套,在上述的实例中,index 包含子组件 listtitlelist 包含子组件 title,所以在最终显示时,index 页面上回显示两个 title 组件。

详细代码请参阅 labrador init 命令生成的示例项目。

总结

页面也是组件,所有的组件都拥有一样的生命周期函数onLoad, onReady, onShow, onHide, onUnload 以及setData函数。

componetspages 两个目录的区别在于,componets 中存放的组件能够被智能加载,pages 目录中的组件在编译时自动加上 Page() 调用,所以,pages 目录中的组件不能被其他组件调用,如果某个组件需要重用,请存放在 componets 目录或打包成NPM包。

贡献者

郑州脉冲软件科技有限公司

梁兴臣

开源协议

本项目依据MIT开源协议发布,允许任何组织和个人免费使用。

21 条评论
lcfevr · 2016年10月10日

index.js:129 Error: Error, realFilepath = npm/babel-runtime/regenerator/index.js, modId = pages/index/index.js, requireId = ../../npm/babel-runtime/regenerator/index.js

这是什么原因 少引入了一个bable库的原因吗

回复

Eyas_Liu · 2016年10月10日

这么好的东西,怎么就没几个 star ,真是奇怪极了

回复

Eyas_Liu · 2016年10月10日

建议到 github 提个pr,让更多的人知道这个工具

回复

攻城猫 · 2016年10月10日

async这个运行好像有问题,我本地跑,getUserInfo没运行

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

应该是因为你用的破解的IDE,并没有微信小程序开发权限,在这种情况下 getUserInfo 会抛出异常的,你可以 try...catch 试试

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日
  • 像文中说的,可能是微信Web开发者工具异常,重启软件试试

  • 你本地的npm可能是v2版,在labrador init 初始化项目时没有指定安装babel-runtime ,你需要手动安装缺失的NPM库

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

已提交

回复

攻城猫 · 2016年10月10日

我index.js直接调用wx.getUserInfo()是没问题的,用app.js里面的 async getUserInfo方法就不行,您本地跑起来是好的么?

回复

攻城猫 · 2016年10月10日

实际上app.js里面的async onLaunch里的this.timer()也没有运行

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

示例代码在我这里是没有问题的,你那里的labrador是不是最新版本,npm是不是v3版本,查看dist/app.js文件,babel是否正常将async函数转码了

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

sorry,是我搞错了,Labrador中有一个bug,现在正在解决

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

问题解决了,请将全局的 labrador-cli 升级至 0.3.2。 谢谢你的反馈

回复

攻城猫 · 2016年10月10日

ok,辛苦了

回复

lcfevr · 2016年10月10日

bable-runtime是安装了的

回复

lcfevr · 2016年10月10日

现在是新引入的库会报这个路径错误

回复

脉冲软件_梁兴臣 作者 · 2016年10月10日

首先查看对应文件是否存在,如果存在,编译项目时增加 -t参数 labrador build -t 这样的话编译后的所有文件会有try...catch 嫩能够查看更详细的错误

回复

labolado · 2016年10月10日

onLaunch不执行, async timer() {

while (true) {
  console.log('hello##########');
  await sleep(1000);

}

} 我通过点击触发只执行一次

回复

labolado · 2016年10月10日

用的是0.3.2的版本,启动会有个错误 asdebug.js:1 TypeError: Cannot read property 'Setting' of undefined(…)(anonymous function) @ asdebug.js:1(anonymous function) @ asdebug.js:1
title.js:71 handleTap

但页面会正常显示

回复

lcfevr · 2016年10月11日

建议image文件夹也可以打包到dist里

回复

脉冲软件_梁兴臣 作者 · 2016年10月11日

src/image 是可以打包到 dist/image 的,需要 labrador-cli@0.3.3 之前的版本有bug

回复

载入中...
脉冲软件_梁兴臣 脉冲软件_梁兴臣

240 声望

发布于专栏

脉冲软件-梁兴臣

专注JS全栈开发

12 人关注