尤大 几天前发在 GitHub 上的 vue-lit 是啥?

前端劝退师

共 11953字,需浏览 24分钟

 ·

2020-09-24 17:20


写在前面

328634618f9e8c564b4404da1f698a3f.webp

尤大北京时间 9月18日 下午的时候发了一个微博,人狠话不多。看到这个表情,大家都知道有大事要发生。果然,在写这篇文章的时候,上 GitHub 上看了一眼,刚好碰上发布:

85ef0633de74b95402a092d0cc5353a9.webp

我们知道,一般开源软件的 release 就是一个 最终版本,看一下官方关于这个 release 版本的介绍:

Today we are proud to announce the official release of Vue.js 3.0 "One Piece".

更多关于这个 release 版本的信息可以关注:https://github.com/vuejs/vue-next/releases/tag/v3.0.0[1]

除此之外,我在尤大的 GitHub 上发现了另一个东西 vue-lit[2],直觉告诉我这又是一个啥面向未来的下一代 xxx,所以我就点进去看了一眼是啥新玩具。

这篇文章就围绕 vue-lit 展开说说。

Hello World

Proof of concept mini custom elements framework powered by @vue/reactivity and lit-html.

首先,vue-lit 看上去是尤大的一个验证性的尝试,看到 custom elementlit-html,盲猜一把,是一个可以直接在浏览器中渲染 vue 写法的 Web Component 的工具。

这里提到了 lit-html,后面会专门介绍一下。

按照尤大给的 Demo,我们来试一下 Hello World


<html lang="en">
  <head>
    <script type="module">
      import {
        defineComponent,
        reactive,
        html,
        onMounted
      } from 'https://unpkg.com/@vue/lit@0.0.2';
  
      defineComponent('my-component', () => {
        const state = reactive({
          text'Hello World',
        });
        
        function onClick() {
          alert('cliked!');
        }
  
        onMounted(() => {
          console.log('mounted');
        });
  
        return () => html`
          <p>
            <button @click=
${onClick}>Click mebutton>
            
${state.text}
          p>

        `;
      })
    script>
  head>
  <body>
    <my-component />
  body>
html>

不用任何编译打包工具,直接打开这个 index.html,看上去没毛病:

b5d2a4d4bf105c04c727ffe95b907435.webp

可以看到,这里渲染出来的是一个 Web Component,并且 mounted 生命周期也触发了。

介绍 vue-lit 之前,我们需要先有一些前置知识。

关于 lit-html 和 lit-element

vue-lit 之前,我们先了解一下 lit-htmllit-ement,这两个东西其实已经出来很久了,可能并不是所有人都了解。

lit-html

lit-html[3] 可能很多人并不熟悉,甚至没有见过。

8fa42147554c08199cbb7f668836eeb2.webp

所以是啥?答案是 HTML 模板引擎

如果没有体感,我问一个问题,React 核心的东西有哪些?大家都会回答:jsxVirtual-DOMdiff,没错,就是这些东西构成了 UI = f(data)React

来看看 jsx 的语法:

function App() {
  const msg = 'Hello World';
  return <div>${msg}div>;
}

再看看 lit-html 的语法:

function App() {
  const msg = 'Hello World';
  return html`
    <div>
${msg}div>
  `;
}

我们知道 jsx 是需要编译的它的底层最终还是 createElement....。而 lit-html 就不一样了,它是基于 tagged template 的,使得它不用编译就可以在浏览器上运行,并且和 HTML Template 结合想怎么玩怎么玩,扩展能力更强,不香吗?

当然,无论是 jsx 还是 lint-html,这个 App 都是需要 render 到真实 DOM 上。

lint-html 实现一个 Button 组件

直接上代码(省略样式代码):


<html lang="en">
<head>
  <script type="module">
    import { html, render } from 'https://unpkg.com/lit-html?module';

    const Button = (text, props = {
      type'default',
      borderRadius'2px'
    }, onClick) => {
      // 点击事件
      const clickHandler = {
        handleEvent(e) { 
          alert('inner clicked!');
          if (onClick) {
            onClick();
          }
        },
        capturetrue,
      };

      return html`
        <div class="btn btn-
${props.type}" @click=${clickHandler}>
          
${text}
        div>

      `

    };
    render(Button('Defualt'), document.getElementById('button1'));
    render(Button('Primary', { type'primary' }, () => alert('outer clicked!')), document.getElementById('button2'));
    render(Button('Error', { type'error' }), document.getElementById('button3'));
  script>
head>
<body>
  <div id="button1">div>
  <div id="button2">div>
  <div id="button3">div>
body>
html>

效果:

b64ad0c2e8993b30f78dd96e1a64e3b1.webp

性能

lit-html 会比 React 性能更好吗?这里我没仔细看过源码,也没进行过相关实验,无法下定论。

但是可以大胆猜测一下,lit-html 没有使用类 diff 算法而是直接基于相同 template 的更新,看上去这种方式会更轻量一点。

但是,我们常问的一个问题 “在渲染列表的时候,key 有什么用?”,这个在 lit-html 是不是没法解决了。我如果删除了长列表中的其中一项,按照 lit-html 的基于相同 template 的更新,整个长列表都会更新一次,这个性能就差很多了啊。

// TODO:埋个坑,以后看

lit-element

lit-element[4] 这又是啥呢?

3c62adb5bc46f07769d2290222281189.webp

关键词:web components

例子

import { LitElement, html } from 'lit-element';

class MyElement extends LitElement {
  static get properties() {
    return {
      msg: { typeString },
    };
  }
  constructor() {
    super();
    this.msg = 'Hello World';
  }
  render() {
    return html`
      <p>
${this.msg}p>
    `;
  }
}

customElements.define('my-element', MyElement);

效果

764cf783201b28a04e2a79dadd229a56.webp

结论:可以用类 React 的语法写 Web Component

so, lit-element 是一个可以创建 Web Componentbase class。分析一下上面的 Demo,lit-element 做了什么事情:

  1. static get properties: 可以 setterstate
  2. constructor: 初始化 state
  3. render: 通过 lit-html 渲染元素,并且会创建 ShadowDOM

总之,lit-element 遵守 Web Components 标准,它是一个 class,基于它可以快速创建 Web Component

更多关于如何使用 lit-element 进行开发,在这里就不展开说了。

Web Components

浏览器原生能力香吗?

Web Components 之前我想先问问大家,大家还记得 jQuery 吗,它方便的选择器让人难忘。但是后来 document.querySelector 这个 API 的出现并且广泛使用,大家似乎就慢慢地淡忘了 jQuery

浏览器原生 API 已经足够好用,我们并不需要为了操作 DOM 而使用 jQuery

You Dont Need jQuery[5]

再后来,是不是很久没有直接操作过 DOM 了?

是的,由于 React / Vue 等框架(库)的出现,帮我们做了很多事情,我们可以不用再通过复杂的 DOM API 来操作 DOM

我想表达的是,是不是有一天,如果浏览器原生能力足够好用的时候,React 等是不是也会像 jQuery 一样被浏览器原生能力替代?

组件化

React / Vue 等框架(库)都做了同样的事情,在之前浏览器的原生能力是实现不了的,比如创建一个可复用的组件,可以渲染在 DOM 中的任意位置。

现在呢?我们似乎可以不使用任意的框架和库,甚至不用打包编译,仅是通过 Web Components 这样的浏览器原生能力就可以创建可复用的组件,是不是未来的某一天我们就抛弃了现在所谓的框架和库,直接使用原生 API 或者是使用基于 Web Components 标准的框架和库来开发了?

当然,未来是不可知的

我不是一个 Web Components 的无脑吹,只不过,我们需要面向未来编程。

来看看 Web Components 的一些主要功能吧。

Custom elements: 自定义元素

自定义元素顾名思义就是用户可以自定义 HTML 元素,通过 CustomElementRegistrydefine 来定义,比如:

window.customElements.define('my-element', MyElement);

然后就可以直接通过 使用了。

根据规范,有两种 Custom elements

  • Autonomous custom elements: 独立的元素,不继承任何 HTML 元素,使用时可以直接
  • Customized buld-in elements: 继承自 HTML 元素,比如通过 { extends: 'p' } 来标识继承自 p 元素,使用时需要

两种 Custom elements 在实现的时候也有所区别:

// Autonomous custom elements
class MyElement extends HTMLElement {
  constructor() {
    super();
  }
}

// Customized buld-in elements:继承自 p 元素
class MyElement extends HTMLParagraphElement {
  constructor() {
    super();
  }
}

更多关于 Custom elements[6]

生命周期函数

Custom elements 的构造函数中,可以指定多个回调函数,它们将会在元素的不同生命时期被调用。

  • connectedCallback:元素首次被插入文档 DOM
  • disconnectedCallback:元素从文档 DOM 中删除时
  • adoptedCallback:元素被移动到新的文档时
  • attributeChangedCallback: 元素增加、删除、修改自身属性时

我们这里留意一下 attributeChangedCallback,是每当元素的属性发生变化时,就会执行这个回调函数,并且获得元素的相关信息:

attributeChangedCallback(name, oldValue, newValue) {
  // TODO
}

需要特别注意的是,如果需要在元素某个属性变化后,触发 attributeChangedCallback() 回调函数,你必须监听这个属性

class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['my-name'];
  }
  constructor() {
    super();
  }
}

元素的 my-name 属性发生变化时,就会触发回调方法。

Shadow DOM

Web Components 一个非常重要的特性,可以将结构、样式封装在组件内部,与页面上其它代码隔离,这个特性就是通过 Shadow DOM 实现。

dc56639aae2af7deb2b4f6220f07e5fc.webp

关于 Shadow DOM,这里主要想说一下 CSS 样式隔离的特性。Shadow DOM 里外的 selector 是相互获取不到的,所以也没办法在内部使用外部定义的样式,当然外部也没法获取到内部定义的样式。

这样有什么好处呢?划重点,样式隔离,Shadow DOM 通过局部的 HTMLCSS,解决了样式上的一些问题,类似 vuescope 的感觉,元素内部不用关心 selectorCSS rule 会不会被别人覆盖了,会不会不小心把别人的样式给覆盖了。所以,元素的 selector 非常简单:title / item 等,不需要任何的工具或者命名的约束。

更多关于 Shadow DOM[7]

Templates: 模板

可以通过