react-decorator-track 解耦-埋点方案

3,885 阅读4分钟

简单说明

目的:与业务代码完全解耦 支持 组件进入/离开数据埋点(同步/异步)、内部方法埋点(同步/异步) 说明:第一版本,是为了试验性目的,直接提供中间件源码,有需要的可以结合自己的业务,fork源码,进行修改即可

拿到代码后,可以根据自己的业务,需要修改埋点数据的data以及源码中的createTrackData方法即可,主要用于将data中的空值,用state来替换

引入

代码地址 或者 cnpm install react-decorator-track --save

支持埋点类型

  • TrackPage:负责处理生命周期函数中的埋点
  • Track:负责处理方法事件的埋点

TrackPage

  • in类型:componentWillMount,记录进入页面的时间,及自带的数据(data)
@TrackPage({
  in: {
    data: { evt: 'in', test: '1' }
  }
})
export default class xxxx extends React.Component {}
  • out类型:componentWillUnmount,记录离开页面时的时间,及自带的数据(data)
@TrackPage({
  out: {
    data: { evt: 'out', test: '2', productCount: '' }
  }
})
export default class xxxx extends React.Component {}
  • inOut类型:componentWillMount 与 componentWillUnmount,记录进入页面的时间、离开页面时的时间以及页面的停留时间,和自带的数据(data)
@TrackPage({
  mounted: {
    data: { evt: 'in-out', test: '3', productCount: '' }
  },
})
export default class xxxx extends React.Component {}
  • mounted类型:ccomponentDidMount,记录挂载(同步/异步初始化数据)进行埋点数据
@TrackPage({
  mounted: {
    data: { evt: 'mount', test: '5', userCount: '', orderCount: '' }
  },
})
export default class xxxx extends React.Component {}

说明: data 是要使用的埋点数据,包含固定值的数据,和异步请求返回的数据,异步返回的数据需要在state中包含,这样在TrackPage、Track内部才能拿到正确的数据值,将state与data进行结合遍历,替换data中设置为空的值,

  • in: data数据都是固定值,componentWillMount 处理 state 没有意义
  • out - 上面例子中,data包含了 productCount为空,同时它也是state中的属性,这样做的目的是为了,将埋点数据productCount替换成state中对应的数据,同/异步数据问题就可以在内部解决
  • inOut - 同上
  • mounted - 同上

同时也可以,这四种类型结合着使用,比如这样

@TrackPage({
  inOut: {
    data: { evt: 'in-out', test: '3', productCount: '' }
  },
  mounted: {
    data: { evt: 'mount', test: '5', userCount: '', orderCount: '' }

  }
})

Track

@Track({
    data: {  evt: 'func1', test: 'a', userCount: '' },
    // execute: 'before'
})
async submit() {
    console.log('提交')
    const data = await new Promise(resolve => setTimeout(() => resolve('555'), 1000))
    this.setState({ userCount: data })
}

execute 默认为 after: 用于区分在动作完成前(同步)、后进行埋点操作(异步)

看一个demo

import React from 'react'
import './index.scss'
import { TrackPage, Track } from 'react-decorator-track'

@TrackPage({
  in: {
    data: { evt: 'in', test: '1' }
  },
  out: {
    data: { evt: 'out', test: '2', productCount: '' }
  },
  inOut: {
    data: { evt: 'in-out', test: '3', productCount: '' }
  },
  mounted: {
    data: { evt: 'mount', test: '5', userCount: '', orderCount: '' }
  }
})
export default class Home extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      userCount: '-',
      productCount: '-',
      orderCount: '-'
    }
  }

  // 可加可不加
  componentWillMount() {
    // console.log('will')
  }
  
  getHomeCount() {
    const data = {
      userCount: '666',
      productCount: '333',
      orderCount: '999'
    }

    return new Promise(resolve => setTimeout(() => resolve(data), 1000))
  }

  async componentDidMount() {
    // let data = await statiService.getHomeCount()
    let data = await this.getHomeCount()
    this.setState(data)
  }
  
  
  @Track({
    data: {  evt: 'func1', test: 'a', userCount: '' },
    // execute: 'before'
  })
  async submit() {
    console.log('提交')
    const data = await new Promise(resolve => setTimeout(() => resolve('555'), 1000))
    this.setState({ userCount: data })
  }
  
  // 可加可不加,
  componentWillUnmount() {
    console.log('准备离开') 
  }

  render() {
    let { userCount, productCount, orderCount } = this.state
    return (
      <div className="page-wrapper">
        <PageTitle title="首页" />
        <div className="row">
          <div className="col-md-4">
            <Link to="/user" className="color-box brown">
              <p className="count">{userCount}</p>
              <p className="desc">
                <i className="fa fa-user-o"></i>
                <span>用户总数</span>
              </p>
            </Link>
          </div>
          <div className="col-md-4">
            <Link to="/product" className="color-box green">
              <p className="count">{productCount}</p>
              <p className="desc">
                <i className="fa fa-user-o"></i>
                <span>商品总数</span>
              </p>
            </Link>
          </div>
          <div className="col-md-4">
            <Link to="/order" className="color-box blue">
              <p className="count">{orderCount}</p>
              <p className="desc">
                <i className="fa fa-user-o"></i>
                <span>订单总数</span>
              </p>
            </Link>
          </div>
        </div>
        <div className="row">
          <button className="btn" onClick={() => this.submit()}>提交测试</button>
        </div>
      </div>
    )
  }
}

看一下结果: 进入页面时,执行了 in 和 mounted 埋点

componentDidMount中有异步请求,而埋点数据包含异步请求后的数据,因此,需要async componentDidMount() 来使用,如果不加 async,则埋点同步数据

通过从state中拿到 userCount,orderCount的正确值

离开页面

点击submit 如果按照 execute 默认after,则在动作完成后进行埋点,也就是等待异步提交结束后进行埋点

由图可以看到, userCount 为 异步提交后的结果

如果设置 execute 为 before,则在动作开始前进行埋点,

如何编写适合自己业务的埋点插件

1.弄明白decorator 装饰器的使用原理 2.通过装饰器方法,获取调用的组件或方法的实例,获取内部状态及参数 3.把方法或生命周期交给装饰器方法来执行,可以理解为代理

结语

以上demo和源码,大家可以随意拿去使用,改造成适合自己公司业务的埋点方案,技术无壁垒,欢迎大家一起交流学习

小疯子 github