Skip to content

單執行緒與雙執行緒 ​

怎麼選 ​

單執行緒(預設)雙執行緒(runtime: 'dual')
程式在哪裡執行全部在 WebView畫面在 WebView;業務邏輯在容器的獨立 JS 引擎
適合大部分小程序資料處理較重(大量格式化、排序、計算),或希望畫面層保持很薄
重運算會不會卡畫面會(WebView 主執行緒被佔住)不會(邏輯層在另一個執行緒)
最低容器版本1.0.01.1.0(建議 1.2.0,見下方)

不確定就先用單執行緒;之後改成雙執行緒時,SDK 的 auth、ui、system、http、storage、share、telemetry 介面完全相同,搬過去即可。

雙執行緒的架構 ​

┌──────────── WebView(畫面層)────────────┐      ┌──── 容器 JS 引擎(邏輯層)────┐
│ sdk.view.connect(render)  ◀── 資料 ──────┼──────┼── setData({ ... })           │
│ sdk.view.emit('refresh')  ─── 事件 ──────┼──────┼─▶ handlers.refresh()         │
│ sdk.ready() / sdk.theme / sdk.navigate   │      │ sdk.auth / sdk.http / ...     │
└──────────────────────────────────────────┘      └───────────────────────────────┘
  • 邏輯層用 definePage 宣告初始資料、生命週期與事件處理,用 setData 更新資料。
  • 畫面層用 sdk.view.connect 拿到資料(先一份完整快照,之後每次更新都給合併後的完整資料),用 sdk.view.emit 把點擊等事件送給邏輯層。
  • iOS 用系統的 JavaScriptCore,Android 用 QuickJS。

設定 ​

ts
// miniapp.config.ts
export default defineConfig({
  // ...
  runtime: 'dual',
  logic: 'src/logic.ts',
  minContainer: '1.2.0',
});

minContainer 最低 1.1.0;建議 1.2.0:1.2.0 起快照與更新帶有序號,畫面層不會漏掉和快照同時送達的更新。

邏輯層 ​

ts
// src/logic.ts
import { definePage, getData, setData, sdk } from '@im/miniapp-sdk/logic';

interface ViewData {
  status: 'loading' | 'ready' | 'error';
  count: number;
  [key: string]: unknown;
}

definePage<ViewData>({
  data: { status: 'loading', count: 0 },
  async onLoad(launch) {
    // launch 與畫面層的 getLaunch() 欄位相同(appId、locale、path…)
    const res = await sdk.http.fetch('https://api.example.com/count');
    setData<ViewData>({ status: 'ready', count: res.json<{ count: number }>().count });
  },
  onShow() {
    // 從背景回到前景、或關閉後再次打開(保活接回)時呼叫
  },
  onHide() {
    // 退到背景、或關閉小程序(進入保活)時呼叫:要存的資料在這裡存
  },
  handlers: {
    increment() {
      setData<ViewData>({ count: getData<ViewData>().count + 1 });
    },
  },
});
  • definePage 整個 bundle 只能呼叫一次。
  • setData(diff) 是淺層合併:只覆寫你給的頂層 key,其他 key 保留。同一個 microtask 內的多次呼叫會合併成一次送出。
  • 值必須能 JSON 序列化(不能放函式、Date、Map、循環參照)。
  • onLoad、onShow、onHide、handler 裡拋出的錯誤或被 reject 的 Promise 會上報給容器,不會讓邏輯層停止。
  • 畫面層送來不存在的事件名稱也會上報錯誤。

邏輯層可以用什麼 ​

可以不行
setTimeout、clearTimeout、setInterval、clearIntervalwindow、document 等 DOM
console.log/info/warn/error/debug、queueMicrotask、Promisefetch、XMLHttpRequest(網路一律用 sdk.http.fetch)
JSON、Math、Date、RegExp 等語言內建Intl(Android 的 QuickJS 沒有;iOS 雖然有,也不要用,兩邊行為要一致)
@im/miniapp-sdk/logic 的 sdk.auth/ui/system/http/storage/share/telemetrysdk.ready、sdk.theme、sdk.navigate、sdk.view(只有畫面層有)
  • 語法目標是 ES2020,CLI 會轉譯。
  • 邏輯層不讀專案的 vite.config.ts 與 .env:resolve.alias(例如 @/)、Vite plugins、import.meta.env.VITE_* 都不能用,請用相對路徑 import。
  • 邏輯層會打包成單一檔案(動態 import() 也會併進同一個檔案),不能 import CSS 或圖片。體積上限 2 MB。
  • 計時器的延遲和瀏覽器相同,以 32 位元整數換算:NaN、Infinity、負數視為 0;setInterval 最小 4 ms。
  • 數字、日期要自己格式化(例如千分位用正規表示式),不要依賴 toLocaleString。

無窮迴圈無法中斷

邏輯層卡在無窮迴圈(或無限的 Promise 鏈)時,容器無法中斷它,只能等使用者關閉小程序後丟棄整個邏輯層。迴圈要有明確的結束條件,大量資料要分批處理。

畫面層 ​

vue
<script setup lang="ts">
import { sdk } from '@im/miniapp-sdk';
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';

interface ViewData {
  status: 'loading' | 'ready' | 'error';
  count: number;
  [key: string]: unknown;
}

const data = ref<ViewData>({ status: 'loading', count: 0 });
let disconnect: (() => void) | undefined;
let readySent = false;

onMounted(() => {
  disconnect = sdk.view.connect<ViewData>(
    (next) => {
      data.value = next;
      // 邏輯層還沒啟動完成時,第一次可能是空物件:用明確的狀態判斷。
      if (!readySent && (next.status === 'ready' || next.status === 'error')) {
        readySent = true;
        void nextTick(() => sdk.ready().catch(() => {}));
      }
    },
    () => {
      // 取快照失敗(例如不在容器內)
      data.value = { ...data.value, status: 'error' };
    },
  );
});

onBeforeUnmount(() => disconnect?.());

const increment = () => void sdk.view.emit('increment').catch(() => {});
</script>
  • connect 的回呼每次都拿到新物件(合併後的完整資料),可以直接整個替換。
  • 邏輯層還沒啟動完成時,第一次回呼可能是空物件 {},初始資料隨後送達。
  • 同一個頁面可以有多個元件各自 connect,每個都會拿到完整、正確的資料。
  • 邏輯層還在啟動時送出的 emit 會先排隊,啟動完成後依序送達,不會遺失。

資料更新的保證 ​

  • 每次更新送達容器,資料的序號(rev)就加一(短時間內的多次 setData 會先合併成一次)。快照包含序號不大於它的所有更新。
  • SDK 在快照回來前先暫存收到的更新,快照到了只套用比它新的;之後也會丟掉已包含在快照裡的更新。所以不論更新和快照誰先到,畫面層看到的資料都一致。
  • 容器約每一幀把累積的更新送一次。更新只送有變的頂層 key:大型清單不要每次都整份重送,能分開的狀態放在不同的 key。

完整範例見工具裡的 examples/points(會員點數小程序)。