关于前端:HMR系列一-API

7次阅读

共计 3661 个字符,预计需要花费 10 分钟才能阅读完成。

What(HMR 是什么?)

咱们上面探讨的 HMR 都是基于 vite 本身实现的一套 HMR 零碎。
vite实现的 HMR 是依据 ESM HMR 标准 来实现的。

HMRHot Module Reload模块热更新。
之前当咱们在编辑器中更新代码时,会触发浏览器的页面刷新,然而这个刷新是 全量刷新 ,相当于CMD+R。这时页面的状态会被重置掉,总之体验不好。
而模块热更新就是为了解决这样的问题,只是刷新咱们编辑的代码所对应的模块,并且能放弃页面的状态。

能够看到这里咱们在编辑代码时,上面 count 的状态是保留了的。只是热更新了下面的文字局部的模块。

Why(为什么须要 HMR?)

其实每个技术的诞生,都是为了解决之前所凸显进去的问题。HMR 也是如此,其实在下面也曾经说了起因。
这里再来总结一下:为什么须要 HMR?

  1. 解决批改代码后页面 全量更新,体验不好的问题
  2. 解决全量更新导致的 状态失落 问题

How(怎么应用 HMR?)

vite中实现的 HMR 零碎其实是对 ESM HMR 标准中的 API 进行了一层封装。vite会被动监听文件的变动,而后触发对应的 API,来实现模块的热更新。
所以首先咱们来简略理解一下这套标准中的API

API

hmrAPI 都注入到了 import.metahot中。
咱们拜访的时候只须要 import.meta.hot.[name] 即可

import.meta是浏览器中内置的一个对象。【MDN】

interface ImportMeta {
  readonly hot?: {
    readonly data: any
    // ====== 触发更新 =====
    accept(): void //
    accept(cb: (mod: any) => void): void
    accept(dep: string, cb: (mod: any) => void): void
    accept(deps: string[], cb: (mods: any[]) => void): void
    // ==================
    prune(cb: () => void): void
    dispose(cb: (data: any) => void): void
    decline(): void
    invalidate(): void
    // ===== 监听 hmr 事件 ====
    on(event: string, cb: (...args: any[]) => void): void
    // ===================
  }
}

accept(cb)

accept翻译过去就是承受。而在 hmr 中他也是这个意思:承受此次热更新,而承受热更新的模块被称为 HMR 边界
当咱们在文件中退出这行代码的时候,就是手动开启该文件模块的热更新。
当这个文件中的代码产生更新时,就会接管此次热更新的后果。

if (import.meta.hot) {import.meta.hot.accept((mod) => {console.log(mod, '==')
  })
}

:::danger
accept中的 mod 就是更新之后的模块中所导出的内容。
:::
比方咱们的文件是上面这样,导出了 renderother

export const render = () => {// ...}

export const other = () => {//...}

if (import.meta.hot) {import.meta.hot.accept((mod) => {console.log(mod, '==')
  })
}

那么当咱们在这个文件中更新代码,承受热更新时此时 mod 中就是:

如果咱们须要承受其中一个导出模块的更新,那么间接调用 mod.render() 或者 mod.other() 即可在页面上更新到最新的内容。

如果你的文件中导出形式是默认导出 export default xxx,那么mod 中就是mod.default

在下面的代码中,咱们是向 accept 中传递了一个回调函数来被动触发热更新模块中的函数。因为咱们这个文件中只是申明了 renderother 函数,并没有执行,所以须要在 accpet 的回调中手动触发才能够
其实有些状况下也不必传回调函数。accept会把以后变更的文件中的最新内容执行一遍。就比方咱们这个文件就是一个可执行文件(相似自执行函数),当咱们 import 这个文件的时候,文件里的代码就会执行,例如上面的状况:

// render.ts
const render = () => {const app = document.querySelector<HTMLDivElement>('#app')!
  app.innerHTML = `
    <h1>Hello Vite12</h1>
    <p id="p"> 是是是 </p>
  `
}


render()

if (import.meta.hot) {import.meta.hot.accept()
}

// main.ts
import './render.tx'’

render 文件执行执行了 render 函数,这时 accept 就会从新执行这个文件,也就天经地义的触发了 render 函数。这时就不须要咱们向 accpet 传递回调函数了。

accept(dep, cb)

accept办法中也能够接管一个 dep 参数,也就是以后页面热更新时所依赖的 子模块的门路
这个 dep 参数,能够是一个独自字符串,也能够是一个字符串数组,当是数组时阐明 依赖多个子模块

//main.ts
import {render} from './render'
import {initsate} from './state'

render()
initsate()

if (import.meta.hot) {import.meta.hot.accept('./render.ts', (mod) => {console.log(mod, '==')
    mod?.render()})
}

main模块依赖 render 文件
render文件变更时,会接管热更新
因为此时没有依赖 state 文件,所以当 state 文件产生变更时会 **reload page**,而不会热更新。
因为此时热更新的边界仅仅是 render 模块,只有 render 模块中的变更才会触发 main 的热更新

//main.ts
import {render} from './render'
import {initsate} from './state'

render()
initsate()

if (import.meta.hot) {import.meta.hot.accept(['./render.ts', './state.ts'], ([mod1, mod2]) => {console.log(mod1, mod2, '==')
    mod1?.render()
    mod2?.initsate()})
}

这时,当 state 模块中的文件发生变化时,就也会触发 main 的热更新了。
此时,回调函数中的 mod 为:(因为仅仅变更了 state 模块,所以 mod1undefined,也就阐明 render 模块没有更新,合乎预期。

dispose()

这个函数就是比较简单。就是在 新模块更新前 旧模块销毁时 的钩子。用来清理掉旧模块中的一些副作用。

const timerId = setInterval(() => {countEle.innerText = Number(countEle.innerText) + 1 + ''
}, 1000)

if (import.meta.hot) {import.meta.hot.dispose((data) => {
    // 清理副作用
    clearInterval(timerId)
  })
}

在咱们须要 hmr 的模块中如果有定时器之类的操作,咱们热更新后如果不提前销毁定时器,就会反复执行定时,那么可能会呈现意想不到成果。

on(event,cb)

监听 自定义 HMR 事件
自定义 HMR 事件,是在服务端定义发送的。在 vite 中,咱们能够在插件中实现这件事。
vite插件中提供了handleHotUpdate

// vite-plugin.tx
// 省略其余代码
handleHotUpdate({server}) {
  server.ws.send({
    type: 'custom',
    event: 'xxx-file-change', // 自定义事件名称
    data: {} // 携带的信息})
  return []}

// client
 import.meta.hot.on('xxx-file-change', (payload) => {console.log(payload)
})

https://github.com/sanyuan0704/island.js/pull/79
有时自定义 hmr 事件,没有触发页面更新。咱们能够利用监听自定义事件,来被动触发页面的rerender

data

该属性用来共享 同一个模块中 更新前后的数据。
在这外面绑定的数据,不会被 hmr 影响或重置。

import.meta.hot.data.count = 1

decline()

示意此模块不可热更新,如果在流传 HMR 更新时遇到此模块,浏览器应该执行 齐全从新加载

invalidate()

从新加载页面。

下回书

hmr 的具体执行流程请看系列的第二篇文章

正文完
 0