Page and Component Lifecycle

A lifecycle describes everything that happens to a UI instance between its creation and disposal. Pages, reusable components, and framework-managed views all follow this general pattern: they are created, inserted into a view tree, updated in response to state or data changes, and eventually removed. Each framework exposes these transitions through hooks or effects, giving developers specific points at which to initialize state, interact with the DOM, start background work, or release resources.

The underlying concept is consistent across platforms, although the names and timing differ. Understanding the lifecycle model is essential for writing predictable applications, especially when components perform network requests, subscribe to events, or create timers and animations.
In a WeChat Mini Program, a page has a lifecycle closely tied to navigation and visibility. Its main hooks are:

Hook When it runs Common use
onLoad When the page is first loaded Read route parameters and fetch initial data
onShow Whenever the page becomes visible Refresh status that may have changed
onReady After the first rendering completes Query layout details and interact with the view
onHide When the page is hidden Pause nonessential work
onUnload When the page is removed Cancel requests, stop timers, and release resources

These hooks should not be treated as interchangeable. For example, onLoad is appropriate for a one-time request based on an item ID, while onShow may be useful for refreshing an unread-message count whenever the user returns to the page.

Page({
  onLoad(options) {
    this.fetchProduct(options.id);
  },

  onShow() {
    this.refreshAvailability();
  },

  onHide() {
    this.pausePolling();
  },

  onUnload() {
    this.stopTimers();
    this.unsubscribeFromUpdates();
  }
});

A page is not necessarily destroyed when it is merely hidden. Navigating to another page may trigger onHide, but the original instance can remain in memory and later reappear through onShow. This distinction explains why onLoad does not run again when a user navigates back, while onShow does.

Component Lifecycle: Mounting and Unmounting

Custom components have a more compact lifecycle focused on their position in the node tree. In the WeChat Mini Program, these phases are declared under lifetimes:

  • created runs when the component instance is created. The view tree is not yet available, so setData and node queries cannot be used safely.
  • attached runs after the component has entered the page's node tree. Its data is initialized, making this a suitable place for setup work.
  • ready runs when the component's layout is complete. Nodes can now be measured or inspected.
  • moved runs if the component is relocated within the node tree.
  • detached runs after the component leaves the tree and is treated as the appropriate cleanup stage.
Component({
  lifetimes: {
    attached() {
      this.startClock();
      this.subscribeToStore();
    },

    ready() {
      this.measureHeader();
    },

    detached() {
      this.stopClock();
      this.unsubscribeFromStore();
    }
  }
});

The difference between attached and ready matters. Data initialization can begin in attached, but code that depends on rendered dimensions, positions, or other layout information should wait until ready.

Letting Components Respond to Page Visibility

A reusable component does not normally receive page hooks such as onShow or onHide directly. Instead, a WeChat Mini Program component can use pageLifetimes to observe the lifecycle of the page that contains it:

Component({
  pageLifetimes: {
    show() {
      this.resumeUpdates();
    },

    hide() {
      this.suspendUpdates();
    }
  }
});

This is especially useful for components that own continuous or visually sensitive behavior. A carousel might pause when its page is hidden, a countdown may need to recalculate its remaining time, and a map component may reduce network activity while it is out of view.

However, a component should not duplicate work already performed by its parent page. If the page fetches data on onShow and passes it through props, the component can simply react to those prop changes. Page visibility hooks are most valuable for work that genuinely belongs to the component itself.

Why Ordering Matters

When a page containing a custom component is opened for the first time, the lifecycle generally follows this sequence:

  1. The page's onLoad hook runs.
  2. The component is created and attached.
  3. The page's onShow hook runs.
  4. The component's ready hook runs.
  5. The page's onReady hook runs.

This order provides a practical mental model. A page can prepare the initial route state in onLoad, pass that state to child components, and then let each component complete its own setup. If a child component expects data that the page creates during onShow, there is no guarantee that the value will already be available in the component's attached hook.

Lifecycle order also changes when the user returns to an existing page. In that case, onShow may run again, but onLoad and onReady do not. Code that must run on every visit therefore belongs in onShow, not in a one-time initialization hook.

Cleanup Is Part of the Lifecycle

A common source of bugs is treating destruction as an afterthought. Any resource created during setup should have a clearly defined owner and a corresponding cleanup path.

Typical resources include:

  • Timers and polling tasks
  • WebSocket connections
  • Event listeners
  • Store or message subscriptions
  • Pending network requests
  • Animation frames
  • Observer instances

For example, a subscription created in attached should be removed in detached. A timer started when the component becomes visible should be cleared when the page is hidden or the component is removed. This prevents stale updates, duplicate callbacks, and memory leaks.

Cleanup should be idempotent when possible. A component may be hidden and shown many times, and it may be detached in different application states. Guarding cleanup with clearInterval, removing listeners defensively, or checking whether a subscription still exists can make the implementation more robust.

Component({
  attached() {
    this.handleResize = () => {
      this.updateLayout();
    };

    window.addEventListener('resize', this.handleResize);
  },

  detached() {
    if (this.handleResize) {
      window.removeEventListener('resize', this.handleResize);
      this.handleResize = null;
    }
  }
});

The same rule applies to asynchronous work. When a request finishes after its component has been destroyed, the response should not update a component that no longer exists. An abort controller, request token, or mounted-state flag can prevent such updates.

The Same Model in Vue and React

Although hook names vary, the lifecycle model is recognizable in other mainstream frameworks.

In Vue's Options API, created is used for early initialization, mounted runs after the component is inserted into the DOM, and unmounted provides a cleanup point. Vue's Composition API expresses similar timing through functions such as onMounted, onUpdated, and onUnmounted.

React function components use useEffect to synchronize with external systems. The effect runs after rendering, and its cleanup function runs before the effect is reapplied or the component is unmounted:

useEffect(() => {
  const connection = openConnection();

  return () => {
    connection.close();
  };
}, []);

React effects are not exact equivalents of class-component lifecycle methods, and development tools may intentionally run an effect more than once to expose unsafe setup logic. Even so, the central idea remains the same: setup and cleanup should be paired.

The migration between frameworks therefore becomes a matter of mapping concepts rather than learning an entirely new model:

  • Creation corresponds to instance construction or effect setup.
  • Rendering completion corresponds to ready, mounted, or post-render effects.
  • Visibility changes are often handled by framework-specific visibility APIs.
  • Destruction corresponds to detached, unmounted, or effect cleanup.

Practical Guidelines

A few habits can prevent most lifecycle-related problems:

  • Perform one-time initialization in the correct hook. Route parsing and initial data loading usually belong in onLoad or attached.
  • Reserve onShow for work that must happen on every visit. Avoid placing expensive startup logic there unless it truly needs to repeat.
  • Wait for rendering when layout matters. Node measurements and visual calculations should run in ready or an equivalent post-render hook.
  • Pair every resource with cleanup. Timers, listeners, subscriptions, and connections should all be stopped when their owner is hidden or destroyed.
  • Keep page and component responsibilities separate. Let the page coordinate navigation and shared data; let components manage their internal behavior.
  • Test repeated navigation. Returning to a page, backgrounding the application, or rapidly switching views often reveals lifecycle bugs that a single load cannot expose.

A well-designed lifecycle does more than expose framework callbacks. It creates clear boundaries for ownership: work begins only when the required environment exists, continues only while the instance is relevant, and ends predictably. Once those boundaries are understood, lifecycle hooks become a reliable tool for building components that are easier to reason about, reuse, and maintain.