[译] 写给 React 开发者的自定义元素指南

最近我须要构建 UI 界面,虽然如今 React.js 是我更为青睐的 UI 解决方案,不过长时间以来我第一次没有选择用它。而后我看了浏览器内置的 API 发现使用自定义元素(也就是 Web 组件)可能正是 React 开发者须要的方案。css

自定义元素能够具备与 React 组件大体相同的优势,并且实现起来无需绑定特定的框架。自定义元素能提供新的 HTML 标签,咱们可使用原生浏览器的 API,用编程的方式操控它。html

让咱们说说基于组件的 UI 优势:前端

  • 封装 — 把专一点放在组件的内部实现上
  • 复用 — 当把 UI 分割成更通用的小块时,它们更容易分解为你能够复用的形态
  • 隔离 — 由于组件是被封装过的,你能得到隔离带来的额外好处,即让你更轻松地定位错误和更易修改应用中的特定部分

用例

你可能想知道有谁在生产环境中使用自定义元素。比较出名的有:react

  • GitHub 在模态对话框、自动补全和显示时间三个功能上使用了自定义元素。
  • YouTube 的新 Web 应用使用了 Polymer 和 Web 组件。

和组件 API 的类似点

当试图比较 React 组件和自定义组件时,我发现它们的 API 很是类似:jquery

  • 它们都是类,而类已经不是新的概念了,而且都能扩展自基类
  • 它们都继承挂载或渲染生命周期
  • 它们都须要经过 props 或 attributes 来静态或动态传入数据

演示

那么,让咱们来构建一个小型应用,提供 GitHub 仓库的详细信息列表。android

结果截图

若是我要用 React 来实现,我会定义一个以下的简单组件:ios

<Repository name="charliewilco/obsidian" />
复制代码

这个组件须要一个 prop —— 仓库名,咱们要这么实现它:git

class Repository extends React.Component {
  state = {
    repo: null
  };

  async getDetails(name) {
    return await fetch(`https://api.github.com/repos/${name}`, {
      mode: 'cors'
    }).then(res => res.json());
  }

  async componentDidMount() {
    const { name } = this.props;
    const repo = await this.getDetails(name);
    this.setState({ repo });
  }

  render() {
    const { repo } = this.state;

    if (!repo) {
      return <h1>Loading</h1>;
    }

    if (repo.message) {
      return <div className="Card Card--error">Error: {repo.message}</div>;
    }

    return (
      <div class="Card">
        <aside>
          <img
            width="48"
            height="48"
            class="Avatar"
            src={repo.owner.avatar_url}
            alt="Profile picture for ${repo.owner.login}"
          />
        </aside>
        <header>
          <h2 class="Card__title">{repo.full_name}</h2>
          <span class="Card__meta">{repo.description}</span>
        </header>
      </div>
    );
  }
}
复制代码

请看 Charles (@charliewilco) 在 CodePen 上的 React 演示 — GitHubgithub

来深刻看一下,咱们有一个组件,这个组件有它本身的状态,即仓库的详细信息。开始时,咱们把它设为 null,由于此时尚未任何数据,因此在加载数据时会有一个加载提示。web

在 React 的生命周期中,咱们使用 fetch 从 GitHub 得到数据,建立选项卡,而后在咱们拿到返回数据后使用 setState() 触发一次从新渲染。全部 UI 使用的不一样状态都会在 render() 方法里表现出来。

定义/使用自定义元素

使用自定义元素实现起来稍有不一样。和 React 组件同样,咱们的自定义元素也须要一个属性 —— 仓库名,它的状态也是本身管理的。

以下就是咱们的元素:

<github-repo name="charliewilco/obsidian"></github-repo>
<github-repo name="charliewilco/level.css"></github-repo>
<github-repo name="charliewilco/react-branches"></github-repo>
<github-repo name="charliewilco/react-gluejar"></github-repo>
<github-repo name="charliewilco/dotfiles"></github-repo>
复制代码

请看 Charles (@charliewilco) 在 CodePen 上的自定义元素演示 — GitHub

如今,咱们所须要作的就是定义和注册自定义元素,建立一个类,它继承自 HTMLElement 类,而后用 customElements.define() 注册元素的名字。

class OurCustomElement extends HTMLElement {}
window.customElements.define('our-element', OurCustomElement);
复制代码

它是这样调用的:

<our-element></our-element>
复制代码

这个新元素如今还不是颇有用,可是有它以后,咱们能用三个方法来扩展这个元素的功能。这些方法相似于 React 组件的 生命周期 API。两个和咱们最相关的类生命周期函数是 disconnectedCallBackconnectedCallback,并且因为自定义元素是一个类,它天然会有一个构造器。

名字 什么时候调用
constructor 用来建立或更新元素的实例。经常使用来初始化状态、设置事件监听或建立 Shadow DOM。若是你想知道在 constructor 能够作什么,请查看设计规范。
connectedCallback 在元素被插入 DOM 后调用。用来运行建立任务的代码,例如获取资源或渲染 UI。整体上说,你应该在这里尝试异步任务。
disconnectedCallback 在元素被移出 DOM 后调用。用来运行作清理任务的代码。

为了实现咱们的自定义元素,咱们建立了以下类并设置了和 UI 相关的属性:

class Repository extends HTMLElement {
  constructor() {
    super();

    this.repoDetails = null;

    this.name = this.getAttribute("name");
    this.endpoint = `https://api.github.com/repos/${this.name}`    
    this.innerHTML = `<h1>Loading</h1>`
  }
}
复制代码

经过在咱们的构造器中调用 super(),元素本身的上下文和 DOM 操做 API 就可使用了。目前,咱们已经设置了默认的仓库详情为 null,从元素属性取得仓库名,建立一个用来调用的 endpoint,这样咱们不用在后面定义,最重要的是,将初始的 HTML 设置成了加载提示。

为了获取关于元素仓库的详情,咱们将须要向 GitHub 的 API 发送请求。咱们使用 fetch因为它是基于 Promise 的,咱们使用 asyncawait 来使咱们的代码更易阅读。你能够在这里了解更多关于 async/await 关键字,而且能够在这里了解更多浏览器的 fetch API 的内容。你还能够在 Twitter 上和我讨论,了解我是否更喜欢 Axios 库。(提示,这取决于我早餐时喝了茶仍是咖啡。)

如今,让咱们给这个类添加方一个方法来向 GitHub 查询仓库详情。

class Repository extends HTMLElement {
  constructor() {
  // ...
  }

  async getDetails() {
    return await fetch(this.endpoint, { mode: "cors" }).then(res => res.json());
  }
}
复制代码

下面,让咱们使用 connectedCallback 方法和 Shadow DOM 来使用 getDetails 方法的返回值。使用这个方法的效果和咱们在 React 示例中调用 Repository.componentDidMount() 相似。咱们将开始时赋给this.repoDetailsnull 替换掉 —— 并将在后面调用模板建立 HTML 时使用它。

class Repository extends HTMLElement {
  constructor() {
    // ...
  }

  async getDetails() {
    // ...
  }

  async connectedCallback() {
    let repo = await this.getDetails();
    this.repoDetails = repo;
    this.initShadowDOM();
  }

  initShadowDOM() {
    let shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = this.template;
  }
}
复制代码

你会注意到咱们正在调用与 Shadow DOM 相关的方法。除了做为被漫威电影拒绝的标题以外,Shadow DOM 还有本身丰富的 API 值得研究。为了咱们的目标,它将抽象出一种将 innerHTML 添加到元素的实现。

如今咱们将 this.template 赋值给 innerHTML。如今来定义 template

class Repository extends HTMLElement {
  get template() {
    const repo = this.repoDetails;

    // 若是获取错误信息,向用户显示提示信息
    if (repo.message) {
      return `<div class="Card Card--error">Error: ${repo.message}</div>`
    } else {
      return `
      <div class="Card">
        <aside>
          <img width="48" height="48" class="Avatar" src="${repo.owner.avatar_url}" alt="Profile picture for ${repo.owner.login}" />
        </aside>
        <header>
          <h2 class="Card__title">${repo.full_name}</h2>
          <span class="Card__meta">${repo.description}</span>
        </header>
      </div>
      `
    }
  }
}
复制代码

自定义元素差很少就是这样。自定义元素能够管理自身状态、获取自身数据及将状态体现给用户,同时提供了能够在应用程序里使用的 HTML 元素。

在完成本次练习以后,我发现自定义元素惟一须要的依赖是浏览器的原生 API 而不是另外须要解析和执行的框架。这是一个更具可移植性和可复用性的解决方案,并且这个方案和你喜欢并用之谋生的框架的 API 很类似。

固然,这种方法也有缺点,咱们说的是不一样浏览器的支持问题和缺少一致性。此外,DOM 操做 API 可能会十分混乱。有时它们是赋值。有时它们是函数。有时这些方法须要回调函数而有时又不须要。若是你不相信,那就去看一下使用 document.createElement() 将类添加进 HTML 元素的方法,这是使用 React 的五大理由之一。基本实现其实并不复杂,但它与其余相似的 document 方法不一致。

现实的问题是:它是否会被淘汰?也许会。React 仍然在它该擅长的东西上表现良好:虚拟 DOM、管理应用状态、封装和在树中向下传递数据。如今几乎没有在该框架中使用自定义元素的动力。另外一方面,自定义元素在制做浏览器应用上很是简单实用。

了解更多

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索