Frontend Development 18 min read

Building a Multi‑Platform Mini Program with Taro, Taro UI, TypeScript, and MobX

This article details the development of a multi‑platform mini‑program using the Taro framework, covering project background, architecture, technology selection, TypeScript practices, MobX state management, API service encapsulation, image scaling, and canvas‑based poster sharing, with code examples and implementation insights.

JD Retail Technology
JD Retail Technology
JD Retail Technology
Building a Multi‑Platform Mini Program with Taro, Taro UI, TypeScript, and MobX

The project, called "丰客多", is an enterprise membership mall built by JD's business unit, aiming to achieve a C‑to‑B viral growth model through WeChat mini‑programs.

Technology selection : After comparing multi‑terminal frameworks, the team chose the self‑developed Taro framework because it supports React, Vue, and other stacks while keeping maintenance costs low.

Project architecture includes Taro integration with native mini‑programs, TypeScript usage, MobX for state management, API Service/HttpClient encapsulation, image proportional scaling, and poster sharing via Canvas.

Plugin abstraction for multi‑environment login :

abstract class Plugin {
abstract getToken(): void; /** 获取token信息 */
abstract outLogin(): void; /** 退出登录 */
abstract openLogin(): void; /** 打开登录页 */
}
class WeChatPlugin extends Plugin {
getToken(): void { /* ... */ }
outLogin(): void { /* ... */ }
openLogin(): void { /* ... */ }
}
class JDPlugin extends Plugin {
getToken(): void { /* ... */ }
outLogin(): void { /* ... */ }
openLogin(): void { /* ... */ }
}
class H5Plugin extends Plugin {
getToken(): void { /* ... */ }
outLogin(): void { /* ... */ }
openLogin(): void { /* ... */ }
}
export class pluginHelper {
private plugin: Plugin;
constructor() {
switch (process.env.TARO_ENV) {
case 'weapp': this.plugin = new WeChatPlugin(); break;
case 'jd': this.plugin = new JDPlugin(); break;
case 'h5': this.plugin = new H5Plugin(); break;
}
}
get plugin(): Plugin { return this.plugin; }
}
export default pluginHelper;

TypeScript state management – replacing interface‑based state with a class to reduce boilerplate:

interface ITsExampleState {
name: string;
name2: string;
name3: string;
name4: string;
}
export default class TsExample extends Component
{
state: Readonly
= { name: "", name2: "", name3: "", name4: "" };
// ...
}
class ITsExampleState {
name: string = "";
name2: string = "";
name3: string = "";
name4: string = "";
}
export default class TsExample extends Component
{
state: Readonly
= new ITsExampleState();
// ...
}

HttpClient encapsulation for unified request handling and error messaging:

import Taro, { request } from "@tarojs/taro";
const baseUrl = "https://xxxxx";
export class HttpClient {
private checkStatus(response) {
if (response && (response.statusCode === 200 || response.statusCode === 304 || response.statusCode === 400)) {
// process success
} else {
Taro.showToast({ title: '系统有点忙,耐心等会呗', icon: 'none', duration: 2000 });
return null;
}
}
public post(url: string, params: any = {}) { return this.request('post', url, params); }
public get(url: string, params: any = {}) { return this.request('get', url, params); }
// async request implementation ...
}

Image proportional scaling utility used in the home page waterfall layout:

export default class Utils {
static imageScale = (e) => {
const originalWidth = e.detail.width;
const originalHeight = e.detail.height;
const originalScale = originalHeight / originalWidth;
const { windowWidth, windowHeight } = Taro.getSystemInfoSync();
const windowScale = windowHeight / windowWidth;
if (originalScale < windowScale) {
return { imageWidth: windowWidth, imageHeight: (windowWidth * originalHeight) / originalWidth };
} else {
return { imageHeight: windowHeight, imageWidth: (windowHeight * originalWidth) / originalHeight };
}
}
}

Canvas‑based poster sharing – creating an off‑screen canvas, drawing background, QR code, avatar, and text, then saving the image to the photo album:

// Canvas element
// CanvasUtil for text wrapping, rounded rect, and circular image
export class CanvasUtil {
static breakLinesForCanvas(context, text, width, font) { /* ... */ }
static circleImg(ctx, img, x, y, r) { ctx.save(); ctx.beginPath(); ctx.arc(x+r, y+r, r, 0, Math.PI*2); ctx.clip(); ctx.drawImage(img, x, y, r*2, r*2); ctx.restore(); }
static drawRoundedRect(ctx, x, y, w, h, r, fill) { /* ... */ }
}
// Poster generation logic
let context = Taro.createCanvasContext('shareCanvas');
context.drawImage(inviteImageUrl, 0, 0, canvasWidth, canvasHeight);
CanvasUtil.drawRoundedRect(context, qrBgX, qrBgY, qrBgWidth, qrBgHeight, 5, true);
context.drawImage(this.downloadQRcode, qrBgX+2, qrBgY+2, qrBgWidth-4, qrBgHeight-4);
CanvasUtil.circleImg(context, avatarTempPath, wxAvatarX, wxAvatarY, wxAvatarWidth/2);
context.setTextAlign('center');
context.fillText('扫一扫', centerPx, qrBgY + qrBgHeight + 20);
context.fillText('立即注册丰客多', centerPx, qrBgY + qrBgHeight + 34);
context.draw();
Taro.canvasToTempFilePath({ canvasId: 'shareCanvas', fileType: 'jpg', success(res) { Taro.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success() { Taro.showToast({ title: '保存成功', icon: 'success' }); } }); } });

In summary, the author migrated from native WeChat mini‑program development to a Taro‑based stack, leveraging its multi‑platform capabilities, TypeScript, MobX, and a set of utility classes to streamline development, reduce boilerplate, and enable features such as image scaling and canvas poster sharing.

frontendTypeScriptMiniProgramcanvasAPIMobXTaro
JD Retail Technology
Written by

JD Retail Technology

Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.