分类
前端工具链

近期帮一个朋友做的 Vue 网站优化方案

前几天有个朋友找到我,说他们公司的网站产品打开速度不太理想,加载的数据量很大,想优化一下。并且询问我,是不是用微前端会好一些。

分析

我看了一下,大概有几个点:

  1. 他们是做自动化运维的,提供多个工具,所有工具都是独立的 Vue 项目
  2. 用户一般先登录 dashboard 页面,然后导航到具体的工具页
  3. 因为每个工具都独立开发、独立打包,所以每个页面都会用到不同的 app.[hash].jschunk.vendors.[hash].js
  4. 于是,用户每次切换工具,都要完整加载一遍公共资源,如 vue 全家桶和 antdv
  5. 已经对一些模块做了 lazy-loading

于是我得出一些结论:

  1. 首先,因为所有项目的技术栈高度趋同,所以并不需要微前端(微前端的解释在后面)
  2. 现在重复加载最多的应该是 vue 全家桶和 antdv,重复加载的原因是没有拆包。适当拆分打包后资源,应该可以大大提高代码复用率,减轻不同项目间的切换成本
  3. 同时还应开启 http/2,提高连接复用率

方案

于是我给朋友提出了以下改进方案:

  1. 整站启用 http/2
  2. 所有项目手动分 chunk
    1. vue 全家桶
    2. antdv
    3. 其它好统计的、全站都在使用的仓库
    4. 其它仓库
  3. 统一所有项目的依赖,提交 lock 文件入库
  4. 所有项目对公共仓库的引用顺序需保持一致,保证 webpack 打包之后的序号能维持一致
  5. 替换页面中的资源位置,指向同一个资源
  6. 延长资源缓存时间,提高利用率

下一步

他们需要一些时间来消化和实施这些方案,所以这一次咨询先到这里。

接下来,我可能会建议他们调整支持的浏览器、增加 ESM build。以及使用 npm workspace 创建一个核心项目,把所有工具项目放到一起构建,减少前面的(2)(4)(5)环节。

总结和广告

希望上述方案对大家也有启发和帮助。

顺便帮他们打个广告吧:

OpsAny,云原生场景下的智能化运维平台。我们倡导“以资源为中心”和“以应用为中心”相融合的运维理念,提高运维效率、保障业务连续性。

OpsAny, make ops perfect
分类
vue

Vue3 < script setup > + TypeScript 笔记

近期把一个老项目(Vue 组件库)从 Options API 风格重构成了 <script setup> 风格 + TypeScript,写篇博客记录下坑和心得。

注:这篇博客只记录超出我踩过坑和不熟悉的内容,符合直觉、看了文档写完一次过的部分,我就不记录了。

0. Composition API 与 <script setup>

Vue 3 给我们带来了 Composition API,改变了之前 Options API 使用 mixins 继承和扩展组件的开发模式。这种模式让前端在开发时复用组件、改进组件变得更容易。具体改进我在本文里不再详述,大家可以看下 Vue master 的视频教程:Why the Composition API

之后尤大又提出了 script-setup rfc,进一步改进了使用 Composition API 的体验。简单总结下新提案:

  1. 使用 <script setup> 取代 setup(),里面定义的所有变量,包括函数,都可以直接在模版中使用,不需要手动 return
  2. 可以使用顶级 await
  3. <script setup> 可以与其它 <script> 在同一个 SFC 里共存

实际体验之后,我觉得这个提案的确能节省不少代码,不过也会增加一些复杂度。

1. 导出 name 等非模版属性

<script setup>setup() 的语法糖,主要目的是降低 composition API 的代码复杂度。它直接向模板暴露整个 <script setup> 的上下文,所有定义过的变量自动导出供模版使用,这样我们就不需要手动一个一个 return 了。

所以它就不适合导出其它非模版属性,比如组件名称 name。如有需要,可添加一个普通 <script> 节点,用常见的 export default 导出初始化对象。如下:

<script>
export default {
  name: 'SomeVue3Component',
};
</script>

<script setup>
import {ref} from 'vue';

const foo = ref('bar');
</script>

2. defineProps/defineEmits

要在 <script setup> 里使用 props(传入参数)和 emit(广播事件),需要使用 defineProps()defineEmits() 定义。但要注意,这俩东西其实是编译宏,并非真实函数,不能把它们当作函数来使用。所以也不需要 import,当它们是全局函数直接用就好。同时记得修改 .eslintrc.js 把它们添加到 global 里。

最简单的使用方法如下,以前的 props 定义规则可以沿用。

const props = defineProps({
  foo : {
    type: String,
    default: 'bar',
  },
});
const emit = defineEmits(['change']);

要配合 TypeScript,通常需要使用 withDefaults()props 生成默认值。这也是个宏,不能当函数用,也不能使用当前环境里的变量。改造后的代码如下:

interface Props = {
  foo: string;
};
const props = withDefaults(defineProps<Props>(), {
  foo: 'bar', // 'bar' 不能是变量
});
const {
  foo,
} = toRefs(props);

const emit = defineEmits<{
  (e:'change', value:string),
}>();

这个地方的设计相当不完善,我不知道 Vue 团队会如何改进这里。比如,我们不能使继承 interface,然后再初始化 props,因为继承是常规 ts 语句,而 defineProps 是编译宏,两者的工作环境不一样。而因为无法使用变量,我们也无法将父级组件的 props 混入本地 props。于是复用组件又变得麻烦起来。

耐心等待吧。

3. 使用 undefined 初始化对象

定义 props 问题真不少。有一些参数是可选参数,不一定要定义,也不一定要用到;但是使用 ts 定义时,即使如此,也要初始化他们,可以传值为 undefined,不然 tsc 可能会报告错误:变量可能为空。之所以用 undefined,而不是 null,则是因为 TypeScript 认为 null 是独立类型。

即:

interface Props {
  foo?: string;
}
const props = withDefaults(defineProps<Props>(), {
  foo: undefined,
});
const {
  foo,
} = toRefs(props);

// 接下来才能正常使用
const bar = computed(() => {
  return foo.value || 'bar';
});

4. 使用的变量未初始化错误

有些函数,可以传入为定义变量做参数,但是函数自身的签名没有很好的体现这一点,就会报错。比如 setTimeout,以下这段代码就会报告:

type timer = ReturnType<typeof setTimeout>;
let timeout:timer;
function doAction() {
  clearTimeout(timeout);
}

我确定这段代码没问题,但是 tsc 不给过,只好给 timeout 加上 ! 修饰符,即:let timeout!:timer。这样就可以了。

5. Vue SFC 文件类型定义

为让 tsc 能够正确理解 Vue SFC 格式,需要创建这个描述文件,并且告诉 tsc 加载这个描述文件。

declare module "*.vue" {
  import { defineComponent } from "vue";
  const component: ReturnType<typeof defineComponent>;
  export default component;
}
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,

    "paths": {
      "@/*": [
        "./src/*"
      ]
    },
    "lib": ["DOM", "ESNext"]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.vue",
    "test/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ],
  "files": ["src/types/vue-shims.d.ts"]
}

6. 待解决问题

  • 因为第一次使用 TypeScript,很多不熟悉的地方,tsc 大量报错。但是由于使用了 vue-loader,所以 tsc 报告的错误行号基本都不对,很难查找问题所在,浪费了大量时间。
  • 导入了 moment locale 文件,但是缺少定义,不知道该怎么声明某个对象属于某个接口。可能要通过前面类似定义 Vue SFC 的方式。

7. 项目地址

对项目感兴趣,或者寻求范例的同学可以在 GitHub 上找到这个项目:meathill/muimui-ui: A simple vue 3 ui components suit. (github.com)

分类
vue

升级 Vue@2 项目到 Vue@3

这篇主要是笔记。(我估计会是第一篇,因为只迁移了一个项目)

1. 安装新包

只记录必须重装的:

npm i vue@3 vue-loader@16.0.0-beta.8 vue-router@4.0.0-beta.13 @vue/compiler-sfc

2. 修改 Webpack 配置

// v2
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// v3
const {VueLoaderPlugin} = require('vue-loader');

// for DefinePlugin
{
  plugins: [
    new DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false,
    }),
  ],
}

3. 修改入口文件

没有 new Vue({}) 了,取而代之的是 Vue.createApp({}),后者还支持 tree-shaking。

也不需要注册 Vue-router 了,直接 app.use(router) 就好。所以传统的入口文件就要修改为:

// v2
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './app';
import 'bootstrap/dist/css/bootstrap.min.css';
import '@/styl/index.styl';
import router from './router';

Vue.use(VueRouter);

Vue.config.productionTip = false;

new Vue({
  router,
  ...App,
}).$mount('#app');

// v3
import {createApp} from 'vue';
import App from './app';
import 'bootstrap/dist/css/bootstrap.min.css';
import '@/styl/index.styl';
import router from './router';

const app = createApp({
  ...App,
});
app.use(router);
app.mount('#app');

4. 修改 router

Vue-router 的变化很大,建议大家好好看看 迁移手册。就我厂这个项目而言,主要是三个变化:

  1. 使用支持 tree-shaking 的函数 createRouter
  2. 修改 history: createWebHistory()
  3. 使用渲染函数 h 替换之前渲染方式
// 加载方式
import {h} from 'vue';
import {
  createRouter,
  createWebHistory,
  createWebHashHistory,
  RouterView,
} from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'home',
    component: {
      // vue-router v3
      render(createElement) {
        return createElement('router-view');
      }

      // vue-router v4
      render() {
        return h(RouterView);
      },
    },
    children: components,
  },
  // ....
];

const router = createRouter({
  // vue-router v3
  mode: process.env.NODE_ENV === 'production' ? 'history' : 'hash',
  // vue-router v4
  history: process.env.NODE_ENV === 'production'
    ? createWebHistory()
    : createWebHashHistory(),
  scrollBehavior: (to) => {
    if (to.hash && !/^#/.test(to.hash)) {
      return {selector: to.hash};
    }
    // 这里有个小改动,x => left, y => top,简单提一下
    return {top: 0};
  },
  routes,
});

5. 自定义组件 v-model 修改

  • prop: value => modelValue
  • event: input => `update:modelValue`

6. 一些小修改

  • beforeDestroy => beforeUnmount

7. createApp 与 Application,与 Component

v2 时,我们可以通过 new Vue({}) 初始化 Vue 实例。这个阶段,Vue 默认有一个全局对象 + 若干个实例,除了 local 的,就是全局的。

v3 时,引入了 Application(应用)的概念,在全局和组件之间,增加了一个新的层级。这样一来,我们就可以在同一个 Web 产品中,使用 Application 来划分命令、组件、mixins 的范围。应该会增加代码的强壮程度(虽然我暂时还没用到)。

不过,迁移代码的时候,也要注意。以前我们可能 new 一个实例,调用它的 methods;现在不行了,要这样做:

// v2
const ins = new Vue({});
ins.doSomething();

// v3
const app = createApp({});
const vm = app.mount('$el');
vm.doSomething();

8. 新的响应式 API

v3 最大的变化就是重构了响应式实现,所以新增了不少响应式 API。同时,也会检查开发者的代码,如果发现不需要响应式的地方用到响应式对象,就会提示开发者,因为响应式会增加系统开销。

这个时候可以用 markRawtoRaw 方法来修改对象,撤销之前附加在上面的响应式属性,提高访问效率。

其它 API 还很多,后面慢慢更新吧。

9. Devtool 和 SourceMap

遗憾的是,目前 Vue Devtool 无法检测到 Vue。老项目的 SourceMap 也完全不生效,无法正常对 SFC 进行 debug。

分类
vue

webpack 入口是 vue 文件时,无法合并 CSS

在多页网站中合并 CSS 是很常见的优化手法。一般来说,CSS 体积不会太大,使用同一份 CSS,改进用户点击链接后的加载速度,大部分收益都大于多加载几 K CSS 带来的损耗。

对于 UI 库来说也是如此,用统一的样式库,减少 import 时的心智负担,也是性价比很高的做法。

最开始我在 UI 库的入口文件里 export 所有组件,然后在其它仓库里用 import {component} from 'my-components' 使用组件。后来通过分析得知,这样做无法 tree-shaking,而且会导致组件库的重复引用,即 A import B,B import C,那么无论 A 里有没有用到 C(比如用到 B 的一个小功能 b1,它不依赖 C),都会把 C 的代码打包进去。

通过研读 Webpack 的 tree-shaking 文档,我得知也没有什么好办法可以规避这个问题,毕竟 JS 很灵活,你也不知道哪个开发者会搞个 eval('const myRequire = require'),如果要识别解析分辨所有情况太复杂,所以选择最保守的策略:认不出来具体功能的代码都给你带上。(早年我写 NerveNet 的时候,就是想不通这里怎么搞,最后坑掉了。早知道大家都选择绕开,说不定我的 NerveNet 也能如愿写出来了……)

总之,目前 lodash 的做法比较常见且能解决问题,即增加多入口,为每个可能用到的函数都打包独立的函数。这样需要引用那个就引用哪个,不用担心把整个 lodash 都打包进去。

于是我就立项开始重构我厂的几个前端库。然后很快就卡在 UI 库上面:无法生成合并过的 CSS 文件。Google 许久没有结果,吃饭前我灵机一动:vue-loader 必须搭配 VueLoaderPlugin 才能正确打包,会不会这个过程有 bug,导致如果我的入口都是 Vue 单文件组件,就会没法正确合并 CSS 文件。

然后我就测试了一下,代码大约如下:

// a.js
import './a.styl'

// b.js
import './b.styl'

可以生成合并过的 CSS 文件。与之相较,这样的 Vue 单文件组件就不行:

// a.vue
<style lang="stylus">
body
  font-size 16px
</style>

// b.vue
<style lang="stylus">
body
  color red
</style>

但是,同样是 vue 单文件组件,样式使用 import 的方式导入,就没问题:

// a.vue
<script>
import './a.styl';
</script>

// b.vue
<script>
import './b.styl';
</script>

现在可以确定是 vue-loader 或者 VueLoaderPlugin 有问题。不过最近身体欠佳,每天要花很多时间锻炼,另外还欠下不少坑要填,所以暂时没空去翻 issue 或者提 issue。哪位同学看见了,愿意帮忙的,可以搞一下,也算参与 开源社区建设了,功德无量。

分类
vue

Vue 3.0-beta.1 发布!

经历一年多的推倒重来反复打磨,在众多开发者的千呼万唤之下,Vue 开发团队终于在今天发布了 3.0-beta.1 版本,也就是测试版。通常来说,从测试版到正式版,只会修复 bug,不会引入新功能,或者删改老功能。所以,如果你对新版本非常感兴趣,或者有新项目即将上马,不妨尝试一下新版本。

按照官方路线图,原计划 Q1 末发布测试版,Q2 发布正式版。目前看来稍微有些延期,不过不多。相信正式版会很快到来,非常期待呀。

在我看来,Vue 已经是一份杰作,而 3.0 的变化则让它更加优秀。

起初,Vue 开创性的使用 Object.defineProperty 改写对象属性赋值运算,隐性收集依赖,把前端开发的难度降低了一个维度。从此,我们不需要考虑怎么绑定数据,怎么更新视图,只需要简简单单修改变量的值,界面就会自动变化,如魔法一般。

如今,Vue 3.0 更进一步,使用 Proxy 拦截赋值操作,不仅实现了同样的功能,还大幅降低系统消耗、减少计算时间。——更加值得期待的是,因为新 API 更强大,之前困扰众多开发者的“修改数据,界面不更新”问题,应该会变得极为罕见。

这会令 Vue 的入门门槛变得更低。在我看来,这件事善莫大焉。写代码并不仅仅是写代码,它是这个世界上成本最低的“创造型”工作。其它创造型工作,比如雕塑、美术,除了复杂的基础教育,还需要昂贵的生产成本。编程不需要,只要你有一台电脑,坐下来就可以写,想怎么写就怎么写,想写什么就写什么。而前端,又是其中成本最低的,你甚至不需要搭建开发环境,拿个记事本就能写,写完放到浏览器里就能跑。

从这个角度出发,每次在知乎看到“我出身不好,能不能学编程”,或者“我学历不好,能不能学编程”,我都会建议他们学。因为学会编程,不仅仅是掌握一门手艺,可能养家糊口,更是给自己的未来增加了一种可行性。学会编程,你就可以把手伸到我这种专业程序员伸不到的地方,帮助自己、帮助他人、甚至帮助世界,同时也能给自己换来各种各样的回报。

另外,新增的 Composition API——不管是一些人口中的“学 React hooks”,还是 Vue 作者尤雨溪说的“这不是 hooks,这比 hooks 强太多了”——大大增强了代码的可复用性,能很好的改进代码架构,为更大的项目、更强的架构,带来了更多的可能。

如果说 jQuery 让更多的人可以学会开发,那么 Vue 就让更多的人可以开发中大型软件。它会给整个行业带来的巨大帮助,让更多的产品可以有更好的用户体验,让更多的产品可以迁移、升级到新平台、新架构。

这个影响是潜移默化的,就像当年 Flash Player 集成 H.264 视频解码器一样,虽然很多人没注意,但是视频网站的春天就是那个时候到来的。如今,有了更好的基础设施,哪个新产品会崛起,我无法判断,但我觉得,对技术来说,更好的一天已经开始了。

分类
vue

Vue 2020 年路线图,Vue 3.0 计划于 Q2 发布

昨天 Vue 团队更新了 2020 年的路线图,里面包含了很多 Vue 3.0 的信息。建议大家一定要看原文,地址在:https://github.com/vuejs/vue/projects/6。下面我结合自己的理解翻译一下:

FAQ

问:3.0 啥时候能好?

答:请往后看。另外请注意,这些日期仅供参考,我们团队的首要目标是发布生产级别的高质量代码,不是赶 deadline。

问:3.0 里都有啥变化啊?

答:请自行翻阅最新的 RFC。另外,也要注意核心团队提交的 RFC 草案。

如果某个 RFC 里包含破坏性变更,那么里面一定会有“升级策略”章节,讨论迁移问题。

对于现在的 2.x 用户,我们会提供:

  • 迁移向导
  • 能够兼容 2.x 的兼容性版本(如果能兼容的话),并且对该升级的地方给出提示和升级建议
  • 命令行迁移工具
    • 自动升级能升级的代码
    • 不能自动升级的,扫描出来提示手动升级

问:我是新人,我现在该学 Vue 2.0 还是等 3.0?

答:如果你刚刚开始学习框架,那么应该开始使用 Vue 2。我们没有对 Vue 3 进行巨大的重新设计,所以大部分 Vue 2 知识仍将适用于 Vue 3。 如果你打算学习框架,没有必要等待。

如果你要为某个生产级别的项目选择技术栈:

  • 如果项目需要立即动工:我们仍然建议使用 Vue 2,以便获得完整的、框架级别的支持。 但是,也请留心 3.0 中即将发生的更改,不要使用将被移除的功能,最好也不要使用与 Vue 2 深度耦合的第三方依赖。
  • 如果项目可以等到 Q2 末:那我们建议你等等,用 3.0。

问:以后 2.x 会咋样呢?

答:接下来会有一个小版本(2.7)更新:

  • 将兼容的 3.x 功能反向移植回 2.x
  • 对 3.x 弃用的功能发出警告

这是 2.x 最后一个小版本,并提供长达 18 个月的 LTS(长期支持)。即使在 LTS 结束之后,我们也会继续提供重要的安全更新。

问:Vuex 方面有什么计划么?

答:一方面,我们正在开发Vuex(4.0)版本,其 API与 当前版本(3.0)完全相同,但与 Vue 3 兼容。我们力求向下兼容,让用户可以在 Vue 3 项目中继续使用现有 Vuex 代码。

另一方面,我们也在尝试新的设计,更多的利用 Vue 3 的响应式 API,也让 Vuex API 不那么冗长。 这个新版本暂定为“ vuex-next”,也就是 5.0。 眼下,我们只是在进行早期探索,最早也要到 2020 年第三季度才会发布。

2020 一季度计划

  • 3.0 SSR
  • 3.0 迁移
    • 升级向导(施工中)
    • 2.x 兼容版本
    • 迁移工具
  • 3.0 框架
    • router(施工中)
    • Vuex(施工中)
    • 测试工具(施工中)
    • JSX babel 插件(施工中)(我以为不会有这个东西了呢)
    • CLI
    • Devtools
    • 其它(虽然最后三个没标施工中,不过我觉得多半也是在施工中咯)
  • 3.0 beta
    • Q1 末发布!
    • 3.0 核心现在其实已经完成了,我们希望 API 到这个时候也能稳定下来。
    • 我们还需要更多的时间才能更新周边的库和工具。 如果您的使用场景对 router 和 vuex 没有硬性要求,这个时候就可以开始使用 3.0 了,但最好是非关键性应用程序。

2020 二季度计划

  • 继续之前未完成的 3.0 框架工作
  • 季度中,发布 3.0 RC
    • 冻结 API,不再有重大变化。进入 RC 之前,所有涉及到重大变更的 RFC 都要定案。
    • 全家桶能够和 3.0 版本协同工作。
    • 3.0 版本就绪,此阶段基本可用。仍然会有一些小错误和框架集成问题,在 RC 阶段都会慢慢被解决掉。
  • 3.0 发布管理
    • 回归测试
    • 自动化每晚发布
    • 正式确定版本生命周期
  • 3.0 IE11 兼容性版本
  • 3.0 官方正式版

2020 三季度计划

发布 2.7 版本

  • 反相迁移 3.x 功能到 2.x
  • 对 3.x 中弃用的功能发出警告
  • 2.x 最后的小版本,LTS
分类
vue

迎接 Vue 3.0

思否(SegmentFault)去年引入了新的运营合伙人,大张旗鼓,计划重新梳理视频教程体系。第一个举措就是推出新系列:思否编程公开课。这个系列的优势在于结合社区运营的知识与一线讲师的实战能力:即社区寻找大家关心的话题,请相关领域的讲师来讲解,达到受众明确、内容扎实的目标。

很高兴能够得到邀请,做了一期《迎接 Vue 3.0》,我也趁机深入了解了一把 Vue 3.0。

Vue 3.0 原计划 2019 年发布,但是开发过程有些波折,一些早期的设计被放弃了,因为不够好;引入了新的更好的设计,于是延期至今,仍然是 alpha 版。在新的设计下,Vue 3.0 更快,快到不需要时间切片;而且还能基本保持与 2.x 版本的兼容。

至于新增的 Compisition API,其实是选修课,类似 Async 之于 Promise。有了它,我们能更好的复用代码、维护代码;不用它,也不耽误我们享受其它升级带来的便利。

其实看过很多 Vue 3.0 的东西之后,我越发感激开发团队为这个框架做得一切,越发感激开源文化为我们带来的新世界。

我备课一向认真充分,所以讲课的内容应该是现在最新最全面的,欢迎大家围观:【思否编程公开课】迎接Vue 3.0

分类
js 技术

在 Chart.js 的线图里增加竖线

我经常使用 Chart.js 生成图表,无它,名字好记+免费+持续更新。现在主要基于 Vue 开发项目,所以经常使用 vue-chart.js。前阵子遇到一个需求:

  1. 把一组数据画成线图
  2. 用户可以任意点击时间
  3. 用数据生成饼图

那么就需要在线图中画一条竖线,标识出当前时间点。本以为这个需求不复杂,没想到不仅 Chart.js 不支持,包含这个功能的插件因为 Chart.js 升级的关系,暂时没法用。所以只好自己实现。

分类
js

Intersection Observer 笔记

有时候我们需要根据一个元素的位置来修改它的属性,比如图片的 lazyload,比如视频离开视窗之后停止播放。

以前的做法通常是:

  1. 侦听 window.scroll 事件
  2. scroll 触发,遍历每个要检查的 DOM Element,执行 .getBoundingClientRect() 取出它的 widthheighttopleft,然后根据 viewport 和宽高 scrollTop scrollLeft 计算对象是否应该出现
  3. 然后做处理

这样做会产生一些问题:

  1. 如果漏掉移除侦听器,可能造成内存泄漏
  2. 不同组件之间,很难共享侦听器
  3. 每一次都计算所有 Element,成本很高

于是现在我们有了 Intersection Observer,专门用来观察一个 Element 是否出现在 viewport 或者父容器里。它可以很好的解决这些问题:

  1. API 更清晰
  2. 逻辑原生,速度更快,消耗更少
  3. 可以自定义阈值,更加可控

关于 Intersection Observer 的详细知识,建议大家认真阅读 MDN – Intersection Observer,我就不抄文档了。

使用 Intersection Observer 大体上分为三步:

  1. 声明一个 Intersection Observer 实例,这一步最关键的是要确定显隐依据哪个元素,也就是 root
  2. 将需要检查的元素加入侦听队列
  3. 在回调函数里处理元素状态

写成代码大概是这样的:

// 声明一个实例
// 因为我的视口即当前 viewport,所以这里不需要 `options`
const observer = new IntersectionObserver(entries => {
  // 遍历所有实例,如果它显示出来,即 intersectionRatio 显示比例大于 0
  // 那么就让它 `dispatch('visible')`
  entries.forEach(({target, intersectionRatio}) => {
    const event = new CustomEvent('visible', {
      detail: {
        isVisible: intersectionRatio > 0,
      },
    });
    target.dispatchEvent(event);
  });
});

// 然后可以在 Vue 里侦听这个事件
export default {
  template: '<div @visible="onVisible"></div>',
  mounted() {
    observer.observe(this.$el);
  },
  beforeDestroy() {
    observer.unobserve(this.$el);
  },
}

Intersection Observer API 公布一段时间之后,又进行了升级,现在支持更丰富的参数,比如我们可以定义一个函数去判断更复杂情况下的显示状态。不过大部分场景下,我们并不需要很精准的判断,所以我觉得这个只是保障选择的机会。


参考阅读:

Proposed Updates for Intersection Observer

分类
分享 技术

Chat idea:记一次 Firefox 下 Vue 带来的性能危机的解决

前两天遇到一个问题:我厂的一个产品在 Firefox 下,可能发生因为 CPU 占用过高而卡死的情况。这个问题在测试环境不复现,在 Chrome下基本上也不会复现。

因为我厂老板是这个产品的主要用户,这个 bug 让我倍感压力,但一直没有解决它的好办法。终于有一天,在某台生产机上调试另外一个 bug 的时候,我终于发现了稳定复现这个 bug 的方式。

接下来就是几个小时的 debug,然后发现问题所在,然后解决问题,然后发现解决方案不理想,于是寻求新的解决方案,然后找到新的 API,最终彻底的解决这个问题。

接下来我就分享这个过程,读完整篇文章当中你将学会:

  1. 使用开发者工具查找性能问题
  2. 不断切分,缩小问题范围
  3. 理解 Vue 响应式原理分析问题根源
  4. 修复问题并验证
  5. 新的解决方案 Intersection Observer
  6. 解决问题并上线

目标读者:

  1. 中级开发者
  2. 熟悉原生 JS

大家觉得这个 idea 如何?请留言告诉我。不出意外的话这将是我下个月的 gitchat 内容。


有同学在 Drift 里留言,这里更新回答一下:这篇文章最后没写,因为当时 GitChat 的编辑不感兴趣,所以就放弃了。将来有机会会写。