Skip to content
  1. 拉取触点配置(规则层) 系统启动后先做的是拿“触发规则”,不是拿展示内容。规则来源有两条路径: 缓存路径:如果配置了本地存储 key,优先读本地缓存 读取后校验版本号和结构合法性 校验通过直接进入后续流程 实时路径:缓存不存在/不合法时,调用配置接口实时获取 配置返回的核心信息: 触点列表(每个触点有页面标识、触点标识、条件列表) 策略(终止型 or 覆盖型) 这里的“触点”本质是一个规则单元:当它自己的条件列表全部满足时,才有资格进入内容请求阶段。
  2. 过滤页面触点(作用域收敛) 拿到全量配置后,不会全跑。先按当前页面标识过滤: 只保留“属于当前页面”的触点 存入“待检测触点池” 这一步目的很明确: 降低判定开销 避免跨页面触点误触发 让页面内判定逻辑只围绕本页规则运行
  3. 初始化触发器(事件源挂载) 过滤后,系统会把触点条件拆成可监听的触发源,当前主要两类: 3.1 停留时长条件(timeout) 本地维护一个定时器,定时更新当前停留时长状态 每次更新时触发统一判定入口 3.2 滚动停止条件(scroll_stop) 页面滚动结束时触发 对惯性滑动做区分:若仍有惯性速度,延迟一小段时间再触发(app端可以判断是否有滚动惯性) 触发后进入统一判定入口,标记滚动停止已达成 关键点:timeout 和 scroll_stop 只是“触发源”,真正判定都走同一个条件引擎入口。
  4. 满足状态表(运行时状态) 系统维护一份“已满足状态表”,用于跨事件累积条件。比如: scroll_stop 是否已发生 当前已达到的 timeout 阈值是多少 每次事件发生,只是“更新状态 + 触发重判”,而不是一次性拍脑袋决定展示。 这使得条件顺序无关: 先滚动后到时长,能触发 先到时长后滚动,也能触发
  5. 条件判定(每个触点做 AND/every) 统一判定入口每次执行时都会: 更新满足状态表(写入本次事件状态) 根据策略先做短路判断(如终止型且已展示过则直接返回) 遍历待检测触点池 对每个触点的条件列表做“全部满足”判断(every/AND) 滚动条件:看标记是否存在 时长条件:看当前达成值是否 >= 配置阈值 (记录已经在当前页面停留多久,本地维护一个state,然后定时更新,每次更新的时候checkConditions) 收集命中触点列表 只有“单触点内所有条件都满足”才算命中,不是任一条件满足。
  6. 命中触点消费(防重复的第一层) 当某次判定得到命中列表后: 这些触点会立即从待检测触点池移除 然后再发起内容请求 “先移除后请求”很重要: 防止同一触点被后续事件重复命中 降低重复请求概率
  7. 请求触点实际数据(内容层) 命中后才请求展示内容接口,入参是命中触点标识集合。后端在这个阶段做真正“重逻辑”: 频控判断(是否允许展示) 业务数据判断(该触点当前是否有效) 返回可展示内容(文案、样式、方向、时长、跳转等) 前端拿到结果后选择展示对象并渲染。
  8. 展示策略与并发防重(两层保险) 系统支持两种策略: 终止型:页面展示一次后,不再处理后续触点 覆盖型:后续命中可继续展示(允许覆盖) 在终止型下,做了双层防竞态: 请求前拦截:若已展示,后续命中不再发请求 响应后拦截:即使并发请求已发出,后返回者也会被丢弃 这样可以避免“几乎同时命中导致多次展示”的问题。
  9. 页面事件联动(滚动/离开) 触点系统本身并不直接拥有页面原生事件,因此通过接入层桥接: 页面滚动结束 -> 通知触点系统进行 scroll_stop 判定 页面离开 -> 主动关闭当前展示 并且桥接方式是“先执行业务原逻辑,再执行触点补充逻辑”,尽量不破坏原页面行为。
  10. 生命周期清理(防脏状态) 页面卸载时会统一清理: 待检测触点池 满足状态表 所有 timeout 定时器 已展示标记、当前展示标识、自动关闭定时器 确保下次进入页面从干净状态重新开始,避免跨次污染。
  11. 实现方法 系统提供了一个核心容器 TouchPointProvider。在原有页面外层进行包裹,即可零侵入地注入触点逻辑和弹窗能力。

11.1 内部实现原理

TouchPointProvider 本质上是一个高阶组件容器,它通过渲染 props.children 来保持原有业务页面的渲染,并同步挂载一个独立的渲染引擎节点。

jsx
// TouchPointProvider 伪代码
const TouchPointProvider = ({ children, pageId }) => {
  return (
    <TouchContext.Provider value={{ activeData }}>
      {/* 渲染原业务页面:保持其原有层级和生命周期 */}
      {children}

      {/* 触点展示层:与业务页面在 DOM 上属于同级或通过 Portal 挂载 */}
      <TouchPointModal />
    </TouchContext.Provider>
  );
};

11.2 挂载方式

开发者只需在页面入口处包裹一次,即可激活功能:

jsx
// 业务使用方
<TouchPointProvider pageId="HOME_PAGE">
  <HomePage />
</TouchPointProvider>