vue3入门50 - Vite 进阶 - HMR-API 详细解析

140 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情

自动查找接收模块的更新

// main.js
import "./style.css";

import { render } from "./renderA";

render();

// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  import.meta.hot.accept();
}
// renderA.js
export function render() {
  document.querySelector("#app").innerHTML = `
  <h1>Hello Vite!</h1>
  <a href="https://vitejs.dev/guide/features.html" target="_blank">
    Documentation
  </a>
`;
}
  • 当我们修改了 renderA 模块,vite 会自动查找我们引入的模块是否有更新,有更新会执行模块中被调用方法
  • 当我们修改了 renderA 模块中的 render 方法,由于在 main.js 中被引用,vite 检测到了 renderA 更新,main.js 会重新执行 render 方法

只更新我们想更新的模块

// main.js
import "./style.css";

import { render } from "./renderA";

render();

// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(['./style.css']);
}
  • 当我们 accept 函数中传入了数组,指定的引用更新时会进行热更新,没有指定的引用更新时会进行页面刷新
  • 当我们 修改了 renderA 模块,由于我们没有指定处理这个模块的热更新,所以页面会刷新

处理我们想更新的模块

// main.js
import "./style.css";

import { render } from "./renderA";

render();

// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(["./renderA"], ([newA]) => {
    newA.render(); // 读取代码中的方法
  });
}
  • 如果 accept 中只接收一个数组,那这个时候只会接受这个数组引用模块的更新,文件本身不会进行更新,需要在文件本身进行更新的处理
  • accept(["./renderA"] 接受了 renderA 模块的更新,但是 main.js 本身不会更新,需要通过回调来处理更新

处理 side effect

问题

如果我们不是通过暴露函数,通过外面引用执行,而是直接在模块中执行某些行为,就会产生副作用,造成不可控制,内存溢出等等

// renderA.js
export function render() {
  document.querySelector("#app").innerHTML = `
  <h1>Hello Vite!</h1>
  <a href="https://vitejs.dev/guide/features.html" target="_blank">
    Documentation
  </a>
`;
}

let index = 1;
// side effet
setInterval(() => {
  console.log(index++);
}, 1000);
  • 我们在 renderA 模块中定义了定时器,在 main.js 中引用这个模块,就会自动执行定时器,导致我们无法控制此定时器

处理

在模块中监听模块的卸载

let index = 0;
// side effet
const timer = setInterval(() => {
  console.log(index++);
}, 1000);

if (import.meta.hot) {
  // 当模块被卸载时
  import.meta.hot.dispose(() => {
    if (timer) clearInterval(timer);
  });
}

保留当前状态

问题

当我们修改代码时,不想让我们操作过的数据重新开始,就需要保留状态

let timer = null;
let index = 0;
export function render() {
  timer = setInterval(() => {
    index++;
    document.querySelector("#app").innerHTML = `
      <h1>Hello Vite!</h1>
      <a href="https://vitejs.dev/guide/features.html" target="_blank">
        documentation${index}
      </a>
    `;
  }, 1000);
}

if (import.meta.hot) {
  // 当模块被卸载时
  import.meta.hot.dispose(() => {
    if (timer) clearInterval(timer);
  });
}

  • 每次页面刷新,index 都会被重置为 0 的状态,如果我们想保留 index 值,就需要做一些保留状态的处理

处理

import.meta.hot.data 保存同一个模块中,热更新之前的状态

let timer = null;
// 需要判断是否有cache和getIndex
let index = import.meta.hot.data?.cache?.getIndex() ?? 0;

export function render() {
  timer = setInterval(() => {
    index++;
    document.querySelector("#app").innerHTML = `
      <h1>Hello Vite!</h1>
      <a href="https://vitejs.dev/guide/features.html" target="_blank">
        11Documentation${index}
      </a>
    `;
  }, 1000);
}

if (import.meta.hot) {
  // data本身无法修改,只能在data中添加内容
  // import.meta.hot.data.index = index; 直接赋值是个原始类型,初始化时赋值为0,后面一直为0
  import.meta.hot.data.cache = {
    // 赋值对象时一个引用类型
    getIndex() {
      return index;
    },
  };
  // 当模块被卸载时
  import.meta.hot.dispose(() => {
    if (timer) clearInterval(timer);
  });
}

主模块 热更新和强制刷新

// main.js
import "./style.css";

import { render } from "./renderA";

render();

// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(["./renderA"], ([newA]) => {
    // console.log(import.meta.hot.data);
    newA.render(); // 读取代码中的方法
  });
}
  • 此时我们在 main 中修改代码,是会刷新页面的,如果想让 main 也保持热更新,需要做如下处理
// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(["./renderA"], ([newA]) => {
    // console.log(import.meta.hot.data);
    newA.render(); // 读取代码中的方法
  });
  import.meta.hot.accept(() => {}); // 保证 main.js 修改时,不重新刷新页面,也进行热更新
}
  • 如果想让 main 页面强制刷新,可以使用如下方法
import.meta.hot.decline()
// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(["./renderA"], ([newA]) => {
    // console.log(import.meta.hot.data);
    newA.render(); // 读取代码中的方法
  });
  // import.meta.hot.accept(() => {}); // 保证 main.js 修改时,不重新刷新页面,也进行热更新
  import.meta.hot.decline();
}

强制在 accept 之后,浏览器刷新

// 判断是否有 hot,因为生产版本没有 hot
if (import.meta.hot) {
  // 如果模块没有被热更新接受,就会走刷新页面
  import.meta.hot.accept(["./renderA"], ([newA]) => {
    // console.log(import.meta.hot.data);
    if (newA.index > 15) { // 如果大于 5
      import.meta.hot.invalidate(); // 强制在 accept 之后,浏览器刷新
    } else {
      newA.render(); // 读取代码中的方法
    }
  });
  import.meta.hot.accept(() => {}); // 保证 main.js 修改时,不重新刷新页面,也进行热更新
  // import.meta.hot.decline();
}