# Hooks 与事件

URL: https://caijiao.org/driver-js/03-advanced/02-hooks
Source: docs/driver-js/03-advanced/02-hooks.md
Description: 讲解 Driver.js 的生命周期 hooks，用于统计埋点、步骤切换、权限校验、异步保存和销毁清理。

Driver.js 提供生命周期 hooks，让你在导览开始、步骤切换、弹层渲染、关闭销毁等时机执行业务逻辑。

Hooks 适合处理四类事情：

- 记录用户是否看过引导。
- 上报步骤浏览和完成埋点。
- 在进入下一步前保存表单或打开面板。
- 在路由切换、权限变化时清理导览。

## 常见 hooks

```js
const driverObj = driver({
  onHighlightStarted: (element, step) => {
    console.log("开始高亮", element, step);
  },
  onHighlighted: (element, step) => {
    console.log("高亮完成", element, step);
  },
  onDeselected: (element, step) => {
    console.log("离开当前步骤", element, step);
  },
  onDestroyed: () => {
    console.log("导览已结束或被关闭");
  },
  steps: [
    {
      element: "#create-project",
      popover: {
        title: "创建项目",
        description: "从这里开始创建项目。",
      },
    },
  ],
});
```

不同 hooks 的参数会随触发时机不同而变化。实际编码时建议参考编辑器类型提示和官方 API 文档。

## 上报导览完成

```js
const TOUR_KEY = "dashboard-tour-shown";

const driverObj = driver({
  onDestroyed: () => {
    localStorage.setItem(TOUR_KEY, "1");
    analytics.track("dashboard_tour_closed");
  },
  steps: tourSteps,
});
```

如果要区分“看完”和“中途关闭”，可以结合当前步骤索引判断。

```js
const driverObj = driver({
  onDestroyed: () => {
    const state = driverObj.getState();
    const completed = state.activeIndex === tourSteps.length - 1;

    analytics.track("dashboard_tour_end", {
      completed,
      activeIndex: state.activeIndex,
    });
  },
  steps: tourSteps,
});
```

## 自定义下一步行为

有时下一步之前要先展开菜单、切换标签页或保存状态。可以在按钮 hook 中接管默认行为：

```js
const driverObj = driver({
  steps: [
    {
      element: "#settings-button",
      popover: {
        title: "打开设置",
        description: "先打开设置面板，再查看下一步。",
        onNextClick: async () => {
          await openSettingsPanel();
          driverObj.moveNext();
        },
      },
    },
    {
      element: "#settings-panel",
      popover: {
        title: "设置面板",
        description: "这里可以调整团队偏好。",
      },
    },
  ],
});
```

如果接管了按钮行为，需要自己调用 `moveNext()` 或 `movePrevious()`，否则引导不会继续。

## 按步骤设置 hooks

除了全局 hooks，也可以在某个 step 上设置局部逻辑：

```js
const driverObj = driver({
  steps: [
    {
      element: "#advanced-menu",
      onHighlightStarted: () => {
        document.querySelector("#sidebar").classList.add("expanded");
      },
      popover: {
        title: "高级菜单",
        description: "展开后可以看到更多配置项。",
      },
    },
  ],
});
```

局部 hooks 适合处理某一步特有的 DOM 状态。

## 清理副作用

如果 hook 中修改了页面状态，记得在导览结束时恢复：

```js
const sidebar = document.querySelector("#sidebar");

const driverObj = driver({
  onDestroyed: () => {
    sidebar?.classList.remove("expanded");
  },
  steps: [
    {
      element: "#advanced-menu",
      onHighlightStarted: () => {
        sidebar?.classList.add("expanded");
      },
      popover: {
        title: "高级菜单",
        description: "这里包含更多配置项。",
      },
    },
  ],
});
```

Hooks 很强，但不要把复杂业务流程都塞进导览。导览应该说明界面，而不是替代界面本身。
