Teach you to build a frameless buried point system

Teach you to build a frameless buried point system

Author: Lilly Jiang, Aiqing Dong

background

Buried point system composition

Generally speaking, a complete buried point system consists of the following three parts:

  • application
  • Data analysis platform
  • Data Platform SDK

Buried point reporting is the process of uploading application layer events to the upper platform. For example, on a shopping website, the user clicks the "favorite" button. At this time, a click event is generated, and this event will be reported to a data analysis platform. In this way, relevant data analysts, product managers, operations and other students can use the reported event data analysis on the data analysis platform to obtain all aspects that can be optimized in the application. It can be seen that the reporting of buried points is an important part of the excellence of each product.

Through the above description, we have come to know the two main protagonists of the reporting process: application and data analysis platform. From the perspective of front-end technology, we usually need a third role as an assist, which is the data platform SDK. This SDK encapsulates various interfaces of the data analysis platform, exposing simple methods for us to call and implement simple Buried point upload.

Two burying events

We can divide application layer events into two categories:

  • "Page event": One is a general "page event", such as the user's stay and active time on a certain page of the application. We hope that this kind of global burying point can only be injected once when the project is initialized, and there is no need to in the code For maintenance.
  • "Trigger event": The other is a custom "trigger event", such as clicking a specific button to open a specific process. This type of event requires front-end students to manually inject embedded points into the code.

We have developed a set of upload SDK for these two events. Below, we will explain in detail the technical knowledge of these two SDKs.

SDK for handling "page events"-
monitor-tracer

monitor-tracer
It is a front-end SDK used to monitor the visible and active duration of pages and components, and it is also a core component of the Monitor front-end embedded point system.

background

In order to better understand the user's use of each business function, so as to optimize and adjust the corresponding product:

  • For general web applications, we need to make corresponding statistics on the user's stay and active time on a certain page of the application;

  • For large-market/kanban/dashboard-type pages (as shown in the figure below), we hope to further count the visible duration of each component to users based on the page dimensions, so as to optimize their order and content.

Based on the above requirements, we have developed

monitor-tracer
The SDK aims to realize the statistics of "page visible, active time" and "component visible time" .

Glossary

  • Page -the webpage opened in the browser, different pages are based on the path
    location.pathname
    To make a distinction
  • Page Visible Time -the cumulative time a page is visible to the user;
  • Page active duration -the cumulative duration of effective mouse, keyboard and touch activities performed by the user on the page;
  • Component -A collection of DOM elements that are part of the page. A page can contain multiple components;
  • Component visible time -the cumulative time a component is visible to the user.

The relationship is:

  • The active time of the page the visible time of the page;
  • The visible duration of the component the visible duration of the page;
  • When a page is invisible, it must be inactive, and all of its components must also be invisible.

Statistics of page visibility and active time

In our design, two important indicators are needed to measure the stay and active time of a page:

  • Visibility

    • visible
      -The page is in the viewport of the current browser, and the browser window is not minimized;
    • invisible
      -The page is not in the viewport of the current browser, or it cannot be seen due to the minimized browser.
  • Activity

    • active
      -The user has activities in the web page (such as mouse, keyboard activities and page scrolling, etc.);
    • inactive
      -The user has no activity on the page.

As long as the timestamps of the occurrence of these four states can be obtained, the method shown in the following figure can be used to cumulatively calculate the visible and active duration of the page from loading to exiting:

Get page visibility data

Web Lifecycle

In July 2018, Google proposed a set of Page Lifecycle API specifications to describe the life cycle of web pages . This SDK is based on this set of specifications to monitor changes in page visibility. The specification points out that during the process from loading to destruction of a webpage, the following six lifecycle states will be transformed into each other through various events of the browser.

Life cycle statedescription
active
The page is visible and has focus
passive
The webpage is visible, but out of focus
hidden
The webpage is not visible, but it is not frozen by the browser, usually triggered by the user switching to another tab or minimizing the browser
frozen
The web page is frozen by the browser (some background tasks such as timers, fetch, etc. are suspended to save CPU resources)
terminated
The web page is unloaded by the browser and cleared from memory. This state is triggered when the general user actively closes the webpage
discarded
The webpage was forced to clean up by the browser. Generally caused by a serious shortage of system resources

The transformation relationship between life cycle states is shown in the following figure:

From the above information, we can get the mapping relationship between the page life cycle state and the page visible state:

Life cycle stateVisible state
active
passive
visible
hidden
terminated
frozen
discarded
invisible

Therefore, we only need to monitor the changes in the life cycle of the page and record its time, and then we can obtain statistics on the visibility of the page accordingly.

Monitor page life cycle changes

While developing the Page Lifecycle specification, the Google Chrome team also developed

The SDK implements the monitoring of the life cycle status described in this set of specifications, and is compatible with all browsers above IE 9. For ease of use and stability considerations, we decided to use this SDK to monitor the life cycle. due to
PageLifecycle.js
It is written in JavaScript, and we have added type definitions and encapsulated it to be compatible with TypeScript
@byted-cg/page-lifecycle-typed
SDK.
PageLifecycle.js
The method of use is as follows:

import lifecycleInstance, { StateChangeEvent, } from "@byted-cg/page-lifecycle-typed" ; lifecycleInstance.addEventListener( "statechange" , ( event: StateChangeEvent ) => { switch (event.newState) { case "active" : case "passive" : //page visible, do something break ; case "hidden" : case "terminated " : case "frozen" : //page invisible, do something else break ; } }); Copy code

by

PageLifecycle.js
, We can monitor
statechange
Event to be notified when the page life cycle changes, and the life cycle status is
active
with
passive
Mark the page as
visible
Status, when the life cycle status is other several, mark the page as
invisible
Status, update the last visible timestamp, and accumulate the visible time of the page.

PageLifecycle.js
Defects

For our needs,

PageLifecycle.js
There are two disadvantages as follows. We have also made some improvements for these two defects.

  • Unable to monitor page changes in a single page application (SPA)

    In a single-page application, the page usually depends on changes in history or hash routing to switch, and the page itself does not reload, so

    PageLifecycle.js
    Unable to perceive page switching. In order to cope with this situation, we manually added events for routing changes in monitor-tracer (
    popstate
    replacestate
    Etc.) monitoring. If it is found that the routing of the page has changed, it will be considered that the current page has entered
    terminated
    Life cycle, so as to deal with accordingly. The logic of monitoring routing changes here reuses the @byted-cg/puzzle-router SDK we developed earlier , and interested students can refer to it.

  • Unable to capture

    discarded
    Life cycle

    discarded
    The life cycle occurs when a web page is forcibly cleared by the browser. At this time, the web page has been destroyed and cleared from the memory, and no events can be transmitted to the outside, so
    PageLifecycle.js
    It will not be able to push
    discarded
    event. Once this happens, it will cause the loss of the cleared web page statistics. In order to respond to this scenario,
    monitor-tracer
    Will enter on the page
    invisible
    In the state, the existing page duration statistics data are stored and used
    JSON.stringify
    Serialized and stored in
    localStorage
    among. If the page is restored
    visible
    Status, it will put
    localStorage
    The data in is cleared; and if the page is cleared, the next time the page is entered, the
    localStorage
    The data of the previous page stored in is pushed out through events. This ensures to the greatest extent that even if the page is forcibly cleared, its data can be sent out without being lost.

Get page activity data

Compared with page visibility, the judgment of page activity is more straightforward, and the page status is directly judged by the following method

active
still is
inactive
That's it.

active
Judgment Standard

By listening to a series of browser events, we can determine whether the user is active on the current page.

monitor-tracer
The following six events will be monitored:

eventdescription
keydown
Triggered when the user hits the keyboard
mousedown
Triggered when the user clicks the mouse button
mouseover
Triggered when the user moves the mouse pointer
touchstart
Triggered when the user's finger touches the touch screen (touch screen devices only)
touchend
Triggered when the user's finger leaves the touch screen (touch screen devices only)
scroll
Triggered when the user scrolls the page

Once the above events are monitored,

monitor-tracer
Will mark the page as
active
Status, and record the current timestamp, accumulate the active duration.

inactive
Judgment Standard

In the following two cases, the page will be marked as

inactive
status:

  • Exceeding a certain time threshold (the default is 30 seconds, which can be customized when initializing the SDK) did not monitor the six events that indicate that the page is active;
  • The page status is
    invisible
    . Because if the page is not visible to the user, then it must be inactive.

The page is marked as

inactive
Rear,
monitor-tracer
The current timestamp will be recorded and the active duration will be accumulated.

Component visibility time statistics

Two conditions are required to count the active duration of the component level. One is to get all the DOM elements that need to be counted, and the other is to monitor these DOM elements according to certain standards.

Get the DOM elements that need statistics

Monitor DOM structure changes

Obtaining DOM elements requires us to monitor changes to the entire DOM structure.

monitor-tracer
used
MutationObserver
API
, any changes to the DOM, such as the increase or decrease of nodes, attribute changes, and text content changes, can be notified through this API.

Conceptually, it is very close to the event, which can be understood as triggering when the DOM changes

MutationObserver
event. However, it is essentially different from events: events are triggered synchronously, that is, changes in the DOM will immediately trigger corresponding events, and
MutationObserver
It is triggered asynchronously. DOM changes will not be triggered immediately, but will not be triggered until all current DOM operations have ended.

This design is to cope with the frequent changes of the DOM and save performance. For example, if you insert 1000 consecutively in the document

<p></p>
Tag, it will trigger 1000 insert events continuously, and execute the callback function of each event, which is likely to cause the browser to freeze. and
MutationObserver
It's completely different. It will only trigger after the insertion of 1000 labels is complete, and it will only trigger once.

MutationObserver
The usage of the API is as follows:

const observer = new MutationObserver( function ( mutations, observer ) { mutations.forEach( function ( mutation ) { console .log(mutation.target); //target: the DOM node that has changed }); }); observer.observe( document .documentElement, { childList : true , //Changes in child nodes (referring to new, deleted or changed) attributes : true , //Changes in attributes characterData : true , // Changes in node content or node text subtree : true , //Indicates whether to apply the observer to all descendant nodes of the node attributeOldValue : false , //Indicates whether the attribute value before the change needs to be recorded when observing attribute changes. characterDataOldValue : false , //Indicates whether to observe the change of characterData When, whether it is necessary to record the value before the change. attributeFilter : false ,//Indicates a specific attribute that needs to be observed, such as ['class','src'] }); observer.disconnect(); //Used to stop the observation. After calling this method, if the DOM changes again, the observer will not be triggered to copy the code

Mark the elements that need to be monitored

In order to find the elements that need to be monitored among the many DOM elements, we need a method to mark these elements.

monitor-tracer
SDK stipulates that if a component needs to count the active time, it needs to add a
monitor-pv
or
data-monitor-pv
Attributes. currently using
MutationObserver
When scanning for DOM changes,
monitor-tracer
The DOM elements with these two attributes will be collected into an array for monitoring. For example, the following two components:

< div monitor-pv = '{ "event": "component_one_pv", "params": {...} }' > Component One </div > < div > Component Two </div > Copy code

Component One because of the addition of

monitor-pv
The attributes will be recorded and the visible duration will be counted. Component Two does not.

babel-plugin-tracer
Plug-in

If the component to be monitored is written through some component library (such as Ant Design or ByDesign), add it

monitor-pv
or
data-monitor-pv
Such custom attributes may be filtered by the component itself and will not appear on the final generated DOM element, causing the monitoring of the component to not take effect. In order to solve similar problems, we developed
@byted-cg/babel-plugin-tracer
babel plugin. This plugin will look for the added
monitor-pv
Attribute component, and wrap a custom
<monitor></monitor>
label. E.g:

Import {Card} from 'antd' ; const Component = () => { return < Card monitor-pv = {{ event: " component_one_pv ", params: { ... } }}> HAHA </Card > } Copy code

If no plug-in is added, the resulting DOM is:

< div class = "ant-card" > <!-- ... Ant Design Card components--> </div > Copy code

visible

monitor-pv
The properties disappear after component filtering.

After installing the babel plug-in, the final compiled DOM structure is:

< monitor is = "custom" data-monitor-pv = '{ "event": "component_one_pv", "params": {...} }' > < div class = "ant-card" > <!--. .. Ant Design Card assembly -> </div > </Monitor > copy the code

monitor-pv
The properties are preserved, and the plug-in automatically adds
data-
Prefix, in response to React 16 versions before only supported
data-
The problem with the custom attribute at the beginning; at the same time use the passed object
JSON.stringify
Converted to the only supported DOM element attribute
string
Types of.

Since the custom label does not have any style, wrapping the label will not affect the style of the original component.

monitor-tracer
After the SDK scans the DOM elements, it will collect all
<monitor></monitor>
Information about the elements in the label, and monitor the elements it wraps.

Determine the visibility of DOM elements

The judgment of component visibility can be divided into three dimensions:

  • Is the component in the browser viewport-use
    IntersectionObserver
    API
    judgment;
  • Whether the component style is visible-according to the element CSS
    display
    visibility
    and
    opacity
    Style attribute judgment;
  • Whether the page is visible-judged based on page visibility.

Determine whether the component is in the browser viewport

Here we used

API . This API provides a method for asynchronously detecting changes in the intersection of the target element and the ancestor element or viewport .

In the past, event monitoring was usually used for intersection detection, and it needed to be called frequently

Method to obtain boundary information of related elements. Event monitoring and invocation
Element.getBoundingClientRect
All run on the main thread, so frequent triggers and calls may cause performance problems. This detection method is extremely strange and inelegant.

and

IntersectionObserver
API will register a callback function, whenever the monitored element enters or exits another element (or viewport), or when the size of the intersection of two elements changes, the callback method will be triggered to execute. In this way, the main thread of our website does not need to work hard to monitor the intersection of elements, and the browser will optimize the management of the intersection of elements by itself.

IntersectionObserver
The usage of the API is as follows:

const observer = new IntersectionObserver( ( entries ) => { entries.forEach( function ( entry ) { /** entry.boundingClientRect//Information about the rectangular area of the target element entry.intersectionRatio//The visible ratio of the target element, that is, the ratio of intersectionRect to the boundingClientRect, which is 1 when it is completely visible, and less than or equal to 0 when it is completely invisible entry.intersectionRect//Information about the intersection area between the target element and the viewport (or root element) entry.isIntersecting//indicates whether the element has been converted to an intersecting state (true) or out of an intersecting state (false) entry.rootBounds//Information about the rectangular area of the root element, the return value of the getBoundingClientRect method, if there is no root element (that is, scroll directly relative to the viewport), it returns null entry.target//The target element to be observed is a DOM node object entry.time//The time when the visibility changes, a high-precision timestamp, in milliseconds **/ }); }, { threshold : [ 0 , 0.25 , 0.5 , 0.75 , 1 ], //This attribute determines when the callback function is triggered. It is an array, each member is a threshold value, the default is [0], that is, the callback function is triggered when the intersection ratio (intersectionRatio) reaches 0 } ); observer.observe( document .getElementById( "img" )); //start monitoring a target element observer.disconnect (); //stop listening all the work Copy the code

If the intersection ratio of a component and the viewport is less than a certain value (default is 0.25), then the component will be marked as

invisible
On the contrary, if the ratio is greater than a certain value (default is 0.75), then this component will be marked as
visible
.

Determine whether the CSS style of the component is visible

If the CSS style of the element is set

visibility: hidden
or
opacity: 0
, Then even if the ratio of its intersection with the viewport is 1, it is invisible to the user. Therefore, we need to additionally determine whether the CSS properties of the target element are visible.

If the style of a component is set to one of the following, then it will be marked as

invisible
.

  • visibility: hidden
  • display: none
  • opacity: 0

Determine whether the page is visible

When the page is not visible, all components are naturally invisible, so the page is

invisible
In the state,
monitor-tracer
The status of all components to be monitored will also be marked as
invisible
.

SDK for handling "trigger events"-
monitor

monitor
SDK positioning

The single burying point reporting method of the data platform SDK cannot satisfy our ultimate pursuit of clean code in development

The SDK of the data platform often only provides a functional method for reporting the buried point. Although it can meet our daily development needs, it cannot solve our two major pain points when writing the buried point code:

  • Only report the buried points one by one
  • Coupling of embedded logic and business logic

We hope that the embedded code can be easily added, modified and deleted without affecting the business code. Therefore, based on TypeScript development, we are insensitive to the framework

monitor
SDK. It supports uploading multiple buried points one by one, and accepts the function that returns the buried points, and reports its return value; it provides three ways to inject the buried points, covering all scenarios, and completely separating the buried points from the business code.

As can be seen,

monitor
It is both a data processor and a method library. More intuitive, use
monitor
Later, the process of our application when reporting the buried points is as follows:

The buried point is sent by the application layer to

monitor
Rear,
monitor
1. the data will be processed, and then the data platform SDK will be called to report the embedding event to the data platform.

Right

monitor
After getting a preliminary understanding, this article will mainly explain
monitor
How to decouple the business logic and the buried point logic through the following three methods of buried point injection.

Let's take a look

monitor
with
monitor-tracer
SDK specific technical design and implementation methods.

3.buried point injection methods

Command-like

monitor
Provides a way to inject the buried point of the class instruction. For example, the next code uses
monitor-click
The instructions are injected into the buried point. When this button is clicked (click),
monitor-click
The corresponding value, that is, an event, will be reported.

//Example of imperative burying point <Button monitor-click={ JSON .stringify({ type : "func_operation" , params : { value : 3 }, })} > Click Me </Button> Copy code

How is this achieved? Why only add one to the component

monitor-click
Attributes,
monitor
Will it be reported when this button is clicked?

Implementation and principle

in fact,

monitor
When the SDK is initializing, it will give the current
document
Object plus a series of Event Listeners, monitoring
hover
click
input
focus
Wait for the event. When the listener is triggered,
monitor
Will trigger the event
target
Starting from the object, traverse up level by level to see if the current element has an instruction corresponding to this event. If so, report this event until it encounters an element node without an event instruction. The following diagram shows the reporting process of the command-like buried point:

Progressive escalation process

Take the following code as an example, when the cursor hover to

Button
Time,
document
Monitor installed on the object
hover
The function of the event will be executed. This function is first in
event.target
which is
Button
Find on whether there is a relationship with
hover
Event-related instructions (ie attributes).
Button
Have
monitor-hover
This instruction, at this time the function uploads the event corresponding to this instruction, namely
{type:'func_operation', params: {value: 1 }}
.

Next, the function moves up one level to the

Button
The parent element of
div
, Repeat the above process, it found
data-monitor-hover
This instruction also reported the corresponding burying incident. And arrived
section
This layer, although it has
data-monitor-click
Command, but this command is wrong
hover
In response to the incident, the process of reporting the buried points level by level is over.

//Command-style burying points to achieve step -by- step reporting < section data-monitor-click = {JSON.stringify({ type: ' func_operation ', params: { value: 3 }, })} > < div data-monitor-hover = {JSON.stringify({ type: ' func_operation ', params: { value: 2 }, })} > < Button monitor-hover = {JSON.stringify({ type: ' func_operation ', params: { value: 1 }, })} > Click Me </Button > </div > </section > Copy code

Instruction-like embedded point injection is suitable for simple embedded point reporting, which is clearly separated from the business code. But if we need to process the reported data before reporting the incident, then this approach cannot be satisfied. Also, not all scenes can be covered by DOM events. If I want to report the buried point when the user enters a value in the search box, then I need to analyze the value entered by the user, but cannot

input
The buried point is reported every time an event is triggered.

Decorator

The decorator is essentially a higher-order function. It accepts a function and returns another decorated function. Therefore, we naturally think of using decorators to inject embedding logic into business functions, which not only realizes the separation of embedding points and business code, but also adapts to complex embedding scenarios.

The following code uses

@monitorBefore
Decorator.
@monitorBefore
The return value of the received function is the event to be reported. in
handleSearch
When the function is called,
monitor
Will report the buried incident first, and then execute
handleSearch
The logic of the function.

//@monitorBefore usage example @monitorBefore ( ( value: string ) => ({ type : 'func_operation' , params : { keyword : value }, })) handleSearch () { console .log( '[Decorators Demo]: this should happen AFTER a monitor event is sent.' , ); } return ( < AutoComplete onSearch = {handleSearch} /> ) Copy code

From
@readonly
Understand the decorator principle

How does the decorator integrate the embedding logic and business logic? In our detailed interpretation

@monitorBefore
Before, let s start with a commonly used decorator
@readonly
Let's talk about it.

The decorator is applied to a single member of a class, including the properties, methods, getters and setters of the class. When called, the decorator function receives 3 parameters:

  • target
    -The class the decorator is in
  • name
    -The name of the decorated function
  • descriptor
    -The attribute descriptor of the decorated function
//@readonly decorator code implementation readonly = ( target, name, descriptor ) => { console .log(descriptor); descriptor.writable = false ; return descriptor; }; Copy code

The above code passes

console.log
The output result is:

To our common

@readonly
As an example, its implementation method is as above. Log it out in the above code
descriptor
, We learned
descriptor
The attributes are:

  • writable
    -Whether the decorated function can be changed by the assignment operator;
  • enumerable
    -Whether the decorated function appears in the enumeration property of the object;
  • configurable
    -Whether the descriptor of the decorated function can be changed and whether it can be deleted from the object;
  • value
    -The value of the decorated function, that is, its corresponding function object.

visible,

@readonly
Decorator will
descriptor
of
writable
Attribute is set to
false
, And return this
descriptor
, Successfully set its decorated class members to read-only state.

We use it as follows

@readonly
Decorator:

class Example { @readonly a = 10 ; @readonly b () {} } Copy code

@monitorBefore
Realization of

@monitorBefore
Decorator is better than
@readonly
It is more complicated. How does it integrate the embedded logic with the business logic to generate a new function?

1. let's take a look at the following piece of code.

monitorBefore
Receive a buried event
event
As a parameter, and returned a function. The parameters of the returned function are the same as those mentioned above
@readonly
The received parameters are consistent.

//monitorBefore function source code monitorBefore = ( event: MonitorEvent ) => { return ( target: object , name: string , descriptor: object ) => this .defineDecorator(event, descriptor, this .before); }; Copy code
//@monitorBefore usage @monitorBefore ( ( value: string ) => ({ type : 'func_operation' , params : { keyword : value }, })) handleSearch () { console .log( '[Decorators Demo]: this should happen AFTER a monitor event is sent.' , ); } return ( < AutoComplete onSearch = {handleSearch} /> ) Copy code

At compile time,

@monitorBefore
Received one
event
Parameters, and returns the following function:

= F ( target: Object , name: String , descriptor: Object ) => the this .defineDecorator (Event, descriptor, the this .before); duplicated code

Then, the compiler calls the function

f
, Pass the current class, the name of the decorated function and its attribute descriptor to
f
. Function
f
return
this.defineDecorator(event, descriptor, this.before)
Will be parsed as a new
descriptor
Object, its
value
Will be called at runtime, which means that it will be
onSearch
Called when triggered.

Now, let s interpret in detail

defineDecorator
How the function is changed to generate a new
descriptor
Right.

//defineDecorator function source code before = ( event: MonitorEvent, fn: () => any ) => { const that = this ; return function ( this : any ) { const _event = that.evalEvent(event)(... arguments); that.sendEvent(_event); return fn.apply( this , arguments ); }; }; defineDecorator = ( event: MonitorEvent, descriptor: any , decorator: (event: MonitorEvent, fn: () => any ) => any ) => { if (isFunction(event) || isObject(event) || isArray(event)) { const wrapperFn = decorator(event, descriptor .value); function composedFn ( this : any ) { return wrapperFn.apply( this , arguments ); } set(descriptor, "value" , composedFn); return descriptor; } else { console .error( `[Monitor SDK @ ${decorator} ] the event argument be an object, an array or a function.` ); } }; monitorBefore = ( event: MonitorEvent ) => { return ( target: object , name: string , descriptor: object ) => this .defineDecorator(event, descriptor, this .before); }; Copy code

defineDecorator
The function receives three parameters:

  • event
    -Burying points that need to be reported;
  • descriptor
    -The attribute descriptor of the decorated function;
  • decorator
    -A higher-order function. It receives a buried point event and a callback, returns a function to report the buried point, and then executes the callback.

decorator
First returns a function
wrapperFn
. When called,
wrapperFn
Will report the buried point first, and then execute
descriptor.value
The logic of the decorated function.

in

defineDecorator
of
composedFn
In, we use
wrapperFn.apply(this, arguments)
Pass the parameters passed in when the decorated function is called to
wrapperFn
.

Finally, we will

composedFn
Set as
descriptor.value
, In this way, we have successfully generated a new function that combines embedded point logic and business logic.

The decorator embedded point injection method is very neat and can be clearly distinguished from the business code. Regardless of adding, modifying or deleting buried points, there is no need to worry about changes to the business code. But its limitations are also obvious. Decorators can only be used for class components. Now we can't use decorators for functional components.

React hook

In order to be able to realize the functions brought by decorator embedding in functional components, we also support embedding hooks

useMonitor
. The same principle as the decorator,
useMonitor
Receive a buried function, a business function, and return a new function to merge the two, which not only achieves a clear separation at the code level, but also covers the buried point injection of the entire scene.

//useMonitor source code useMonitor = ( fn: () => any , event: MonitorEvent ) => { if (!event) return fn; const that = this ; return function ( this : any ) { const _event = that.evalEvent(event)(...arguments); that.sendEvent(_event); return fn.apply( this , arguments ); }; }; Copy code

useMonitor
The implementation of is relatively simple, just a high-order function, unlike the decorator that requires syntax analysis. It returns a function, when it is called, it will upload the buried event first, and execute the business logic. Its usage is as follows:

//useMonitor usage example const Example = ( props: object ) => { const handleChange = useMonitor( //business logic ( value: string ) => { console .log( "The user entered" , value); }, //Buried point logic ( value: string ) => { return { type : "func_operation" , params : {value }, }; } ); return < Search onSearch = {} handleChange/> ; }; Copy code

summary

The above three burying methods cover all usage scenarios. Whether you use React, Vue, or native JavaScript, whether you use a class component or a functional component, whether your embedding requires complex pre-logic,

monitor
The SDK provides a usage method suitable for your scenario.

Technology stack

Developer Credits

  • monitor
    SDK-[Lilly Jiang]
  • monitor-tracer
    SDK
    • Webpage visible and active time, babel plug-in-[Aiqing Dong]
    • Component visible time-[Yuling Chen]