21
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[React.js]私的デザインパターン

Last updated at Posted at 2016-01-05

はじめに

最近、React.jsをいじってみているのだが、React.jsをそのまま使おうとすると低レベルすぎて(言語に例えるならアセンブリ言語とか?)使いにくく感じる。

そこで、自分なりの書き方がまとまってきたものを少しずつ書きためていこうと思う。

ちなみに、React.js + TypeScript + Electronな環境で試しています。
さらにいうと、「7行で作るElectronアプリ」で作成したスケルトン(?)を使っています。

Factoryパターン(?)

デザインパターン本を読んだのははるか昔なので、パターンの名前は適当です。

React.Componentを継承して独自のコンポーネントを作成したとき、同じコンポーネントをすこしだけ変えて使いたいときがある。

たとえば、Markdownファイルを読み込んで表示するコンポーネントがあるとしたとき、見た目や動作は同じで、別のファイルを読み込むコンポーネントが必要になったとする。
このとき、似たようなコンポーネントを1から書くのは無駄すぎる。
そんなとき、どうしたらよいのか考えてみた。

基本となるコンポーネント

まずは、元になるコンポーネントを書いておく。

markdown.tsx
/// <reference path="../../../typings/tsd.d.ts" />

// パスは自分の環境のものなので各自読み替えてください。
import React = require('../../node_modules/react/');
import marked = require('../../node_modules/marked/');
import fs = require('../../node_modules/fs-extra/');
import path = require('path');

export class Markdown extends React.Component {
  private template_filename: string = path.resolve(__dirname + '/../markdown-template');

  private template = null;
  private text: string = '';
  private html: string = '';

  static propTypes = { path: React.PropTypes.string }; // this.propsの型定義
  state = { style: 'miniframe' }; // this.stateの初期化。getInitialStateはもう不要。

  constructor(props){
    super(props);

    this.template = require(this.template_filename);
    this.text = fs.readFileSync(this.props.path, 'utf8');
    this.html = marked(this.text);
  }

  onClick(){
    var style = this.state.style === 'miniframe' ? 'maxframe' : 'miniframe';
    this.setState({style: style});
  }

  render(){
    return this.template({
      html: this.html,
      onClick: this.onClick.bind(this)  // bindしないとthisが変になる。
    });
  }
}

一応、ここで使っている、テンプレートファイル(React-jade)とスタイルシート(css)ものせておく。

markdown-template.jade
div(class=this.state.style onClick=onClick dangerouslySetInnerHTML={__html: html})
style.css
.maxframe {
    background: #F8F8F8;
    border: 1px solid #979797;
    border-radius: 3px;
    width: 80%;
    height: 100%;
}

/* Markdown mini frame */
.miniframe {
    background: #F8F8F8;
    border: 1px solid #979797;
    border-radius: 8px;
    width: 300px;
    height: 300px;
}
/* Title: */
h1 {
    font-family: AvenirNext-Bold;
    font-size: 30px;
    color: #4A4A4A;
    line-height: 41px;
}
/* Content: */
p {
    font-family: HiraKakuProN-W3;
    font-size: 20px;
    color: #4A4A4A;
    line-height: 24px;
}

index.htmlとかはのせていない。

上記のMarkdownコンポーネントを再利用したいとする。
そんなとき、どうするか。

継承(?)されたコンポーネント

以下のようなクラスを作成する。

child_markdown.tsx
class ChildMarkdown extends React.Component {
  render(){
    return (<Markdown path={filename} />);
  }
}

class Markdownを直接継承はしていない。
renderメソッドの返り値に

<Markdown path={filename} />

として、JSX(ここではTypeScriptなのでTSX)で記述しているように、Markdownタグのpathプロパティにファイル名を渡している。
ファイル名を変えることにより別のファイルを使うMarkdownコンポーネントの子クラス(?)を作ることはできる。

ここで、ChildMarkdownクラス(あるいは、そもそものMarkdownクラス)のコンストラクタにファイル名を渡せばすむのではないかという疑問がわくと思う。

だが、それは難しい。

React.Componentの子クラスのconstructorの第1引数にはプロパティ(this.propsという名前でReactでよく出てくるやつにセットされる値)が渡されることになっているからだ。
なんとかならないかといろいろやったが、なんともならなかった…(いい案があれば教えて下さい)。

だが、ChildMarkdownクラスみたいなものを必要な数だけ作るのは、面倒だしダメなパターンにはまっている気がする…。

そこで次の手。

Factoryメソッド

MarkdownクラスにstaticなFactoryメソッドを作ればいい。

markdown.tsx(差分)
export class Markdown extends React.Component {
  // 略
  
  static create(filename: string){
    const tmpClass = class extends React.Component { // 無名クラス
      render(){
        return (<Markdown path={filename} />);
      }
    };
    return tmpClass;
  }
}

上記のようなメソッドを書いてやれば、あとは使いたいところで、

const component = Markdown.create(filename);

としてコンポーネントを作成し、あとは

const element = React.createElement(component);
const container = document.getElementById('container');

ReactDOM.render(element, container);

みたいな感じでレンダリングしてやればいい。

補足

この記事は、あくまで考え方の説明なので、ビルドに必要なすべてのコードを載せていません。
(いずれ、ソースコードは「7行で作るElectronアプリ」のElecTSのサンプルプロジェクトとして追加する予定です。)

と、ここまで書いといて、すでに誰かがいろんなパターンを考えてそうな予感が…

追記 ー もっと簡単な方法

夕方に届いた「入門 React」を読んでいたら、もっと簡単な方法がありました…

React.createElementの引数にプロパティを渡せるんですね…。

それを使うと、

const element = React.createElement(Markdown, {path: filename});

のように、ファイル名が違うぐらいのものなら、いくつでも作成可能のようです。

21
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?