When doing a variety of businesses, we inevitably need to carry out burying points in the business, which usually include but not limited to exposure, click, stay time, leave the page and other scenes. In the small program, because of its different architecture from the browser, it makes it more difficult to listen to the page. Normally we would intercept proxies for the native life cycle of small programs by rewriting the Page method, but this has changed with Taro.

The status quo

In Taro, we no longer see explicit Page calls. Even after Taro is packaged, there is no Page in the code. Instead, the native Component of the widget (as you can see by looking at the packaged content) is used. Therefore, in order to achieve automatic embedding of wechat applet in Taro, we need to change a strategy: rewrite Component.

Basic rewrite

In wechat applets, the exposed Component and Page can be overwritten and assigned directly:

const _originalComponent = Component;

const wrappedComponent = function (options) {
	...do something before real Component
    return _originalComponent(options);
}
Copy the code

This can solve the problem quickly, but when we do this in another small program, we need to do this again manually, which is a bit of trouble, why not find a more general solution, we only need to focus on the business (buried).

The solution

The most important thing is to think from scratch, to grasp the real problem and get close to the essence of the problem

The root problem

Let’s look at the nature of the problem before we solve it. In order to automatically bury a small program, what we need to do is to do some fixed processing in the life cycle specified by the small program, so the problem of automatic burying is actually how to hijack the life cycle of the small program, and to hijack the life cycle of the small program, we need to do is to rewrite options.

How to solve

Before we can solve this problem, we need to break down the problems we need to solve:

  • How do I rewrite itoptions
  • Which ones to rewriteoptions
  • How to inject your business into the listening lifecycle.

In order to ensure that our solution can be applied to native applet and multipurpose applet solutions such as Taro, we just need to cover the original applet method with another layer. We should support rewriting both Component and Page. For the last question, we can think of event systems in JS. Similarly, we can implement a publish-subscribe logic that can be customized to trigger events and listeners, and then wrapped around the lifecycle logic.

step 1

First we should save the original methods before overwriting Component and Page so that they don’t get contaminated and we can’t go back. After that we can enumerate all the life cycles in the applet into a default event object. Ensure that we can address and overwrite the original lifecycle method after registering the corresponding lifecycle process.

export const ProxyLifecycle = {
  ON_READY: 'onReady'.ON_SHOW: 'onShow'.ON_HIDE: 'onHide'.ON_LOAD: 'onLoad'.ON_UNLOAD: 'onUnload'.CREATED: 'created'.ATTACHED: 'attached'.READY: 'ready'.MOVED: 'moved'.DETACHED: 'detached'.SHOW: 'show'.HIDE: 'hide'.RESIZE: 'resize'};public constructor() {
  this.initLifecycleHooks();
  this.wechatOriginalPage = getWxPage();
  this.wechatOriginalComponent = getWxComponent();
}

// Initialize all lifecycle hook functions
private initLifecycleHooks(): void {
  this.lifecycleHooks = Object.keys(ProxyLifecycle).reduce((res, cur: keyof typeof ProxyLifecycle) = > {
    res[ProxyLifecycle[cur]] = [] as WeappLifecycleHook[];
    return res;
  }, {} as Record<string, WeappLifecycleHook[]>);
}
Copy the code

step 2

In this step we just need to put the listener into the event object we declared in the first step and perform the rewrite process:

public addLifecycleListener(lifeTimeOrLifecycle: string.listener: WeappLifecycleHook): OverrideWechatPage {
  // Define Hooks for the specified period
  this.lifecycleHooks[lifeTimeOrLifecycle].push(listener);
  const _Page = this.wechatOriginalPage;
  const _Component = this.wechatOriginalComponent;
  const self = this;
  const wrapMode = this.checkMode(lifeTimeOrLifecycle);
  const componentNeedWrap = ['component'.'pageLifetimes'].includes(wrapMode);

  const wrapper = function wrapFunc(options: IOverrideWechatPageInitOptions) :string | void {
    const optionsKey = wrapMode === 'pageLifetimes' ? 'pageLifetimes' : ' ';
    options = self.findHooksAndWrap(lifeTimeOrLifecycle, optionsKey, options);

    const res = componentNeedWrap ? _Component(options) : _Page(options);

    options.__router__ = (wrapper as any).__route__ = res;

    return res;
  };

  (wrapper as any).__route__ = ' ';

  if (componentNeedWrap) {
    overrideWxComponent(wrapper);
  } else {
    overrideWxPage(wrapper);
  }
  return this;
}

/** * Rewrite options * for the corresponding life cycle@param ProxyLifecycleOrTime The lifecycle that needs to be intercepted *@param OptionsKey Specifies the optionsKey to be rewritten. This parameter is used in Lifetime mode *@param Options Options that need to be overridden@returns {IOverrideWechatPageInitOptions} Rewritten options */
private findHooksAndWrap = (
  proxyLifecycleOrTime: string,
  optionsKey = ' '.options: IOverrideWechatPageInitOptions,
): IOverrideWechatPageInitOptions= > {
  letprocessedOptions = { ... options };const hooks = this.lifecycleHooks[proxyLifecycleOrTime];
  processedOptions = OverrideWechatPage.wrapLifecycleOptions(proxyLifecycleOrTime, hooks, optionsKey, options);

  return processedOptions;
};

/** * override options *@param Lifecycle the lifecycle that needs to be rewritten *@param Hooks Added for the life cycle *@param OptionsKey Specifies the optionsKey to be overridden. Only used in Lifetime mode *@param Options Configuration items to be overridden *@returns {IOverrideWechatPageInitOptions} Rewritten options */
private static wrapLifecycleOptions = (
  lifecycle: string.hooks: WeappLifecycleHook[],
  optionsKey = ' '.options: IOverrideWechatPageInitOptions,
): IOverrideWechatPageInitOptions= > {
  letcurrentOptions = { ... options };const originalMethod = optionsKey ? (currentOptions[optionsKey] || {})[lifecycle] : currentOptions[lifecycle];
  const runLifecycleHooks = (): void= > {
    hooks.forEach((hook) = > {
      if(currentOptions.__isPage__) { hook(currentOptions); }}); };constwarpMethod = runFunctionWithAop([runLifecycleHooks], originalMethod); currentOptions = optionsKey ? {... currentOptions, [optionsKey]: { ... options[optionsKey], ... (currentOptions[optionsKey] || {}), [lifecycle]: warpMethod, }, } : { ... currentOptions, [lifecycle]: warpMethod, };return currentOptions;
};
Copy the code

By following these two steps, we can hijack the specified lifecycle and inject our own listeners, which are automatically triggered by overwritten Component or Page.

weapp-lifecycle-hook-plugin

In order to facilitate the direct implementation of this set of general solutions to the wechat small program native environment and multi-end unified solutions such as Taro, I implemented a plug-in to solve this problem (amway)

The installation

NPM install appellate p-life-hook-plugin or YARN add appellate p-life-hook-pluginCopy the code

use

import OverrideWechatPage, { setupLifecycleListeners, ProxyLifecycle } from 'weapp-lifecycle-hook-plugin'; // A hook function for setupLifecycleListeners that takes an argument to the options function simpleReportGoPage(options: any) on the current component/page: void { console.log('goPage', options); } // setupListeners class App extends Component { constructor(props) { super(props); } componentWillMount() { // ... // Manually created instances and setupLifecycleListeners are not the same, Const instance = new OverrideWechatPage(this.config.pages); // OverrideWechatPage(this.config.pages); / / direct call instance addListener method in the global increase monitoring function, can chain calls to the instance. The addLifecycleListener (ProxyLifecycle. SHOW, simpleReportGoPage); SetupLifecycleListeners (ProxyLifecycle.SHOW, [simpleReportGoPage], this.config.pages); / /... } / /... }Copy the code

A simple setup removes a lot of rewriting logic that previously required manual writing, so why not 😝

Extra

Code referred to in this article are excerpts from the above mentioned plug-in part, although not too much, but there is no complete context and implementation, to avoid to read will be obstacles, if you want to know the detailed implementation and implementation of a classmate to my lot and check in detail, if you have something, please don’t hesitate, just watch me a 👍 ~