跪拜 Guibai
← All articles
Frontend · Uni-app

uni-router v1.10 Ships a Global Event Bus That Gives Every Navigation Method a Bidirectional Channel

By PedroQue99 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Any uni-app project that uses replace or relaunch for login flows, stack replacement, or app resets now gets the same inter-page communication that was previously exclusive to push. The sticky cache removes a class of flaky, timing-dependent bugs that are hard to reproduce and harder to fix without a framework-level solution.

Summary

The native uni-app EventChannel only works with navigateTo, leaving replace and relaunch navigations without a way to pass data between pages. uni-router v1.10 introduces a built-in communication manager that wraps uni.$emit/$on, assigning a unique navigationId to every navigation so push, replace, and relaunch all return a usable eventChannel. The same channel is exposed to RouterLink’s navigated event, unifying the API across every navigation method.

A sticky event cache inside UniEventChannel solves the long-standing race condition where an emit fires before the target page’s setup registers its listener. Emitted data is always cached, and any late-arriving on or once registration receives it asynchronously via a microtask. The cache persists until the channel is destroyed, so even a once listener registered later gets the last payload.

The target page retrieves its channel through a new usePageChannel() composable that reads a __nav_id from the URL query, making the channel survive H5 refreshes. When no navId is present—such as a direct browser visit—the API returns a noopChannel so callers never need null checks. Channel cleanup is automatic in onUnmounted, preventing memory leaks from abandoned listeners.

Takeaways
Enabling useUniEventChannel: true in createRouter makes push, replace, and relaunch all return a NavigationResult with an eventChannel property.
UniEventChannel is built on uni.$emit/$on and isolates channels by a unique navigationId, preventing cross-navigation event leakage.
Sticky caching stores the last emit payload; any on or once listener registered later receives it via a Promise microtask, eliminating race conditions.
usePageChannel() replaces getOpenerEventChannel() and reads __nav_id from the URL query, so the channel survives an H5 page refresh.
When no __nav_id is present, usePageChannel() returns a noopChannel whose methods are safe no-ops, removing the need for null checks.
RouterLink’s @navigated event now fires for all navigation types and passes the eventChannel, unifying the template API.
Channel cleanup runs automatically in onUnmounted, destroying listeners and caches to prevent memory leaks.
The release is backward compatible; without useUniEventChannel, behavior matches v1.9.0 exactly.
Conclusions

Sticky caching that never deletes until channel destruction is a deliberate trade-off: it guarantees delivery to late-arriving once listeners but means the cache holds a reference to the last payload for the channel’s entire lifetime.

Passing __nav_id through the URL query mirrors the existing __params_key pattern, which is a pragmatic choice for H5 survival but exposes an internal routing detail in the address bar.

The first-wins strategy in the channel registry prevents a second navigation to the same navId from overwriting an existing channel, which protects against accidental double-registration but could mask bugs where duplicate navigations occur.

Concepts & terms
Sticky event cache
A cache that retains the last emitted payload for a given event name. When a listener registers after the emit, the cache replays the payload asynchronously via a microtask, preventing data loss from timing race conditions.
noopChannel
A sentinel object implementing the EventChannel interface where every method is a no-op that returns itself. It is returned by usePageChannel() when no navigation channel exists, so calling code never needs to check for null or undefined.
navigationId
A unique identifier generated per navigation (format nav-<timestamp>-<seq>) that prefixes event names on the global bus, isolating events between different navigations so they do not interfere with each other.
From the discussion

A preference surfaces for Flutter's promise-based navigation pattern, where a push returns a result directly, over the event-bus workaround uni-router provides. The limitation is acknowledged as a platform constraint — uni-app's own API simply doesn't offer that capability.

Flutter's await navigator.push() pattern, which returns a result directly, is seen as a cleaner alternative to the event-bus approach.
The uni-app framework's native API lacks support for promise-returning navigation, making the event-bus workaround a necessary adaptation rather than a design choice.
Featured comments
似水流年146

More inclined toward the Flutter approach, const result = await navigator.push()

似水流年146

result can then receive the returned parameters

PedroQue99  → 似水流年146

There's no way around it, the uni-app official docs don't support that kind of API [facepalm]

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗