React 与 Vue 集成

Driver.js 不依赖框架。集成到 React 或 Vue 时,重点不是 API 本身,而是处理好组件生命周期、DOM 渲染时机和清理逻辑。

React 示例

React 中通常在 useEffect 里创建实例,并在组件卸载时销毁。

jsx
import { useEffect, useRef } from "react";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";

export function DashboardHelp() {
  const createButtonRef = useRef(null);

  useEffect(() => {
    const driverObj = driver({
      showProgress: true,
      steps: [
        {
          element: createButtonRef.current,
          popover: {
            title: "创建项目",
            description: "点击这里创建你的第一个项目。",
          },
        },
        {
          element: "#project-list",
          popover: {
            title: "项目列表",
            description: "所有项目都会显示在这里。",
          },
        },
      ],
    });

    return () => {
      driverObj.destroy();
    };
  }, []);

  return <button ref={createButtonRef}>创建项目</button>;
}

如果实例只在点击帮助按钮时使用,也可以把创建逻辑放到事件处理函数中。

React 点击启动

jsx
import { driver } from "driver.js";
import "driver.js/dist/driver.css";

const steps = [
  {
    element: "#create-project",
    popover: {
      title: "创建项目",
      description: "从这里开始创建项目。",
    },
  },
];

export function HelpButton() {
  function startTour() {
    const driverObj = driver({ steps });
    driverObj.drive();
  }

  return <button onClick={startTour}>页面引导</button>;
}

这种写法简单,但如果导览过程中路由切换,仍然需要在路由层处理清理。

Vue 示例

Vue 3 中可以在 onMounted 后访问 DOM,在 onBeforeUnmount 中销毁实例。

vue
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";

const createButton = ref(null);
let driverObj;

onMounted(() => {
  driverObj = driver({
    showProgress: true,
    steps: [
      {
        element: createButton.value,
        popover: {
          title: "创建项目",
          description: "点击这里创建你的第一个项目。",
        },
      },
      {
        element: "#project-list",
        popover: {
          title: "项目列表",
          description: "这里展示你的项目。",
        },
      },
    ],
  });
});

onBeforeUnmount(() => {
  driverObj?.destroy();
});

function startTour() {
  driverObj?.drive();
}
</script>

<template>
  <button ref="createButton">创建项目</button>
  <button @click="startTour">页面引导</button>
</template>

路由切换清理

单页应用中,导览可能跨页面或被路由打断。建议在路由切换前销毁当前实例:

js
router.beforeEach(() => {
  activeDriver?.destroy();
  activeDriver = null;
});

如果导览确实需要跨页面,最好拆成多个页面内导览,而不是让一个 tour 依赖多次路由跳转。

封装建议

中大型项目可以封装一个 startTour(name) 方法:

js
const tours = {
  dashboard: [
    {
      element: "#create-project",
      popover: {
        title: "创建项目",
        description: "从这里创建项目。",
      },
    },
  ],
};

let activeDriver;

export function startTour(name) {
  activeDriver?.destroy();

  activeDriver = driver({
    showProgress: true,
    steps: tours[name] || [],
  });

  activeDriver.drive();
}

这样可以统一处理中文按钮、埋点、已读状态和销毁逻辑。