なになれ

IT系のことを記録していきます。

Chrome拡張機能を作る過程で学んだReactのこと

以前にChrome拡張機能を作りました。UI部分はReactで実装しました。

hi1280.hatenablog.com

今回はこのChrome拡張機能を作る際にどのようにReactを使ったのかを紹介します。

Option Page

Chrome拡張機能の設定画面です。

f:id:hi1280:20190228220929p:plain

Material-UI

今回は以下のReact用のモジュールを使っています。

material-ui.com

Material-UIを使えば非Webデザイナーでもある程度見た目が良いWebページを作れるので利用します。

今回は、Material-UIのCSS-in-JS機能を使ってReactコンポーネントCSSを適用しています。
個人的にはこの部分を理解するのに苦労しましたので自分の整理も兼ねて説明します。

Material-UIのCSS-in-JS

CSS-in-JS機能を使うのにwithStylesという関数を使ってCSSのstyleを適用します。

コードの全体像は以下の通りです。

option.tsx

// ...省略

const styles = (theme: Theme) =>
  createStyles({
    // ...省略
  });

interface IProps extends WithStyles<typeof styles> {}

const Option = withStyles(styles)(
  class extends React.Component<IProps> {
    // ...省略
    public render() {
      const { classes } = this.props;
      return (
            // ...省略
            <main className={classes.layout}>
              <Paper className={classes.paper}>
                <Grid container={true} spacing={24}>
                  <Grid item={true} xs={12}>
                    <this.apiKeyMessage />
                  </Grid>
                </Grid>
              </Paper>
            </main>
            // ...省略
      );
    }
    // ...省略
  }
);

ReactDOM.render(<Option />, document.getElementById('option'));

このようなコードにおいて、CSSを適用する流れは以下の通りです。

  • CSSのstyleを定義する
  • propsにclassesプロパティが生成される
  • classesプロパティをコンポーネントのclass名にセットする
TypeScriptで扱う

このwithStylesを使う場合にTypeScriptでは型を扱うためちょっとした対象法を実施する必要があります。

createStyles関数を使って、withStylesの引数に与えるstyleを定義します。

option.tsx

const styles = (theme: Theme) =>
  createStyles({
    layout: {
      marginLeft: theme.spacing.unit * 2,
      marginRight: theme.spacing.unit * 2,
      width: 'auto',
      [theme.breakpoints.up(600 + theme.spacing.unit * 2 * 2)]: {
        marginLeft: 'auto',
        marginRight: 'auto',
        width: 600,
      },
    }
    // ...省略
  });

stylesの定義にしたがって、propsの型を定義します。

interface IProps extends WithStyles<typeof styles> {}

これにより、propsに型がある状態で、classesプロパティを扱うことができます。

下記がTypeScriptを使う場合の詳しい解説になります。

material-ui.com

Content Script

ここでは、Chromeで表示しているWebページにDOMを追加する処理をしていて、そのDOMがCanvasになっています。
Webページのブラウザ上で見える部分全体をCanvasとするために見える部分全体のサイズを計測する必要があります。

Reactにおけるコンポーネントのサイズの取得について説明します。

サイズの計測には下記のモジュールを利用しました。

github.com

サイズを計測するコンポーネントを配置する

Measureコンポーネントを配置します。

content-script.tsx

<Measure
  offset={true}
  onResize={contentRect => {
    this.setState({
      wrapper: contentRect.offset,
    });
  }}
>
   // サイズを計測するコンポーネントなどのコンポーネント群
</Measure>

上記ではoffsetを計測するようにしています。そのほかにもDOMのサイズに関するプロパティを計測することができます。
onResizeイベントでサイズが変更される度にstateにサイズがセットされるように定義しています。

計測の設定

content-script.tsx

  // サイズを計測するコンポーネント群
  {({ measureRef }) => (
    <Wrapper ref={measureRef}>
      <Canvas width={this.state.wrapper.width} height={this.state.wrapper.height} />
    </Wrapper>
  )}

measureRefがサイズを計測する対象のコンポーネントを指定する変数になります。
ここではCanvasがWrapperコンポーネントのサイズをstateを介して取得して、サイズをプロパティにセットしています。

この内容に関してはこちらを参考にしました。

qiita.com

共通

拡張機能のUI全般で国際化対応を行いました。

国際化対応には下記のモジュールを利用しました。

github.com

React Intlを使った国際化対応について説明します。

言語ファイルを作成する

対応する言語ごとにファイルを作成します。

ja_JP.ts

const ja_JP = {
  apikey: 'APIキー',
  config: '設定',
  popup_main: 'オプション画面でAPIキーを設定してください',
  popup_sub: 'APIキーの説明',
};
export default ja_JP;

言語ファイルの設定をコンポーネントに適用する

addLocaleData関数を使用してReact Intlで用意されているLocaleデータを読み込みます。

index.tsx

import { addLocaleData, FormattedMessage, IntlProvider } from 'react-intl';
import * as en from 'react-intl/locale-data/en';
import * as ja from 'react-intl/locale-data/ja';

// ...省略
  constructor(props: object) {
    super(props);
    addLocaleData([...en, ...ja]);
  }
// ...省略

IntlProviderコンポーネントを配置します。
このコンポーネントの範囲が国際化の対応範囲になります。

index.tsx

class Popup extends React.Component {
  private locale = navigator.language.split('_')[0];

  // ...省略

  <IntlProvider locale={this.locale} messages={Util.chooseLocale(this.locale)}>
    // 国際化の対応範囲
    <FormattedMessage id="popup_main" />
  </IntlProvider>
}

navigator.languageで利用言語を特定しています。
言語ファイルの定義に存在するキーをid属性に指定することで各言語の内容を表示することができます。

util.ts

export class Util {
  public static chooseLocale(locale: string) {
    switch (locale) {
      case 'en':
        return en_US;
      case 'ja':
        return ja_JP;
      default:
        return ja_JP;
    }
  }
}

この内容に関してはこちらを参考にしました。
lizefieldwp.azurewebsites.net

まとめ

TypeScriptでReactを使う場合、実装方法に工夫が必要になる場合があります。
なぜそのような実装なのかに気づくためにもただコードをコピペするのではなく、TypeScriptとReactをもっと理解することが必要だと感じています。
まだ色々分かっていない感じです。

参考

Chrome拡張機能における各項目がどういった役割なのかはこちらを見ると理解できると思います。

qiita.com