[譯] 寫給 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索