{"id":29926691,"url":"https://github.com/ecomfe/react-track","last_synced_at":"2025-08-02T12:43:17.798Z","repository":{"id":32405508,"uuid":"130156767","full_name":"ecomfe/react-track","owner":"ecomfe","description":"A declarative, component based solution to track page views and user events with react \u0026 react-router","archived":false,"fork":false,"pushed_at":"2022-12-10T16:51:27.000Z","size":2337,"stargazers_count":70,"open_issues_count":29,"forks_count":16,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-25T08:05:20.536Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ecomfe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-19T03:58:31.000Z","updated_at":"2025-01-16T01:31:29.000Z","dependencies_parsed_at":"2023-01-14T21:07:30.702Z","dependency_job_id":null,"html_url":"https://github.com/ecomfe/react-track","commit_stats":null,"previous_names":["otakustay/react-track"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/ecomfe/react-track","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Freact-track","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Freact-track/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Freact-track/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Freact-track/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecomfe","download_url":"https://codeload.github.com/ecomfe/react-track/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Freact-track/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268392199,"owners_count":24243297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-02T12:42:48.759Z","updated_at":"2025-08-02T12:43:17.774Z","avatar_url":"https://github.com/ecomfe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-track\n\n基于React的声明式PV及用户行为采集框架。\n\n## 为什么自研\n\n在NPM上有若干个类似的包，但它们存在着一些缺陷，这其中主流的两类是：\n\n- [react-tracking](https://www.npmjs.com/package/react-tracking)：偏向于声明式，但使用HOC的形式限制了使用的场景，且通过拦截类方法而臧`props`来进行数据的采集，与React的数据流形式略有不和。\n- [react-tracker](https://www.npmjs.com/package/react-tracker)：采用类似`react-redux`的思想，使用`connect`和`Provider`的形式将功能联系起来。但是这种做法更偏向于命令式，从使用的角度来说繁琐之余也不易追踪。\n\n除此之外，这些包均没有提供PV采集的能力。而PV采集中，有一个非常关键的问题至今没有得到很好的解决：\n\n\u003e 当URL中包含参数时，如`/posts/123`与`/posts/456`在PV上会被认为是两个页面，但事实上它们对应的路由均是`/posts/:id`，是相同的。\n\n这一问题导致如果需要将包含参数的URL进一步的汇总与分组来更精确地计算“页面”的PV，则会需要额外的数据分析成本。因此我们希望从源头，即在数据采集的时候就解决这一问题，这也导致需要与`react-router`进行关联。\n\n我们的目标是：\n\n- 使用声明式的形式进行数据采集。\n- 与React的组件树结构进行整合，可在JSX中形象地表达。\n- 提供PV采集的能力，且**能够获取`react-router`的配置**。\n- 尽可能小的移除成本，当一个行为或页面PV不再需要采集时，可以用最简单的手段移除而不影响已有组件的逻辑。\n\n# 使用文档\n\n## 全局环境准备\n\n类似于`redux`或`react-router`，`react-track`需要一个全局的环境来定义数据的采集过程和数据的记录形式，这些环境由`Tracker`组件来定义。`Tracker`组件需要以下2个属性：\n\n- `{Function} collect`：定义如何采集和组装需要记录的数据。\n- `{Object} provider`：定义如何将数据记录下来或发送至指定服务。\n\n并且支持以下可选配置：\n\n- `{boolean} reportPageViewOnLeafOnly`：仅让作为叶子节点的`TrackRoute`组件报告PV，如果一个`TrackRoute`在`children`属性，则不作为叶子节点处理。该配置默认关闭。\n- `{boolean} warnNestedTrackRoute`：当一个`TrackRoute`在另一个`TrackRoute`里面时，在控制台打印一个警告信息，用于检查一些不符合预期的路由嵌套。该配置默认打开。\n\n以上2个属性也可以直接用于`TrackRoute`组件上，以覆盖`Tracker`组件配置的默认值。\n\n### 定义数据采集\n\n在`Tracker`组件中，`collect`属性用来定义“采集哪些数据”以及“数据的最终结构”。`connect`是一个函数，其签名如下：\n\n```typescript\ntype collectPageView = (type: 'pageView', location: Location) =\u003e object;\ntype collectEvent = (type: 'event') =\u003e object;\ntype collect = collectPageView | collectEvent;\n```\n\n`react-track`内置了几种常用的采集函数：\n\n- `browser()`：添加浏览器相关的信息，包括UA、分辨率、操作系统、浏览器版本、系统语言。这个采集仅在类型为`pageView`时才会生效。\n- `context(env)`：将固定的`env`对象放到采集数据中去，常用于添加当前登录用户名、系统名称、系统版本等信息。\n- `session(storageKey)`：跟踪一次用户的访问，为每一次访问生成一个唯一的标识，并存放在`sessionStorage`中，这个唯一标识会变为名为`session`的属性值。可以通过`storageKey`来自定义`sessionStorage`中对应的键名。\n- `basename(prefix)`：默认采集路径信息是基于`location.pathname`的，但它并不包含basename。所以，当history设置basename时，需要使用该collect指定basename才能采集准确的路径\n\n\n当希望同时使用多个`collect`函数时，可以通过`combineCollects`将它们组合成一个函数。以下代码展示了如何使用多个`collect`函数，并通过`combineCollects`将它们组合成一个：\n\n```js\nimport {combineCollects, browser, context, session} from '@ecomfe/react-track';\n\nconst app = {\n    name: 'My App',\n    version: '1.0.0',\n    branch: 'stable'\n};\n\nconst collect = combineCollects(\n    context(app),\n    browser(),\n    session()\n);\n```\n\n### 定义数据应用\n\n`Tracker`组件的`provider`属性用于控制采集到的数据如何使用，通常在生产环境我们会选择将其发送到指定的服务，如百度统计、Google Analysis等，在开发环境中则可以忽略或者显示在控制台中。\n\n`provider`是一个对象，其定义如下：\n\n```typescript\ntype PageViewData = {\n    location: Location,\n    referrer: Location,\n    [key: string]: any\n};\n\ntype EventData = {\n    category: string,\n    action: string,\n    label: string,\n    [key: string]: any\n};\n\ninterface TrackProvider {\n    install(): void;\n    uninstall(): void;\n    trackPageView(data: PageViewData): void;\n    trackEvent(data: EventData): void;\n}\n```\n\n通常使用`install`来做初始化的工作，`uninstall`进行清理，而`trackPageView` 和`trackEvent`则会在每一次PV或自定义事件数据采集完成后被调用。\n\n`react-track`同样内置了几个常用的处理器：\n\n- `holmes(site)`：封装了百度统计，接受对应的百度统计id。\n- `print()`：用于调试，通过控制台打印对应的数据。\n- `empty()` ：忽略所有的数据。\n\n与`collect`相似地，一个应用中我们可能会需要同时将数据进行多重处理，如既发送到百度统计，又打印在控制台中，此时可以使用`composeProvider`函数进行组装。以下代码自定义了一个`provider`用于将数据通过POST发送到指定的服务器，同时将2个处理器组合为一个，最后仅在生产环境才生效，开发环境仅打印在控制台：\n\n```js\nimport {holmes, print, composeProvider} from '@ecomfe/react-track';\nimport axios from 'axios';\n\nconst post = url =\u003e {\n    const send = type =\u003e data =\u003e axios.post(url, {type, ...data});\n\n    return {\n        install: noop,\n        uninstall: noop,\n        trackPageView: send('pageView'),\n        trackEvent: send('event')\n    };\n};\n\nconst trackProvider = process.env.NODE_ENV === 'production'\n\t? composeProvider(\n\t\tpost('http://127.88.88.88:8888/v1/log'),\n\t\tholmes(mySiteID)\n    )\n\t: print();\n```\n\n### 定义环境\n\n在有了`collect`和`provider`的定义后，将`Tracker`组件置于应用的最外层即可以完成全部环境的准备：\n\n```jsx\nimport {Tracker} from '@ecomfe/react-track';\nimport {BrowserRouter} from 'react-router';\nimport {App} from 'components';\n\n\u003cTracker collect={collect} provider={trackProvider}\u003e\n    \u003cBrowserRouter\u003e\n        \u003cApp /\u003e\n    \u003c/BrowserRouter\u003e\n\u003c/Tracker\u003e\n```\n\n## 采集PV\n\n对于Web应用，PV是数据采集中的必要信息。传统的PV采集会存在一个问题，假设我们有一个展示用户信息的页面，其路由是`/users/:username`，那么对于不同的用户，我们将会得到不同的URL，如`/users/Alice`和`/users/Bob`。在常见的PV采集方案中，采集工具仅对URL作出反应，因此在数据的统计中，我们会看到2个页面分别有n和m的访问量。\n\n但是在对应用系统的分析时，我们更希望得到这样一个信息：用户信息页被访问了多少次。然而问题是，在采集的数据中，要通过`/users/Alice`和`/users/Bob`去还原用户信息页被问题的问题（n + m）是相对困难的，当路由规则更加复杂时，甚至可能是无法实现的。\n\n为此，`react-track`在PV采集上，提供了将`/users/:username`这个URL模块也一并捕获的能力，使用`react-router`的定义，称之为`path`。由于`react-router 4.x`的特征，从全局顶层来获取`path`是不可能的，因此为了实现这一功能，使用`react-track`的系统不得不在代码上做出一些微小的改变。\n\n### 声明PV采集点\n\n`react-track`要求用户显式地声明需要采集PV信息的路由位置，提供了`TrackRoute`这一组件来进行声明。\n\n`TrackRoute`的功能与`react-router`的`Route`组件完全兼容，因此对于一个已经使用了`react-router`的系统，只需要在合适的位置将`\u003cRoute\u003e`修改为`\u003cTrackRoute\u003e`即可：\n\n```jsx\nimport {Switch, Route} from 'react-router-dom';\nimport {CommonHeader, AboutTab, Info, Contact} from 'components';\n\nconst App = () =\u003e (\n    \u003cdiv\u003e\n        \u003cCommonHeader /\u003e\n        \u003cSwitch\u003e\n    \t    \u003cTrackRoute exact path=\"/console\" component={Console} /\u003e\n\t        \u003cTrackRoute exact path=\"/service\" render={() =\u003e \u003cService /\u003e} /\u003e\n        \t\u003cRoute path=\"/about\" component={AboutMe}\u003e\n                \u003cAboutTab /\u003e\n                \u003cTrackRoute exact path=\"/about/info\" component={Info} /\u003e\n                \u003cTrackRoute exact path=\"/about/contact\" component={Contact} /\u003e\n            \u003c/Route\u003e\n        \u003c/Switch\u003e\n\t\u003c/div\u003e\n);\n```\n\n需要注意的一点是，`TrackRoute`就当仅应用在**最底层的路由**上。如上述代码中的`/about`这一级路由并不是最底层的，其下还有`/about/info`和`/about/contact`，如果将这一层的`\u003cRoute\u003e`改为`\u003cTrackRoute\u003e`的话，当访问`/about/info`时，由于上下两个路由都会触发PV采集，最终将会形成2条数据：\n\n- `{pathname: \"/about/info\", path: \"/about\"}`\n- `{pathname: \"/about/info\", path: \"/about/info\"}`\n\n它们的`pathname`是一样的，始终指向当前真实的URL，而`path`则不同，指向`\u003cTrackRoute\u003e`上的`path`属性。这会导致数据的重复。\n\n### 高阶组件\n\n同时`react-track`还提供了`trackPageView`高阶组件，可以将任意的组件声明为PV采集点。`trackRoute`并不会声明路由信息，因此还需要将组件放置在`\u003cRoute\u003e`下：\n\n```jsx\nimport {trackPageView} from 'react-track';\nimport {Route} from 'react-route';\n\nconst Console = () =\u003e (\n    \u003cdiv\u003e\n        ...\n    \u003c/div\u003e\n);\nconst ConsoleWithTrack = trackPageView(Console);\n\n\u003cRoute exact path=\"/console\" component={ConsoleWithTrack} /\u003e\n```\n\n## 采集事件\n\n通过事件采集可以分析用户的行为，帮助理解用户的真实需求并进行产品的改进。`react-track`提供了简单直接的采集方式，允许通过对事件回调类型的属性进行拦截来采集相关数据。\n\n在常见的自定义事件模型中，一个事件由`category`、`action` 和`label`三个属性组成。\n\n### 定义采集点\n\n`react-track`提供了`TrackEvent`组件，使用它包裹在对应组件的外层，并通过`eventPropName`指定需要拦截的事件名称，用`category`、`action`、`label`声明事件的相关信息。除以上4个属性外，其它的属性会透传给其子元素。\n\n子组件需要支持`eventPropName`对应的属性，且必须是函数类型。需要支持多个事件类型时可以嵌套使用：\n\n```jsx\nimport {TrackEvent} from '@ecomfe/react-track';\nimport {NavLink} from 'react-router-dom';\n\nconst NavItem = ({name, to}) =\u003e (\n    \u003cTrackEvent eventPropName=\"onMouseEnter\" category=\"navigation\" action=\"mouseEnter\" label={name}\u003e\n        \u003cTrackEvent eventPropName=\"onMouseLeave\" category=\"navigation\" action=\"mouseLeave\" label={name}\u003e\n            \u003cli\u003e\n                \u003cNavLink exact to={to}\u003e{name}\u003c/NavLink\u003e\n            \u003c/li\u003e\n        \u003c/TrackEvent\u003e\n    \u003c/TrackEvent\u003e\n);\n```\n\n### hooks\n\n```jsx\nconst Item = ({name, onClick}) =\u003e {\n    const trackEvent = useTrackEvent();\n    const handleClick = useCallback(\n        e =\u003e {\n            trackEvent({category: 'navigation', action: 'click', label: name});\n            onClick(e)\n        },\n        [name]\n    )\n    return \u003cButton onClick={handleClick}\u003eClick Me\u003c/Button\u003e\n};\n```\n\n### 高阶组件\n\n`react-track`同时提供了`trackEvent`高阶组件，用于直接在一个现有组件上添加事件采集的能力。在希望采集多个事件时，与`recompose`一起配合能取得更好的代码可读性：\n\n```jsx\nimport {trackEvent} from '@ecomfe/react-track';\nimport {NavLink} from 'react-router-dom';\nimport {compose} from 'recompose';\n\nconst NavItem = ({name, to}) =\u003e (\n    \u003cli\u003e\n    \t\u003cNavLink exact to={to}\u003e{name}\u003c/NavLink\u003e\n    \u003c/li\u003e\n);\n\nconst track = action =\u003e {\n    const options = {\n        eventPropName: 'on' + action[0].toUpperCase() + action.slice(1),\n        category: 'navigation',\n        action: action,\n        label: null\n    };\n\n    return trackEvent(options);\n};\n\nconst enhance = compose(\n    track('mouseEnter'),\n    track('mouseLeave')\n);\n\nexport default enhance(NavLink);\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Freact-track","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecomfe%2Freact-track","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Freact-track/lists"}