React 高阶组件实现埋点
功能需求:
老板希望可以看到,我们做的系统的访问量,以及各个功能模块的被点击次数。
相关技术:
高阶组件和普通组件的区别:
定义和用途
- 普通组件:
- 定义:普通组件可以是函数组件或类组件,用于定义UI结构和交互逻辑。
- 用途:主要用于渲染UI,展示数据和处理用户交互。
- 高阶组件(HOC):
- 定义:高阶组件是一个函数,接受一个组件并返回一个新的组件。
- 用途:用于复用组件逻辑,将公共的功能封装起来,可以增强或修改传入组件的行为。
代码示例
- 普通组件:
jsx复制代码// 函数组件
const MyComponent = (props) => {
return <div>Hello, {props.name}!</div>;
};
// 类组件
class MyComponent extends React.Component {
render() {
return <div>Hello, {this.props.name}!</div>;
}
}
- 高阶组件(HOC):
jsx复制代码// 高阶组件
const withExtraProps = (WrappedComponent) => {
return (props) => {
return <WrappedComponent {...props} extraProp="I am an extra prop!" />;
};
};
// 使用高阶组件增强普通组件
const EnhancedComponent = withExtraProps(MyComponent);
// 渲染EnhancedComponent将会渲染MyComponent并传入额外的属性
<EnhancedComponent name="World" />;
使用方式:
- 普通组件:直接定义并使用,可以在任何地方直接渲染。
- 高阶组件:通过传入一个组件并返回一个新的组件来使用,通常用于为现有组件添加功能。
抽象级别:
- 普通组件:关注具体的UI和交互。
- 高阶组件:关注逻辑复用和功能增强,通常不会直接定义UI。
渲染:
- 普通组件:直接渲染UI。
- 高阶组件:返回一个包裹了原组件的新组件,并在新组件中添加逻辑。
为什么要用高阶组件
- 逻辑复用:可以将公共逻辑抽取出来,避免在多个组件中重复代码。
- 解耦:将特定功能与组件的核心逻辑分离,使代码更清晰、更易维护。
- 增强组件:可以在不修改原组件的情况下,增强其功能。
实现埋点功能
实现的核心代码逻辑如下所示:
_addEventDot
函数主要实现的是打点的逻辑。这个逻辑也不复杂,就是获取相关的事件名称和用户信息,传给后端即可。
再看下面代码,可以看到里面的内容实现了两次return:
- 由
WithInnerPlayer
组件return 出来的<WrappedComponent/>
- 由
WithTracking
组件return出来的WithInnerPlayer
让我们来具体解释一下这两层 return
的作用:
- 内部组件 (
WithInnerPlayer
):- 这个内部组件是被
WithTracking
HOC 创建的新组件,它接收原始组件 (WrappedComponent
) 的所有属性,并添加了一些额外的功能(在这个例子中是追踪功能)。 WithInnerPlayer
组件的主要职责是接受来自外部的属性 (props
),并将其与新添加的功能一起传递给WrappedComponent
。
- 这个内部组件是被
- 外部 HOC (
WithTracking
):- 这个 HOC 是一个工厂函数,它接收一个组件 (
WrappedComponent
) 并返回一个新的组件 (WithInnerPlayer
)。 - 当
WithTracking
被调用时,它会创建并返回WithInnerPlayer
,这个内部组件已经包含了追踪功能。
- 这个 HOC 是一个工厂函数,它接收一个组件 (
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
import moment from 'moment/moment';
import { addEventDot } from '@/services/api';
import { _getPhoneModel } from '@/utils/utils';
import CONFIG from 'GlobalConfigFile';
const DEFAULT_SERVER = process.env.DEFAULT_SERVER || CONFIG.Server;
const WithTracking = (WrappedComponent) => {
const WithInnerPlayer = ({ ...props }) => {
const { name } = WrappedComponent;
const ua = _getPhoneModel();
const _addEventDot = (params) => {
const uuid = uuidv4();
const trackingParams = {
id: uuid,
source: 'browser',
occurOn: moment().unix() * 1000,
project: 'EAP',
env: DEFAULT_SERVER,
deviceId: ua,
...params,
name: params?.name || name,
};
addEventDot(trackingParams)
};
return <WrappedComponent handleAddEventDotClick={_addEventDot} {...props} />;
};
return WithInnerPlayer;
};
export default WithTracking;
如果只返回一层组件WithInnerPlayer
会怎么样?
这种做法并不符合 HOC 的常规使用方式。在标准的 HOC 设计模式中,您希望返回一个增强过的组件,该组件保留了原始组件的所有特性,同时还添加了新的功能。
使用高阶组件
import MyComponent from './MyComponent';
import WithTracking from './WithTracking';
const TrackedMyComponent = WithTracking(MyComponent);
// 在其他组件中使用 TrackedMyComponent
function App() {
return <TrackedMyComponent />;
}
或者如果有自定义的打点参数要传的话:
import WithTracking from './WithTracking';
function TrackedMyComponent(props) {
const { handleAddEventDotClick } = props;
...
handleAddEventDotClick({
name: 'visit:consult/appointment',
props: {
eventName: '咨询:访问预约(客服)'
}
});
...
}
export default WithTracking(TrackedMyComponent);