微信小程序的開發(fā)范式BeautyWe.js入門詳解
一個簡單的介紹

BeautyWe.js 是什么?
它是一套專注于微信小程序的企業(yè)級開發(fā)范式,它的愿景是:
讓企業(yè)級的微信小程序項目中的代碼,更加簡單、漂亮。
為什么要這樣命名呢?
Write beautiful code for wechat mini program by the beautiful we!
「We」 既是我們的 We,也是微信的 We,Both beautiful!
那么它有什么賣點呢?
- 專注于微信小程序環(huán)境,寫原汁原味的微信小程序代碼。
- 由于只專注于微信小程序,它的源碼也很簡單。
- 插件化的編程方式,讓復(fù)雜邏輯更容易封裝。
- 再加上一些配套設(shè)施:
- 一些官方插件。
- 一套開箱即用,包含了工程化、項目規(guī)范以及微信小程序環(huán)境獨特問題解決方案的框架。
- 一個CLI工具,幫你快速創(chuàng)建應(yīng)用,頁面,組件等。
它由以下幾部分組成:
一個插件化的核心 - BeautyWe Core
對 App、Page 進行抽象和包裝,保持傳統(tǒng)微信小程序開發(fā)姿勢,同時開放部分原生能力,讓其具有「可插件化」的能力。
一些官方插件 — BeautyWe Plugins
得益于 Core 的「可插件化」特性,封裝復(fù)雜邏輯,實現(xiàn)可插拔。官方對于常見的需求提供了一些插件:如增強存儲、發(fā)布/訂閱、狀態(tài)機、Logger、緩存策略等。
一套開箱即用的項目框架 - BeautyWe Framework
描述了一種項目的組織形式,開箱即用,集成了 BeautyWe Core ,并且提供了如:全局窗口、開發(fā)規(guī)范、多環(huán)境開發(fā)、全局配置、NPM 等解決方案。
一個CLI工具 - BeautyWe Cli
提供快速創(chuàng)建應(yīng)用、頁面、插件,以及項目構(gòu)建功能的命令行工具。并且還支持自定義的創(chuàng)建模板。
一個簡單的例子
下載

用 BeautyWe 包裝你的應(yīng)用

之后,你就能使用 BeautyWe Plugin 提供的能力了。

開放原生App/Page,支持插件化
new BtApp({...}) 的執(zhí)行結(jié)果是對原生的應(yīng)用進行包裝,其中包含了「插件化」的處理,然后返回一個新的實例,這個實例適配原生的 App() 方法。
下面來講講「插件化」到底做了什么事情。
首先,插件化開放了原生 App 的四種能力:
1.Data 域
把插件的 Data 域合并到原生 App 的 Data 域中,這一塊很容易理解。
2.原生鉤子函數(shù)
使原生鉤子函數(shù)(如 onShow, onLoad)可插件化。讓原生App與多個插件可以同時監(jiān)聽同一個鉤子函數(shù)。如何工作的,下面會細(xì)說。
3.事件鉤子函數(shù)
使事件鉤子函數(shù)(與 view 層交互的鉤子函數(shù)),盡管在實現(xiàn)上有一些差異,但是實現(xiàn)原理跟「原生鉤子函數(shù)」一樣的。
4.自定義方法
讓插件能夠給使用者提供 API。為了保證插件提供的 API 足夠的優(yōu)雅,支持當(dāng)調(diào)用插件 API 的時候(如 event 插件 this.event.on(...) ),API 方法內(nèi)部仍然能通過 this 獲取到原生實例。
鉤子函數(shù)的插件化
原生鉤子函數(shù),事件鉤子函數(shù)我們統(tǒng)一稱為「鉤子函數(shù)」。
對于每一個鉤子函數(shù),內(nèi)部是維護一個以 Series Promise 方式執(zhí)行的執(zhí)行隊列。
以 onShow 為例,將會以這樣的形式執(zhí)行:
native.onShow → pluginA.onShow → pluginB.onShow → ...
下面深入一下插件化的原理:

工作原理是這樣的:
- 經(jīng)過
new BtApp(...)包裝,所有的鉤子函數(shù),都會有一個獨立的執(zhí)行隊列, - 首先會把原生的各個鉤子函數(shù)
push到對應(yīng)的隊列中。然后每 use 插件的時候,都會分解插件的鉤子函數(shù),往對應(yīng)的隊列push。 - 當(dāng)
Native App(原生)觸發(fā)某個鉤子的時候,BtApp會以 Promise Series 的形式按循序執(zhí)行對應(yīng)隊列里面的函數(shù)。 - 特殊的,
onLaunch和onLoad的執(zhí)行隊列中,會在隊列頂部插入一個初始化的任務(wù)(initialize),它會以同步的方式按循序執(zhí)行Initialize Queue里面的函數(shù)。這正是插件生命周期函數(shù)中的plugin.initialize。
這種設(shè)計能提供以下功能:
1.可插件化
只需要往對應(yīng)鉤子函數(shù)的事件隊列中插入任務(wù)。
2.支持異步
由于是以 Promise Series 方式運行的,其中一個任務(wù)返回一個 Promise,下一個任務(wù)會等待這個任務(wù)完成再開始。如果發(fā)生錯誤,會流轉(zhuǎn)到原生的 onError() 中。
3.解決了微信小程序 app.js 中 getApp() === undefinded 問題
造成這個問題,本質(zhì)是因為 App() 的時候,原生實例未創(chuàng)建。但是由于 Promise 在 event loop 中是一個微任務(wù),被注冊在下一次循環(huán)。所以 Promise 執(zhí)行的時候 App() 早已經(jīng)完成了。
一些官方插件
BeautyWe 官方提供了一系列的插件:
- 增強存儲: Storage
- 數(shù)據(jù)列表:List Page
- 緩存策略:Cache
- 日志:Logger
- 事件發(fā)布/訂閱:Event
- 狀態(tài)機:Status
它們的使用很簡單,哪里需要插哪里。
由于篇幅的原因,下面挑幾個比較有趣的來講講,更多的可以看看官方文檔:BeautyWe
增強存儲 Storage
該功能由 @beautywe/plugin-storage 提供。
由于微信小程序原生的數(shù)據(jù)存儲生命周期跟小程序本身一致,即除用戶主動刪除或超過一定時間被自動清理,否則數(shù)據(jù)都一直可用。
所以該插件在 wx.getStorage/setStorage 的基礎(chǔ)上,提供了兩種擴展能力:
- 過期控制
- 版本隔離
一些簡單的例子
安裝
import { BtApp } from '@beautywe/core';
import storage from '@beautywe/plugin-storage';
const app = new BtApp();
app.use(storage());
過期控制
// 7天后過期
app.storage.set('name', 'jc', { expire: 7 });
版本隔離
app.use({ appVersion: '0.0.1' });
app.set('name', 'jc');
// 返回 jc
app.get('name');
// 當(dāng)版本更新后
app.use({ appVersion: '0.0.2' });
// 返回 undefined;
app.get('name');
更多的查看 @beautywe/plugin-storage 官方文檔
數(shù)據(jù)列表 List Page
對于十分常見的數(shù)據(jù)列表分頁的業(yè)務(wù)場景, @beautywe/plugin-listpage 提供了一套打包方案:
- 滿足常用「數(shù)據(jù)列表分頁」的業(yè)務(wù)場景
- 支持分頁
- 支持多個數(shù)據(jù)列表
- 自動捕捉下拉重載:
onPullDownRefresh - 自動捕捉上拉加載:
onReachBottom - 自帶請求鎖,防止帕金森氏手抖用戶
- 簡單優(yōu)雅的 API
一個簡單的例子:
import BeautyWe from '@beautywe/core';
import listpage from '@beautywe/plugin-listpage';
const page = new BeautyWe.BtPage();
// 使用 listpage 插件
page.use(listpage({
lists: [{
name: 'goods', // 數(shù)據(jù)名
pageSize: 20, // 每頁多少條數(shù)據(jù),默認(rèn) 10
// 每一頁的數(shù)據(jù)源,沒次加載頁面時,會調(diào)用函數(shù),然后取返回的數(shù)據(jù)。
fetchPageData({ pageNo, pageSize }) {
// 獲取數(shù)據(jù)
return API.getGoodsList({ pageNo, pageSize })
// 有時候,需要對服務(wù)器的數(shù)據(jù)進行處理,dataCooker 是你定義的函數(shù)。
.then((rawData) => dataCooker(rawData));
},
}],
enabledPullDownRefresh: true, // 開啟下拉重載, 默認(rèn) false
enabledReachBottom: true, // 開啟上拉加載, 默認(rèn) false
}));
// goods 數(shù)據(jù)會被加載到,goods 為上面定義的 name
// this.data.listPage.goods = {
// data: [...], // 視圖層,通過該字段來獲取具體的數(shù)據(jù)
// hasMore: true, // 視圖層,通過該字段來識別是否有下一頁
// currentPage: 1, // 視圖層,通過該字段來識別當(dāng)前第幾頁
// totalPage: undefined,
// }
只需要告訴 listpage 如何獲取數(shù)據(jù),它會自動處理「下拉重載」、「上拉翻頁」的操作,然后把數(shù)據(jù)更新到 this.data.listPage.goods 下。
View 層只需要描述數(shù)據(jù)怎么展示:
<view class="good" wx:for="listPage.goods.data"> ... </view> <view class="no-more" wx:if="listPage.goods.hasMore === false"> 沒有更多了 </view>
listpage 還支持多數(shù)據(jù)列表等其他更多配置,詳情看: @beautywe/plugin-listpage
緩存策略 Cache
@beautywe/plugin-cache 提供了一個微信小程序端緩存策略,其底層由 super-cache 提供支持。
特性
- 提供一套「服務(wù)端接口耗時慢,但加載性能要求高」場景的解決方案
- 滿足最基本的緩存需求,讀?。╣et)和保存(set)
- 支持針對緩存進行邏輯代理
- 靈活可配置的數(shù)據(jù)存儲方式
How it work
一般的請求數(shù)據(jù)的形式是,頁面加載的時候,從服務(wù)端獲取數(shù)據(jù),然后等待數(shù)據(jù)返回之后,進行頁面渲染:

但這種模式,會受到服務(wù)端接口耗時,網(wǎng)絡(luò)環(huán)境等因素影響到加載性能。
對于加載性能要求高的頁面(如首頁),一般的 Web 開發(fā)我們有很多解決方案(如服務(wù)端渲染,服務(wù)端緩存,SSR 等)。
但是也有一些環(huán)境不能使用這種技術(shù)(如微信小程序)。
Super Cache 提供了一個中間數(shù)據(jù)緩存的解決方案:

思路:
- 當(dāng)你需要獲取一個數(shù)據(jù)的時候,如果有緩存,先把舊的數(shù)據(jù)給你。
- 然后再從服務(wù)端獲取新的數(shù)據(jù),刷新緩存。
- 如果一開始沒有緩存,則請求服務(wù)端數(shù)據(jù),再把數(shù)據(jù)返回。
- 下一次請求緩存,從第一步開始。
這種解決方案,舍棄了一點數(shù)據(jù)的實時性(非第一次請求,只能獲取上一次最新數(shù)據(jù)),大大提高了前端的加載性能。
適合的場景:
- 數(shù)據(jù)實時性要求不高。
- 服務(wù)端接口耗時長。
使用
import { BtApp } from '@beautywe/core';
import cache from '@beautywe/plugin-cache';
const app = new BtApp();
app.use(cache({
adapters: [{
key: 'name',
data() {
return API.fetch('xxx/name');
}
}]
}));
假設(shè) API.fetch('xxx/name') 是請求服務(wù)器接口,返回數(shù)據(jù):data_from_server
那么:
app.cache.get('name').then((value) => {
// value: 'data_from_server'
});
更多的配置,詳情看:@beautywe/plugin-cache
日志 Logger
由 @beautywe/logger-plugin 提供的一個輕量的日志處理方案,它支持:
- 可控的 log level
- 自定義前綴
- 日志統(tǒng)一處理
使用
import { BtApp } from '@beautywe/core';
import logger from '@beautywe/plugin-logger';
const page = new BtApp();
page.use(logger({
// options
}));
API
page.logger.info('this is info');
page.logger.warn('this is warn');
page.logger.error('this is error');
page.logger.debug('this is debug');
// 輸出
// [info] this is info
// [warn] this is warn
// [error] this is error
// [debug] this is debug
Level control
可通過配置來控制哪些 level 該打?。?/p>
page.use(logger({
level: 'warn',
}));
那么 warn 以上的 log (info, debug)就不會被打印,這種滿足于開發(fā)和生成環(huán)境對 log 的不同需求。
level 等級如下:
Logger.LEVEL = {
error: 1,
warn: 2,
info: 3,
debug: 4,
};
更多的配置,詳情看: @beautywe/plugin-logger
BeautyWe Framework
@beautywe/core 和 @beautywe/plugin-... 給小程序提供了:
- 開放原生,支持插件化 —— by core
- 各種插件 —— by plugins
但是,還有很多的開發(fā)中實際還會遇到的痛點,是上面兩個解決不到的。
如項目的組織、規(guī)范、工程化、配置、多環(huán)境等等
這些就是,「BeautyWe Framework」要解決的范疇。
它作為一套開箱即用的項目框架,提供了這些功能:
- 集成 BeautyWe Core
- NPM 支持
- 全局窗口
- 全局 Page,Component
- 全局配置文件
- 多環(huán)境開發(fā)
- Example Pages
- 正常項目需要的標(biāo)配:ES2015+,sass,uglify,watch 等
- 以及我們認(rèn)為良好的項目規(guī)范(eslint,commit log,目錄結(jié)構(gòu)等)
也是由于篇幅原因,挑幾個有趣的來講講,更多的可以看看官方文檔:BeautyWe
快速創(chuàng)建
首先安裝 @beautywe/cli
$ npm i @beautywe/cli -g
創(chuàng)建應(yīng)用
$ beautywe new app
> appName: my-app
> version: 0.0.1
> appid: 123456
> 這樣可以么:
> {
> "appName": "my-app",
> "version": "0.0.1",
> "appid": "123456"
> }
回答幾個問題之后,項目就生成了:
my-app ├── gulpfile.js ├── package.json └── src ├── app.js ├── app.json ├── app.scss ├── assets ├── components ├── config ├── examples ├── libs ├── npm ├── pages └── project.config.json
創(chuàng)建頁面、組件、插件
頁面
- 主包頁面:
beautywe new page <path|name> - 分包頁面:
beautywe new page --subpkg <subPackageName> <path|name>
組件
beautywe new component <name>
插件
beautywe new plugin <name>
自定義模板
在 ./.templates 目錄中,存放著快速創(chuàng)建命令的創(chuàng)建模板:
$ tree .templates .templates ├── component │ ├── index.js │ ├── index.json │ ├── index.scss │ └── index.wxml ├── page │ ├── index.js │ ├── index.json │ ├── index.scss │ └── index.wxml └── plugin └── index.js
可以修改里面的模板,來滿足項目級別的自定義模板創(chuàng)建。
全局窗口
我們都知道微信小程序是「單窗口」的交互平臺,一個頁面對應(yīng)一個窗口。
而在業(yè)務(wù)開發(fā)中,往往會有諸如這種述求:
- 自定義的 toast 樣式
- 頁面底部 copyright
- 全局的 loading 樣式
- 全局的懸浮控件
......
稍微不優(yōu)雅的實現(xiàn)可以是分別做成獨立的組件,然后每一個頁面都引入進來。
這種做法,我們會有很多的重復(fù)代碼,并且每次新建頁面,都要引入一遍,后期維護也會很繁瑣。
而「全局窗口」的概念是:希望所有頁面之上有一塊地方,全局性的邏輯和交互,可以往里面擱。
global-view 組件
這是一個自定義組件,源碼在 /src/components/global-view
每個頁面的 wxml 只需要在頂層包一層:
<global-view id="global-view"> ... </global-view>
需要全局實現(xiàn)的交互、樣式、組件,只需要維護這個組件就足夠了。
全局配置文件
在 src/config/ 目錄中,可以存放各種全局的配置文件,并且支持以 Node.js 的方式運行。(得益于 Node.js Power 特性)。
如 src/config/logger.js:
const env = process.env.RUN_ENV || 'dev';
const logger = Object.assign({
prefix: 'BeautyWe',
level: 'debug',
}, {
// 開發(fā)環(huán)境的配置
dev: {
level: 'debug',
},
// 測試環(huán)境的配置
test: {
level: 'info',
},
// 線上環(huán)境的配置
prod: {
level: 'warn',
},
}[env] || {});
module.exports.logger = logger;
然后我們可以這樣讀取到 config 內(nèi)容:
import { logger } from '/config/index';
// logger.level 會根據(jù)環(huán)境不同而不同。
Beautywe Framework 默認(rèn)會把 config 集成到 getApp() 的示例中:
getApp().config;
多環(huán)境開發(fā)
BeautyWe Framework 支持多環(huán)境開發(fā),其中預(yù)設(shè)了三套策略:
- dev
- test
- prod
我們可以通過命令來運行這三個構(gòu)建策略:
beautywe run dev beautywe run test beautywe run prod
三套環(huán)境的差異
Beautywe Framework 源碼默認(rèn)在兩方面使用了多環(huán)境:
- 構(gòu)建任務(wù)(
gulpfile.js/env/...) - 全局配置(
src/config/...)
構(gòu)建任務(wù)的差異
| 構(gòu)建任務(wù) | 說明 | dev | test | prod |
|---|---|---|---|---|
| clean | 清除dist文件 | √ | √ | √ |
| copy | 復(fù)制資源文件 | √ | √ | √ |
| scripts | 編譯JS文件 | √ | √ | √ |
| sass | 編譯scss文件 | √ | √ | √ |
| npm | 編譯npm文件 | √ | √ | √ |
| nodejs-power | 編譯Node.js文件 | √ | √ | √ |
| watch | 監(jiān)聽文件修改 | √ | ||
| scripts-min | 壓縮JS文件 | √ | ||
| sass-min | 壓縮scss文件 | √ | ||
| npm-min | 壓縮npm文件 | √ | ||
| image-min | 壓縮圖片文件 | √ | ||
| clean-example | 清除示例頁面 | √ |
Node.js Power
Beautywe Framework 的代碼有兩種運行環(huán)境:
- Node.js 運行環(huán)境,如構(gòu)建任務(wù)等。
- 微信小程序運行環(huán)境,如打包到
dist文件夾的代碼。
運行過程
Node.js Power 本質(zhì)是一種靜態(tài)編譯的實現(xiàn)。
把某個文件在 Node.js 環(huán)境運行的結(jié)果,輸出到微信小程序運行環(huán)境中,以此來滿足特定的需求。
Node.js Power 會把項目中 src 目錄下類似 xxx.nodepower.js 命名的文件,以 Node.js 來運行,
然后把運行的結(jié)果,以「字面量對象」的形式寫到 dist 目錄下對應(yīng)的同名文件 xxx.nodepower.js 文件去。
以 src/config/index.nodepower.js 為例:
const fs = require('fs');
const path = require('path');
const files = fs.readdirSync(path.join(__dirname));
const result = {};
files
.filter(name => name !== 'index.js')
.forEach((name) => {
Object.assign(result, require(path.join(__dirname, `./${name}`)));
});
module.exports = result;
該文件,經(jīng)過 Node.js Power 構(gòu)建之后:
dist/config/index.nodepower.js:
module.exports = {
"appInfo": {
"version": "0.0.1",
"env": "test",
"appid": "wx85fc0d03fb0b224d",
"name": "beautywe-framework-test-app"
},
"logger": {
"prefix": "BeautyWe",
"level": "info"
}
};
這就滿足了,隨意往 src/config/ 目錄中擴展配置文件,都能被自動打包。
Node.js Power 已經(jīng)被集成到多環(huán)境開發(fā)的 dev, test, prod 中去。
當(dāng)然,你可以手動運行這個構(gòu)建任務(wù):
$ gulp nodejs-power
NPM
BeautyWe Framework 實現(xiàn)支持 npm 的原理很簡單,總結(jié)一句話:
使用 webpack 打包
src/npm/index.js,以 commonjs 格式輸出到dist/npm/index.js

這樣做的好處:
- 實現(xiàn)簡單。
- 讓 npm 包能集中管理,每次引入依賴,都好好的想一下,避免泛濫(尤其在多人開發(fā)中)。
- 使用 ll dist/npm/index.js 命令能快速看到項目中的 npm 包使占了多少容量。
新增 npm 依賴
在 src/npm/index.js 文件中,進行 export:
export { default as beautywe } from '@beautywe/core';
然后在其他文件 import:
import { beautywe } from './npm/index';
更多
總的來說,BeautyWe 是一套微信小程序的開發(fā)范式。
core 和 plugins 擴展原生,提供復(fù)雜邏輯的封裝和插拔式使用。
而 framework 則負(fù)責(zé)提供一整套針對于微信小程序的企業(yè)級項目解決方案,開箱即用。
其中還有更多的內(nèi)容,歡迎瀏覽官網(wǎng):beautywejs.com
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于JavaScript實現(xiàn)動畫時動畫抖動的原因與解決方法
最近在使用JS動畫做一些練習(xí)的時候我發(fā)現(xiàn)在動畫執(zhí)行時間內(nèi)快速移開鼠標(biāo)時會出現(xiàn)動畫因鼠標(biāo)移動過快從而導(dǎo)致代碼沖突讓畫面抖動的bug,這篇文章主要給大家介紹了關(guān)于JavaScript實現(xiàn)動畫時動畫抖動的原因與解決方法,需要的朋友可以參考下2022-06-06
layui動態(tài)渲染生成左側(cè)3級菜單的方法(根據(jù)后臺返回數(shù)據(jù))
今天小編就為大家分享一篇layui動態(tài)渲染生成左側(cè)3級菜單的方法(根據(jù)后臺返回數(shù)據(jù)),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
javascript:void(0)是什么意思及href=#與href=javascriptvoid(0)的區(qū)別
Javascript中void是一個操作符,該操作符指定要計算一個表達式但是不返回值,本文給大家介紹javascript:void(0)是什么意思及href=#與href=javascriptvoid(0)的區(qū)別,需要的朋友參考下2015-11-11
微信小程序云開發(fā)實現(xiàn)云數(shù)據(jù)庫讀寫權(quán)限
這篇文章主要為大家詳細(xì)介紹了微信小程序云開發(fā)實現(xiàn)云數(shù)據(jù)庫讀寫權(quán)限,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
JavaScript中創(chuàng)建原子的方法總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于JavaScript中創(chuàng)建原子的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08

