Git Product home page Git Product logo

react-track's Introduction

react-track

基于React的声明式PV及用户行为采集框架。

为什么自研

在NPM上有若干个类似的包,但它们存在着一些缺陷,这其中主流的两类是:

  • react-tracking:偏向于声明式,但使用HOC的形式限制了使用的场景,且通过拦截类方法而臧props来进行数据的采集,与React的数据流形式略有不和。
  • react-tracker:采用类似react-redux的**,使用connectProvider的形式将功能联系起来。但是这种做法更偏向于命令式,从使用的角度来说繁琐之余也不易追踪。

除此之外,这些包均没有提供PV采集的能力。而PV采集中,有一个非常关键的问题至今没有得到很好的解决:

当URL中包含参数时,如/posts/123/posts/456在PV上会被认为是两个页面,但事实上它们对应的路由均是/posts/:id,是相同的。

这一问题导致如果需要将包含参数的URL进一步的汇总与分组来更精确地计算“页面”的PV,则会需要额外的数据分析成本。因此我们希望从源头,即在数据采集的时候就解决这一问题,这也导致需要与react-router进行关联。

我们的目标是:

  • 使用声明式的形式进行数据采集。
  • 与React的组件树结构进行整合,可在JSX中形象地表达。
  • 提供PV采集的能力,且能够获取react-router的配置
  • 尽可能小的移除成本,当一个行为或页面PV不再需要采集时,可以用最简单的手段移除而不影响已有组件的逻辑。

使用文档

全局环境准备

类似于reduxreact-routerreact-track需要一个全局的环境来定义数据的采集过程和数据的记录形式,这些环境由Tracker组件来定义。Tracker组件需要以下2个属性:

  • {Function} collect:定义如何采集和组装需要记录的数据。
  • {Object} provider:定义如何将数据记录下来或发送至指定服务。

并且支持以下可选配置:

  • {boolean} reportPageViewOnLeafOnly:仅让作为叶子节点的TrackRoute组件报告PV,如果一个TrackRoutechildren属性,则不作为叶子节点处理。该配置默认关闭。
  • {boolean} warnNestedTrackRoute:当一个TrackRoute在另一个TrackRoute里面时,在控制台打印一个警告信息,用于检查一些不符合预期的路由嵌套。该配置默认打开。

以上2个属性也可以直接用于TrackRoute组件上,以覆盖Tracker组件配置的默认值。

定义数据采集

Tracker组件中,collect属性用来定义“采集哪些数据”以及“数据的最终结构”。connect是一个函数,其签名如下:

type collectPageView = (type: 'pageView', location: Location) => object;
type collectEvent = (type: 'event') => object;
type collect = collectPageView | collectEvent;

react-track内置了几种常用的采集函数:

  • browser():添加浏览器相关的信息,包括UA、分辨率、操作系统、浏览器版本、系统语言。这个采集仅在类型为pageView时才会生效。
  • context(env):将固定的env对象放到采集数据中去,常用于添加当前登录用户名、系统名称、系统版本等信息。
  • session(storageKey):跟踪一次用户的访问,为每一次访问生成一个唯一的标识,并存放在sessionStorage中,这个唯一标识会变为名为session的属性值。可以通过storageKey来自定义sessionStorage中对应的键名。
  • basename(prefix):默认采集路径信息是基于location.pathname的,但它并不包含basename。所以,当history设置basename时,需要使用该collect指定basename才能采集准确的路径

当希望同时使用多个collect函数时,可以通过combineCollects将它们组合成一个函数。以下代码展示了如何使用多个collect函数,并通过combineCollects将它们组合成一个:

import {combineCollects, browser, context, session} from '@ecomfe/react-track';

const app = {
    name: 'My App',
    version: '1.0.0',
    branch: 'stable'
};

const collect = combineCollects(
    context(app),
    browser(),
    session()
);

定义数据应用

Tracker组件的provider属性用于控制采集到的数据如何使用,通常在生产环境我们会选择将其发送到指定的服务,如百度统计、Google Analysis等,在开发环境中则可以忽略或者显示在控制台中。

provider是一个对象,其定义如下:

type PageViewData = {
    location: Location,
    referrer: Location,
    [key: string]: any
};

type EventData = {
    category: string,
    action: string,
    label: string,
    [key: string]: any
};

interface TrackProvider {
    install(): void;
    uninstall(): void;
    trackPageView(data: PageViewData): void;
    trackEvent(data: EventData): void;
}

通常使用install来做初始化的工作,uninstall进行清理,而trackPageViewtrackEvent则会在每一次PV或自定义事件数据采集完成后被调用。

react-track同样内置了几个常用的处理器:

  • holmes(site):封装了百度统计,接受对应的百度统计id。
  • print():用于调试,通过控制台打印对应的数据。
  • empty() :忽略所有的数据。

collect相似地,一个应用中我们可能会需要同时将数据进行多重处理,如既发送到百度统计,又打印在控制台中,此时可以使用composeProvider函数进行组装。以下代码自定义了一个provider用于将数据通过POST发送到指定的服务器,同时将2个处理器组合为一个,最后仅在生产环境才生效,开发环境仅打印在控制台:

import {holmes, print, composeProvider} from '@ecomfe/react-track';
import axios from 'axios';

const post = url => {
    const send = type => data => axios.post(url, {type, ...data});

    return {
        install: noop,
        uninstall: noop,
        trackPageView: send('pageView'),
        trackEvent: send('event')
    };
};

const trackProvider = process.env.NODE_ENV === 'production'
	? composeProvider(
		post('http://127.88.88.88:8888/v1/log'),
		holmes(mySiteID)
    )
	: print();

定义环境

在有了collectprovider的定义后,将Tracker组件置于应用的最外层即可以完成全部环境的准备:

import {Tracker} from '@ecomfe/react-track';
import {BrowserRouter} from 'react-router';
import {App} from 'components';

<Tracker collect={collect} provider={trackProvider}>
    <BrowserRouter>
        <App />
    </BrowserRouter>
</Tracker>

采集PV

对于Web应用,PV是数据采集中的必要信息。传统的PV采集会存在一个问题,假设我们有一个展示用户信息的页面,其路由是/users/:username,那么对于不同的用户,我们将会得到不同的URL,如/users/Alice/users/Bob。在常见的PV采集方案中,采集工具仅对URL作出反应,因此在数据的统计中,我们会看到2个页面分别有n和m的访问量。

但是在对应用系统的分析时,我们更希望得到这样一个信息:用户信息页被访问了多少次。然而问题是,在采集的数据中,要通过/users/Alice/users/Bob去还原用户信息页被问题的问题(n + m)是相对困难的,当路由规则更加复杂时,甚至可能是无法实现的。

为此,react-track在PV采集上,提供了将/users/:username这个URL模块也一并捕获的能力,使用react-router的定义,称之为path。由于react-router 4.x的特征,从全局顶层来获取path是不可能的,因此为了实现这一功能,使用react-track的系统不得不在代码上做出一些微小的改变。

声明PV采集点

react-track要求用户显式地声明需要采集PV信息的路由位置,提供了TrackRoute这一组件来进行声明。

TrackRoute的功能与react-routerRoute组件完全兼容,因此对于一个已经使用了react-router的系统,只需要在合适的位置将<Route>修改为<TrackRoute>即可:

import {Switch, Route} from 'react-router-dom';
import {CommonHeader, AboutTab, Info, Contact} from 'components';

const App = () => (
    <div>
        <CommonHeader />
        <Switch>
    	    <TrackRoute exact path="/console" component={Console} />
	        <TrackRoute exact path="/service" render={() => <Service />} />
        	<Route path="/about" component={AboutMe}>
                <AboutTab />
                <TrackRoute exact path="/about/info" component={Info} />
                <TrackRoute exact path="/about/contact" component={Contact} />
            </Route>
        </Switch>
	</div>
);

需要注意的一点是,TrackRoute就当仅应用在最底层的路由上。如上述代码中的/about这一级路由并不是最底层的,其下还有/about/info/about/contact,如果将这一层的<Route>改为<TrackRoute>的话,当访问/about/info时,由于上下两个路由都会触发PV采集,最终将会形成2条数据:

  • {pathname: "/about/info", path: "/about"}
  • {pathname: "/about/info", path: "/about/info"}

它们的pathname是一样的,始终指向当前真实的URL,而path则不同,指向<TrackRoute>上的path属性。这会导致数据的重复。

高阶组件

同时react-track还提供了trackPageView高阶组件,可以将任意的组件声明为PV采集点。trackRoute并不会声明路由信息,因此还需要将组件放置在<Route>下:

import {trackPageView} from 'react-track';
import {Route} from 'react-route';

const Console = () => (
    <div>
        ...
    </div>
);
const ConsoleWithTrack = trackPageView(Console);

<Route exact path="/console" component={ConsoleWithTrack} />

采集事件

通过事件采集可以分析用户的行为,帮助理解用户的真实需求并进行产品的改进。react-track提供了简单直接的采集方式,允许通过对事件回调类型的属性进行拦截来采集相关数据。

在常见的自定义事件模型中,一个事件由categoryactionlabel三个属性组成。

定义采集点

react-track提供了TrackEvent组件,使用它包裹在对应组件的外层,并通过eventPropName指定需要拦截的事件名称,用categoryactionlabel声明事件的相关信息。除以上4个属性外,其它的属性会透传给其子元素。

子组件需要支持eventPropName对应的属性,且必须是函数类型。需要支持多个事件类型时可以嵌套使用:

import {TrackEvent} from '@ecomfe/react-track';
import {NavLink} from 'react-router-dom';

const NavItem = ({name, to}) => (
    <TrackEvent eventPropName="onMouseEnter" category="navigation" action="mouseEnter" label={name}>
        <TrackEvent eventPropName="onMouseLeave" category="navigation" action="mouseLeave" label={name}>
            <li>
                <NavLink exact to={to}>{name}</NavLink>
            </li>
        </TrackEvent>
    </TrackEvent>
);

hooks

const Item = ({name, onClick}) => {
    const trackEvent = useTrackEvent();
    const handleClick = useCallback(
        e => {
            trackEvent({category: 'navigation', action: 'click', label: name});
            onClick(e)
        },
        [name]
    )
    return <Button onClick={handleClick}>Click Me</Button>
};

高阶组件

react-track同时提供了trackEvent高阶组件,用于直接在一个现有组件上添加事件采集的能力。在希望采集多个事件时,与recompose一起配合能取得更好的代码可读性:

import {trackEvent} from '@ecomfe/react-track';
import {NavLink} from 'react-router-dom';
import {compose} from 'recompose';

const NavItem = ({name, to}) => (
    <li>
    	<NavLink exact to={to}>{name}</NavLink>
    </li>
);

const track = action => {
    const options = {
        eventPropName: 'on' + action[0].toUpperCase() + action.slice(1),
        category: 'navigation',
        action: action,
        label: null
    };

    return trackEvent(options);
};

const enhance = compose(
    track('mouseEnter'),
    track('mouseLeave')
);

export default enhance(NavLink);

react-track's People

Contributors

dancerphil avatar hunter2009 avatar hy-pillow avatar ice-zjchen avatar nick-chenze avatar otakustay avatar thornwu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-track's Issues

HoC形式下的一个获取内部组件props的问题

(按回车直接提交了。。。)
就如README中的最后关于HoC的例子:https://github.com/ecomfe/react-track#%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6-1

const track = action => {
    const options = {
        eventPropName: 'on' + action[0].toUpperCase() + action.slice(1),
        category: 'navigation',
        action: action,
        label: null // 此处label期望值应为NavItem的name prop
    };
    
    return trackEvent(options);
};

我们会期望label的值为 NavItem的其中一个prop: name。但实际并不能取到,所以这个HoC的应用场景是有局限的?

支持拖拽数据场景

Cafe这边PM有个诉求,希望能收集到卡片拖起、放置的使用情况,不知道目前有方式可以收集到么?

Tracker没有将prop透传给children

如果结构是:

<Router>
  <Tracker>
    ...
  </Tracker>
</Router>

Router会把historylocationmatch传给Tracker,而Tracker并没有将这些内容透传下去,导致了渲染出错

支持延迟请求发送、请求合并

目前有一些问题,收集用户数据的时候,尤其是PV的时候,track的请求优先级太高,会影响请求的优先级
image

因此想要支持一些请求延迟发送,最好也可以合并请求

支持HOC形式使用

trackPageView(Component);
trackEvent(eventPropName, category, action, label)(Component)

在现有的组件外,也允许使用HOC的形式来采集数据

移除对uuid的依赖

实测由于uuid -> rng -> crypto的依赖关系,在webpack打包时会引入相关的polyfill导致gzip后60KB的体积消耗

uuid在这个库只用于计算一个session key,可以用随机+时间等方法搞定

没有提供路由的path

假设有这样的路由:

<Route path="/users/:id" component={UserInfo} />

有以下的URL:

- /users/123
- /users/456

这两个URL从location.pathname上看,是2个页面。但对应的是同一个路由,即从match.path上看是一样的。

从PV统计的角度来说,这2个PV应该算是同一个页面的,而事后去用正则或其它手段去做这些PV的合并是很麻烦的,因此应该在采集数据的时候就提供match.path属性。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.