dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

TypeScript: ReactのContextに型をつける(useContextと16.3以前のLegacy Contextも含む)

TypeScript + Reactでコンポーネントを書くとき、Context を使っているコンポーネントに対してどう型を書くのが正解か迷ったので、調べたことをメモしておきます。

調べるきっかけとなったコンポーネントは React 16.3 以前の Legacy Context を使った書き方になっていたのですが、ついでに新しい Context での書き方と、最新の Hooks の useContext() を使った場合も調べます。

なお、Context および useContext() についての説明は公式ドキュメントに譲ります。

目次


Contextを使う前のサンプルコード

import React from "react";
import "./App.css";

const ThemedButton: React.FC<{ theme: string }> = props => (
  <button className={props.theme}>Click me</button>
);

const Toolbar: React.FC<{ theme: string }> = props => (
  <div>
    Hello, TypeScript & React. <ThemedButton theme={props.theme} />
  </div>
);

const App: React.FC = () => {
  return (
    <div className="app">
      <header className="app-header">
        <Toolbar theme="dark" />
      </header>
    </div>
  );
};

export default App;

ThemedButtontheme を渡すために Toolbar にも props を渡しています。


1. React 16.3 以降の Context を使う場合

import React from "react";
import "./App.css";

const ThemeContext = React.createContext("light"); // (1)

class ThemedButton extends React.Component {
  static contextType = ThemeContext; // (1)
  context!: React.ContextType<typeof ThemeContext>; // (2)

  render() {
    return (
      <button className={this.context}>Click me</button>
    );
  }
}

const ThemedButtonFC: React.FC<{ theme: string }> = props => (
  <button className={`app-toolbar-button ${props.theme}`}>Click me</button>
);

const Toolbar: React.FC = props => (
  <div>
    Hello, TypeScript & React. <ThemedButton />
  </div>
);

const App: React.FC = () => {
  return (
    <ThemeContext.Provider value="dark"> // (1-2)
      <div className="app">
        <header className="app-header">
          <Toolbar />
        </header>
      </div>
      <div>
        <div>
          <ThemeContext.Consumer>
            {value => <ThemedButtonFC theme={value} />} // (1-3)
          </ThemeContext.Consumer>
        </div>
      </div>
    </ThemeContext.Provider>
  );
};

export default App;

ThemedButtonFC および <ThemeContext.Consumer>...</ThemeContext.Consumer> については、Consumerを使った箇所も確認したかったのでオマケです。

新しいContextでは、React.createContext(defaultValue) を使ってContextオブジェクトを作ります。
コメントで(1) と書いたところは、通常のContextのお作法通りに書いた箇所です。
これだけで、(1-2)Providervalue(1-3)Consumer 内の関数の value は正しく型チェックされるようになります。

ポイントは (2) のところで、どうやらこれがないとコンポーネント内のthis.contextが型チェックされないようです。
この書き方はReactの型定義ファイルを参考にしました。

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L421-L434

 /**
  * If using the new style context, re-declare this in your class to be the
  * `React.ContextType` of your `static contextType`.
  *
  * ```ts
  * static contextType = MyContext
  * context!: React.ContextType<typeof MyContext>
  * ```
  *
  * @deprecated if used without a type annotation, or without static contextType
  * @see https://reactjs.org/docs/legacy-context.html
  */


2. React 16.3 以前の Legacy Context を使う場合

Legacy Context の場合はこのようになります。

import React from "react";
import PropTypes from "prop-types";
import "./App.css";

// (1)
type ThemeContext = {
  theme: string;
};

class ThemedButton extends React.Component {
  static contextTypes = {
    theme: PropTypes.string
  };
  context!: ThemeContext; // (1)

  render() {
    return (
      <button className={this.context.theme}>
        Click me
      </button>
    );
  }
}

const Toolbar: React.FC = props => (
  <div>
    Hello, TypeScript & React. <ThemedButton />
  </div>
);

class App extends React.Component {
  static childContextTypes = {
    theme: PropTypes.string
  };

  getChildContext(): ThemeContext { // (1)
    return { theme: "light" };
  }

  render() {
    return (
      <div className="app">
        <header className="app-header">
          <Toolbar />
        </header>
      </div>
    );
  }
}

export default App;

公式ドキュメント相当の信頼できそうな情報は見つけられなかったのですが、こちらのブログを参考にしました。

ポイントとしては、Legacy Contextの場合、コンテキストはPropTypesで記述するためランタイムでのチェックができません。
そのため、(1) にあるように同じ構造のTypeScriptの型を定義した後、親のgetChildContext()、子のthis.contextに型を付けています。

ブログの記事との違いとして、ブログでは

context: ThemeContext;

としてますが、これだと--strictPropertyInitializationオプションがONのときに以下のエラーが出ます。

Property 'context' has no initializer and is not definitely assigned in the constructor. ts(2564)


3. React Hooksの useContext() を使った場合

最後に、Hooksを使った場合。

import React, { useContext } from "react";
import "./App.css";

const ThemeContext = React.createContext("light");

const ThemedButton: React.FC = props => {
  const theme = useContext(ThemeContext);

  return <button className={theme}>Click me</button>;
};

const Toolbar: React.FC = props => (
  <div>
    Hello, TypeScript & React. <ThemedButton />
  </div>
);

const App: React.FC = () => {
  return (
    <ThemeContext.Provider value="dark">
      <div className="app">
        <header className="app-header">
          <Toolbar />
        </header>
      </div>
    </ThemeContext.Provider>
  );
};

export default App;

コンポーネントが Function Component で書けるようになるということ以外は 1 と変わりません。

Amazonの商品ページの情報をkintoneに登録するChrome拡張

を作りました。

https://chrome.google.com/webstore/detail/amazon-to-kintone/leipfhjipgnfbdjkbinlmlmfdhgcakki からインストールできます。

アイコン未登録だったりコードがぐちゃぐちゃだったりしますが、とりあえず自分が使うための要件は満たせたので一区切り。


キャプチャ

f:id:dackdive:20190520192441p:plain

商品のタイトル、商品ページのURL、画像URLをkintoneの任意のアプリにレコード登録するだけのChrome拡張です。


モチベーション

kintone の勉強がてら、読みたい/読んだ本の記録を kintone でやっています
気になった本はたいていAmazonで検索してkintoneに登録してたんですが、それを1クリックでできるようにしたかった、というのがきっかけ。

また、技術的にもこのへんを試したかったというのもあります。

  • TypeScript で Chrome 拡張を書く
  • React Hooks
  • (未) styled-components

スタイルについてはせっかくなので kintone-ui-component 使って kintone ライクなUIにしようと考えていたけど
もろもろ断念して SalesforceLightning Design System になった。


技術的なメモ

TypeScript

chrome.*** 系のAPIに対する TypeScript の型定義は @types/chrome が利用できる。
Chrome拡張書くときのボイラープレート的なものは今回でだいぶ学べた。

また、 chrome.*** APIを Promise で書けるようにしつつブラウザ間の差異を吸収した webextension-polyfill というのもある。
今回は使わなかったけど興味あるのでリファクタしたい。


Chrome 拡張 ( chrome.*** API まわり)

すごい単純なChrome拡張だったのでPopupのみでいけるかなーと思ってたんだけど、PopupからはページのDOMを触れない。
content scirptを追加してあげる必要がある。

また、content scriptとPopup間のやり取りは chrome.tabs.sendMessage() および chrome.runtime.onMessage.addListener() を使ったメッセージング機能を利用することになる。

// メッセージ送信側
chrome.tabs.sendMessage(props.tabId, { /* パラメータ */ }, response => {
  // 受信側からのレスポンスが返ってきた後の処理
});
// メッセージ受信側
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  // もろもろ処理
  sendResponse(/* レスポンス */);
});

sendRequest() は deprecated
https://developer.chrome.com/extensions/tabs#method-sendRequest


React Hooks

今回は useState, useEffect しか使ってない。
慣れるまで少し時間がかかったけど、関心ごとに処理をまとめられるようになるのでたしかに読みやすいかも、と思った。

使わなかったものだとカスタムフック は興味あるのと useContext あたりは便利だという話を聞くので、ドキュメント読みたい。


リファレンス

TypeScriptでJestを使うときの設定(ts-jest, @types/jestなど)

メモ。
TypeScript を使ったプロジェクトに Jest を導入する時に必要なパッケージや設定、とくに ts-jest@types/jest が必ず必要なのかどうかがよくわかってなかったので調べた。


先にまとめ

TypeScript -> JavaScriptコンパイルを TypeScript 自身でやるか Babel に任せるかで必要な設定が異なる。
Babel 7 から TypeScript がサポートされた

  • @types/jest は(テストファイルも型チェックするなら)両方で必要
  • TypeScript のコンパイルに Babel を使う場合、@preset/typescriptbabel-jest をインストールしておけば ts-jest は不要
  • TypeScript のコンパイルに TypeScript を使う場合、 ts-jest が必要。jest.config.jsonpreset: 'ts-jest' を指定する


1. TypeScript を使った場合の設定

ts-jest 公式ドキュメントに従い、ts-jest@types/jest をインストールする。

$ npm install -D jest typescript ts-jest @types/jest

jest.config.jspresetts-jest を指定する。(Configuration > Basic usage より)

// jest.config.js
module.exports = {
  // [...]
  // Replace `ts-jest` with the preset you want to use
  // from the above list
  preset: 'ts-jest'
};


2. Babel を使った場合の設定

jest, typescript, @types/jest が必要なのは 1 と同じ。

$ npm install -D jest typescript ts-jest @types/jest

それ以外は Jest 公式ドキュメントの Getting Started に記載の通り。
Getting Started · Jest

Babel との併用のために babel-jest @babel/core @babel/preset-env 、さらに TypeScript のために @babel/preset-typescript が必要になる。

$ npm install -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript

そして Babel の設定にインストールした preset を書いておく。

// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-typescript',
  ],
};

ドキュメントでは @babel/preset-env{targets: {node: 'current'} を指定しているが、なくても動く。プロジェクトに合わせて設定すればいいはず。

Jest 側の設定(jest.config.json)は特に不要。


注意事項

2 の Babel を使った場合、以下に記載されているように制限事項がいくつかある。

特に Babel でのコンパイルの場合 型チェックをしない というのが大きな差。

たとえばこんなスクリプトを書いててもテストでは無視される。

// main.ts
export function add100(a: number) {
  const b: number = '100'; // 型エラー
  return a + b;
}
// main.spec.ts
import { add100 } from './main';

describe('add100', () => {
  it('should be 1 + 100 = 101', () => {
    expect(add100(1)).toEqual(101);
  });
});
# テスト実行結果。コンパイルエラーでなくアサーションでエラー
$ npm test
 FAIL  src/main.spec.ts
  ● add100 › should be 1 + 100 = 101

    expect(received).toEqual(expected)

    Expected: 101
    Received: "1100"

また、型チェックをしないので @types/jest はインストールしなくてもテストは実行できる。


検証コード

https://github.com/zaki-yama/typescript-jest-setting-sample

認定スクラムマスター研修(CSM)で学んだこと

だいぶ前ですが、2/13-15 に認定スクラムマスター研修を受けて、無事認定スクラムマスターになりました。

f:id:dackdive:20190405022421p:plain:w100

こないだ社内でも報告会をやったんですが、ここにも学んだこととか思ったことをメモしておきます。
研修の内容についての詳細はあまり書かれてません。(別にバラされて困ることない、って講師の方はおっしゃってましたが)


認定スクラムマスター研修 (CSM) について

いくつか運営団体があるようですが、自分が受講したのは Scrum Alliance という非営利団体が管理しているやつです。
Odd-e Japan という会社から申し込みました。

講師はエバッキーこと江端さんで、この方は唯一の日本人の認定スクラムトレーナーです。


学び

スクラムマスターはチームの誰よりも論理的に物事を考えられなければならない

個人的に一番誤解していてショッキングだったのがこれ。
3日間の研修中、本当に論理的思考力を要求されました。

理由は後述。


スクラムとは「プロジェクトの現状把握をするためのフレームワーク
  • 一般にフレームワークとは「課題発見」の手助けとなるものと「課題解決」の手助けとなるもの(あるいはその両方)があるが、スクラムは前者
    • もっと言うと、発見されたものを課題とみなすかどうかまでチームに委ねられている
  • 課題を解決するのはチームのメンバー。スクラムが解決してくれるわけではない

...という話を聞いて、前もネットで似たようなこと聞いたなーと思ったら ryuzee さんのこのツイートでした。


世に出回っている「スクラム」に関する本は、ほぼ「チーム(開発メンバー)」目線で書かれたもの。スクラムマスターの目線で書かれた本はほとんどない
  • スクラムに登場する3つの役割として「プロダクトオーナー」「チーム(開発チーム)」「スクラムマスター」があるが、各役割から見たスクラムは全く別モノ
    • 責任範囲が違うので
    1. じゃあ、スクラムマスター目線でスクラムを学ぶには?
      1. スクラムアジャイル開発が生まれた背景にある学問を学ぶ。「組織論」と「集団心理学」

優れたスクラムマスターというのは、極論言うと「「チームの目的にスクラムがマッチしてなかったらスクラムを辞め、別の手法をとる」ことを提案できないといけない。
そのためにはスクラムがどういう背景で生まれたのかとか、その他の手法について熟知してないといけない。
「何がなんでもスクラムをやる」はスクラムマスターじゃなくてスクラムスレーブ(奴隷)。


スクラムチームの3つの役割と責任
  • プロダクトオーナー
    • 投資対効果(ROI)を最大化する
  • チーム(開発チーム)
  • スクラムマスター
    • プロダクトオーナーやチームが目的を達成する確率を最大化する

※ROI や生産性の定義(何を分母分子とするか)は目的によって異なる

すべて「最大化」なのがポイント。常に過去より良くする活動を続ける責任がある。


2つの責任:Accountability と Responsibility
  • 未来に対する責任が Responsibility(説明責任)、過去に対する責任が Accountability(実行責任)
  • 例:熱々のやかんに手を触れようとしている子供と、その親がいて
    • 親が子に向かって「やけどするから危ないよ!」と注意喚起するのが Responsibility
    • 子がそれでもやかんを触ってやけどしてしまったら、それは子の Accountability
  • スクラムマスターが背負うのは 基本的に Responsibility のみ
    • プロダクトオーナーやチームは Accountability も Responsibility も持つ
  • 実行責任を伴わない分、スクラムマスターがプロダクトオーナーやチームに対して行う提案には非常に重みがある
    • 誰よりも「なぜそうすべきなのか」を論理的に説明できないといけないし、その提案は関係者の次の行動に影響を与えるので大きな責任が伴う
    • かつ、スクラムマスターが行うのはあくまで「提案」であって「指示」ではない。提案を受け入れるかどうかはプロダクトオーナーやチームに委ねられている

なので、スクラムマスターには常に現状から一歩先の未来を見通す能力が必要。チームと同じ時間軸で物事を考えているだけでは不十分。
未来に向かって今取るべきアクションを論理的に考え、他人に説明し、納得してもらった上で対象者の行動を変えていかなければならない。
※何がなんでも自分の意見を採用してもらう、というわけではない。提案を納得してもらった上で、チームが別の選択を取るということも受け入れなければならない。その場合はそこでの選択を受けて事前に描いていた未来を描き直し、次の提案に向けてすぐに動き出さなければならない。


自律的なチームに必要な要素
  • 目的が明確
    • できていたかどうかの判断が人によってぶれない
  • 境界線が明確
    • やっていいことと悪いこと、判断基準がはっきりしている
  • 目的を実現するために何をすべきか瞬時に判断し、行動し続けている


(質問)チームが自律的になったらスクラムマスターは不要ですか?スクラムマスターがいないチームというのは健全でしょうか?

これは研修の最中に自分が講師の方にした質問。
今の自分のチームにはスクラムマスターがいないので、それってアリなの?というのが気になっていた。

回答としては「基本的にスクラムマスターが不要になる瞬間というのはないと思って良い」とのこと。

ただ、続けておっしゃっていたのが

スクラムマスターがいなくなることはない。なぜなら、スクラムマスターが関わる対象は
開発チーム→プロダクトオーナーまで含めたスクラムチーム→複数のスクラムチーム→ステークホルダーも含めた「組織」
まで広げていかないといけないから。
逆に言うと、開発チームというミクロな視点で見てスクラムマスターがいないというのは問題だとは思わない

ということだったので、モヤモヤが晴れたとともにスクラムマスターが背負っていうミッションの大きさを再認識させられました。


感想

研修中は課題に対してとにかく論理的に解を出すことが求められ、ちょっとでも理論に穴があると突っ込まれ、停滞する、ということを繰り返した3日間で、本当にきつかったです。
日頃いかになんとなく行動を決めているかを痛感します。

スクラムマスターというとチームが自律的になるための支援をする人、ぐらいの認識だったので、どちらかというと人間力のようなものが求められるのかと思っていましたが
そこが間違いだったことに気づけたのは個人的には一番の学びでした。

また、重要なのは今後自分がどう行動していくか、また自分の行動によって周囲の行動をどう変えていくかだと思いますが
今のところチームでスクラムマスターになりたいと立候補するつもりはなくて、
スクラムマスターとしての考え方を身につけた上で、引き続きチームの1メンバーとしてカイゼンを続けていこうと思っています。

VSCodeVimでxやsでコピー(ヤンク)しないようにする

メモ。

素の Vim ではこちらの記事を参考に xs で1文字削除したときにクリップボードにコピーされないようにしていて、

VSCodeVim でも同様の設定をしたい。


方法

settings.json (コマンドパレットの Preferences: Open Settings (JSON)) を開き、以下を追記する。

"vim.normalModeKeyBindings": [
  {
    "before": ["x"],
    "after": ["\"", "_", "x"]
  },
  {
    "before": ["s"],
    "after": ["\"", "_", "s"]
  }
]

ESLintとPrettierを併用するときの設定(eslint-plugin-prettier, eslint-config-prettier)

今さらだけどメモ。
ESLint を使っているプロジェクトに Prettier を導入するときの設定方法。

結論としては、公式のこのページに全部まとまってた。

なお、 prettier-eslint というのもあるようだが調べていない。


先にまとめ

  1. eslint-plugin-prettier: ESLint と一緒に Prettier のチェックをしたい場合に導入する
  2. eslint-config-prettier: Prettier とバッティングする、ESLint のフォーマット関係のルールを無効化する
  3. 1, 2 両方やりたい場合、両方インストールして ESLint の設定に "extends": ["plugin:prettier/recommended"] と書けば OK

eslint-plugin-prettiereslint-config-prettier も公式のパッケージ。


1. eslint-plugin-prettier: ESLint と一緒に Prettier のチェックをしたい場合に導入する

eslintprettier コマンドを別々に実行するのではなく、ESLint で Prettier のチェックも同時にしたい場合に導入する。

$ yarn add --dev prettier eslint-plugin-prettier

ESLint の設定 ( .eslintrc.json ) には以下のように書く。

{
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error"
  }
}

// main.js
const a = 1
var b = "foo";
let c = function() {};


console.log(b);

結果

# eslint-config-standard を extends してるとする
$ ./node_modules/.bin/eslint main.js
/Users/yamazaki/workspace/eslint-prettier-playground/main.js
  1:7   error  'a' is assigned a value but never used        no-unused-vars
  1:12  error  Insert `;`                                    prettier/prettier
  2:9   error  Strings must use singlequote                  quotes
  2:14  error  Extra semicolon                               semi
  3:5   error  'c' is assigned a value but never used        no-unused-vars
  3:5   error  'c' is never reassigned. Use 'const' instead  prefer-const
  3:17  error  Missing space before function parentheses     space-before-function-paren
  3:22  error  Extra semicolon                               semi
  3:23  error  Delete `⏎`                                    prettier/prettier
  4:1   error  More than 1 blank line not allowed            no-multiple-empty-lines
  6:15  error  Extra semicolon                               semi

Prettier でフォーマットされる箇所が ESLint の1ルールとして検出されている。
また

https://github.com/prettier/eslint-plugin-prettier

The rule is autofixable -- if you run eslint with the --fix flag, your code will be formatted according to prettier style.

なので、--fix オプションつきで実行することによりフォーマットしてファイルを上書きすることも可能。


2. eslint-config-prettier: Prettier とバッティングする、ESLint のフォーマット関係のルールを無効化する

先の例で、セミコロンの有り無しは eslint-config-standard の設定と Prettier のデフォルト設定がコンフリクトしている。
このように ESLint のフォーマット系ルールは Prettier とコンフリクトする可能性があるので、それらのルールを無効化するために導入するのが eslint-config-prettier

$ yarn add --dev eslint-config-prettier

ESLint の設定 ( .eslintrc.json ) には以下のように書く。

{
  "extends": ["prettier"]
}

なお、

https://github.com/prettier/eslint-config-prettier#installation

Then, add eslint-config-prettier to the "extends" array in your .eslintrc.* file. Make sure to put it last, so it gets the chance to override other configs.

とあるので、extends に複数の config を指定する場合は一番最後に設定する。

// .eslintrc.json
{
  "extends": ["standard", "prettier"]
}

先ほどの main.js に対して

$ ./node_modules/.bin/eslint main.js
/Users/yamazaki/workspace/eslint-prettier-playground/main.js
  1:7  error  'a' is assigned a value but never used  no-unused-vars
  3:5  error  'c' is assigned a value but never used  no-unused-vars

フォーマット系のエラーは出なくなった。
ただし、全部無効化しているだけで Prettier のチェックもしていないので、 --fix オプションをつけただけでは何も起きない。
(別途 prettier --write をすればフォーマットされる)


注意 (2019/03/16追記)

https://github.com/prettier/eslint-config-prettier#special-rules によると、いくつか理由があって無効にしているルールがあるらしく、必要なオプション含めて各自判断しながら有効化しないといけないものがあるらしい。


注意2 (2019/03/17追記)

https://github.com/prettier/eslint-config-prettier#installation
をちゃんと読むと、

If you extend a config which uses a plugin, it is recommended to add "prettier/that-plugin" (if available). For example, eslint-config-airbnb enables eslint-plugin-react rules, so "prettier/react" is needed:

とあるので、 eslint-plugin-xxx を入れている場合は (あれば) 対応する prettier/xxxextends に追加すると
そのプラグインに含まれるフォーマット系のルールを無効化してくれる。

{
  "extends": [
    "airbnb",
    "prettier",
    "prettier/react"
  ]
}


3. 1, 2 両方やりたい場合

Use Both セクションに書かれている通りだが、1, 2どちらも有効にしたい場合は
それぞれの設定を書く必要はなく、 "extends": ["plugin:prettier/recommended"] と書けば OK。

// .eslintrc.json
{
  "extends": ["plugin:prettier/recommended"]
}

eslint-plugin-prettier , eslint-config-prettier どちらも npm install / yarn add しておく必要があることに注意。


注意 (2019/03/17追記)

eslint-config-prettier のところで書いた、eslint-plugin-xxx に対応する prettier/xxx については

// .eslintrc.json
{
  "extends": [
    "plugin:prettier/recommended",
    "prettier/react"
  ]
}

というように書くとうまく効いてるように見えた。
(動作確認したが公式ドキュメントからは見つからず)


その他

これらの設定をしても、 rules: { ... } のところに個別にフォーマット系ルールを設定していた場合はそっちが勝つので注意。当たり前だけど。

「よくわかるWeb Components」を読んだ

Web Components を学ぶべく、昨年の 技術書典5 で販売されていた 1000ch さんの本を買って読んでみました。

2時間程度で読み切れるので、Web Components 全く知らない状態から概要を理解するのにはとても良い本だと思います。
自分は Web Components がどういうものかぐらいは知っている状態で読んだけど、第1章で Web Components を採用するメリットや構成技術について再確認できたし、続く第2章以降で実際のコードを見ながら学べたのはよかったです。

そして、この本を読んだ後に昨年の HTML5 Conference 2018 のこのスライドを見返すとより一層理解が深まりました。
(Conference 当日にセッションを聴いていたが、そのときはほとんど理解できてなかった)

コードを写経したものは↓にあげておきます。


本書で学べること

  • Web Components およびそれらを構成する Cutome Elements、Shadow DOM、ES Modules とはどういう技術か
  • 実際に生のJavaScriptで Web Components を書くとどんな感じか
  • 名前しか知らなかった lit-html がどんな役割を果たしているのか


次に勉強するなら

最近リリースされた Ionic 4 が、Angular 以外の UI ライブラリもサポートするために Web Components で実装したみたいな話を聞いて、そしてそれには Stencil.js という TS + JSX で Web Components 作れるライブラリを使用してるらしいので気になっています。

参考:Introducing Ionic 4: Ionic for Everyone | The Ionic Blog

あとは https://www.webcomponents.org/コンポーネントのサンプルを眺めてみるとか。


以下、学習メモ


第1章

  • Web Components とは
    • 部品の本質的な再利用を、web標準技術で可能にする概念
  • メリット
    • ブラウザネイティブで動作する
    • ツールやライブラリに依存しない
    • 既存プロジェクトの技術と競合しにくい(ReactなどのUIライブラリと共存できる)
    • Shadow DOMによる本質的なスコープ
  • 構成技術
    • Custom Elements
    • Shadow DOM
    • ES Modules


第3章

  • 3.2 attachShadow() できるDOM要素は限定されている
  • 3.4 <slot> 要素を使うと、ホスト要素の子要素をShadow DOM内に挿入できる
<div>
  #shadow-root
    <slot><!-- ここには2つの<p>が入る --></slot>
    <slot name="foo"><!-- ここには<button>が入る --></slot>

  <p>Inserted into default slot</p>
  <p>Also inserted into default slot</p>
  <button slot="foo">Inserted into slot[name=foo]</button>
</div>


第4章

  • 4.4 <script nomodule src=...> とすると、ES Modulesに対応していない環境でのみスクリプトが読み込まれる
    • <script type="module"> と併用できる


第5章

  • 5.3 lit-html
    • Polymer チームが開発
    • 仮想DOMのような仕組みを使って効率よくDOMを更新できる
    • html`<div>...` でテンプレートを記述し
    • render(this.template, this.shadowRoot) で render する