• ..

AWS

    JSX のタグ名にはどんなものが使えるかまとめ

    公式ドキュメントには、「頭に小文字を使うと HTML かと思うので、大文字で定義してください」というようなことがこの辺に書かれていますが、少し分からないケースがでてきたので自分でもトランスパイルしてみて調べました。

    変数

    この辺はドキュメントの通り頭小文字で定義した場合は単に文字列として渡されていました。

    const orange = () => <div style={{padding:10,background:'orange'}} />;
    const Orange = () => <div style={{padding:10,background:'orange'}} />;

    展開後。

    React.createElement('orange', {
      style: {padding: 10, background: 'orange'}
    });
    React.createElement(Orange, {
      style: {padding: 10, background: 'orange'}
    });

    オブジェクト

    プロパティアクセス

    ここが良く分からなかった部分です。
    オブジェクトの場合は、大文字小文字関係なく React コンポーネントとして渡されます。

    const div = {}
    div.orange = () => <div style={{padding:10,background:'orange'}} />;
    div.Orange = () => <div style={{padding:10,background:'orange'}} />;

    展開後。

    React.createElement(div.oarnge, {
      style: {padding: 10, background: 'orange'}
    });
    React.createElement(div.Orange, {
      style: {padding: 10, background: 'orange'}
    });

    ちなみにこの場合にdivを使っても React はそれを単に HTML タグと考えらるので問題ないです。

    // 上の続き
    <div />
    
    // は
    React.createElement('div', null);

    インデックス記法でのアクセス

    これもまぁドキュメントに書かれているのですが、div['orange']というような形を JSX のタグで使うことはできません。その場合は事前にOrangeのような変数にその値を格納してから<Orange />のように使う必要があります。

    React と AtomicDesign - Atom編

    HTML 要素と同じように < と > で囲んで使う要素は Atom

    <div><span> のようにある要素を組み上げるための部品が Atom です。 僕はよくstyled-componentsを使いますが、これで生成したコンポーネントは全て最小単位の部品で Atom に属すと考えれます。

    // FlexはAtom
    const Flex = styled.div`
      display: flex;
    `;
    // MyFormはAtom
    const MyForm = styled.form`
      /* ... */
    `;
    // MyInputはAtom
    const MyInput = styled.input`
      /* ... */
    `;

    これらはこのように使えます。

    <Flex>
      <MyForm>
        <MyInput />
      </MyForm>
    </Flex>

    Styled Components を使っていないなら単一要素の SFC と1対1で CSS ファイルを作成し管理するのがいいと思います。

    foo
     ├─ index.jsx
     └─ foo.css
    // atoms/index.js
    const Flex = props => <div className="atom-Flex" {...props} />;
    /* atoms/flex.css */
    .atom-Flex {
      display: flex;
    }

    属性値で見た目の情報を変える

    HTML の<input>readonlydisabledといった属性を渡すとスタイルが変わるようにaria-*data-*といった属性を振り、それでスタイルを振り分けます。

    const Flex = styled.div`
      display: flex;
    
      &[aria-orientation='vertical'] {
        flex-direction: column;
      }
    `;
    
    const FlexItem = styled.div`
      flex: 0;
    
      & + & {
        margin-left: 1em;
      }
    
      &[aria-label='main'] {
        flex: 0 1 1000px;
      }
    `;
    
    // <Flex>
    //   <FlexItem>...</FlexItem>
    //   <FlexItem aria-label="main">
    //     <Flex aria-orientation="vertical" />
    //   </FlexItem>
    // </Flex>

    使えそうなaria-*属性一覧

    追記 (2019-01) - 使えるaria-*属性一覧

    と思いましたが、要素にはもともとデフォルトでroleが割り当てられていて、このroleにこのaria-*は相応しくないというようなルールがあるようです。なので適当に上の属性を当てているとその辺りで Lighthouse のアクセシビリティのスコアが結構下がってしまいます。

    僕的にはaria-*を使ってのスタイル制御は分かりやすく管理しやすいと思っているので、ariadataに変えたもの(data-hidden)を今は使っています。

    React と AtomicDesign - Molecule編

    中で Atom を作ってある程度の塊のブロックを返す関数が Molecule と考えます。

    // 定義
    const createLoginForm = (fields: Field[]) => {
      const LoginForm = () => (
        <Form>
          {fields.map(field => {
            return <Input key={field.name} />;
          })}
        <Form>
      );
    
      LoginForm.displayName = 'LoginForm';
    
      return <LoginForm />;
    }

    例えば Molecule を使う時はこのように、単に変数埋め込み記法を使うだけです。

    class extends React.Component {
      render() {
        const loginForm = createLoginForm([/* ... */]);
    
        return (
          <Flex>
            {loginForm}
          </Flex>
        )
      }
    }

    Molecule の定義の仕方

    tbw

    React と AtomicDesign - Template編

    Template は Webページの間取り

    Templateは、間取りです。家で例えるならこの部屋はこういうことに使おう、あっちの部屋はああいうことに、と決めるような感じです。Webであれば、「header要素はこの中に」「メイン要素はこの中に」となると思います。

    そしてベッドなら寝室に置くでしょうし、ブログ記事ならメイン要素に大抵は置くだろうと思いますが、Template層はそういうブログ記事をレイアウトを意識せずにメイン要素に配置できるようにするのが役割です。

    Template は Abstract Class

    僕は、abstract classを使う方法がお気に入りです。

    abstract class DetailTemplate extends React.Component {
      abstract Header(): JSX.Element;
      abstract Main(): JSX.Element;
    
      render() {
        return (
          <ColumnLayout>
            {/* 上部に固定されるOrganism要素 */}
            <StickyMenu>
              <this.Header />
            </StickyMenu>
    
            <this.Main />
          </ColumnLayout>
        )
      }
    }

    継承したコンポーネントでHeaderを定義すると、それは勝手にColumnLayoutStickyMenuの下に配置されレイアウトのマークアップを意識せずに良くなります。 また、これを継承した時にHeaderMainを定義しなければコンパイルエラーにできるので、定義し忘れ防止もできます。

    Page層ではこのように継承して使います。

    class FooPage extends DetailTemplate {
      Header = () => <h1>タイトル</h1>;
    
      // `this.props.article` はreduxなどで渡ってきた値
      Main = () => <Article article={this.props.article} />;
    }

    ちょっとはシンプルになると思います。

    JSXの中にインラインスクリプトを埋め込む

    インラインスクリプト

    こんな感じのインラインスクリプトを用意します。以下はGoogleAnalyticsの埋め込みタグです。

    <script async src="https://www.googletagmanager.com/gtag/js?id=あなたのid"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'あなたのid');
    </script>

    JSXに変換

    dangerouslySetInnerHTMLattrに__html: 'インラインスクリプト'を持ったオブジェクトをセットします。

    <script async src="https://www.googletagmanager.com/gtag/js?id=あなたのid" />
    <script
      dangerouslySetInnerHTML={{
        __html: `
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'あなたのid');
        `
      }}
    />

    dangerouslySetInnerHTML.__htmlは最初気持ち悪く感じるかもですが、こういうものなのでしょうがないです。
    中身ないので/>にするのも忘れずに書き換えます。

    Enzyme で .simulate() した時に子要素の props が更新されない問題

    バージョンなど

    {
      "devDependencies": {
        "enzyme": "^3.7.0",
        "jest": "^23.6.0"
      }
    }

    以下は2番目のItemをクリックすると、次の状態ではaria-selected={true}となる事を確認しようとしていたテストです。しかし、これは通りません。

    const secondItem = mount(Component)
      .find(Item)
      .at(1);
    secondItem.simulate('click');
    expect(secondItem.prop('aria-selected')).toBeTruthy();

    解決

    チェックする時に再度wrapperから対象の要素を取得したものを使えば更新された状態のItemが取れます。こうすれば、通ります。

    const secondItem = mount(Component)
      .find(Item)
      .at(1);
    secondItem.simulate('click');
    expect(
      wrapper
        .find(Item)
        .at(1)
        .prop('aria-selected')
    ).toBeTruthy();

    おまけ

    一応setTimeoutを使っても通るよおです。

    const secondItem = mount(Component)
      .find(Item)
      .at(1);
    secondItem.simulate('click');
    setTimeout(() => {
      expect(
        wrapper
          .find(Item)
          .at(1)
          .prop('aria-selected')
      ).toBeTruthy();
    }, 0);

    React の型周りでめちゃくちゃエラーがでるようになった

    もしかしたら色々な@types/reactのバージョンが混在しているからかもしれません。

    @types/react のバージョンが混在していないか調べる

    以下のコマンドを実行してみてください。

    yarn list --pattern @types/react

    もし2階層も3階層も表示される場合は混在しています。

    これを解決するにはプロジェクトルートで以下を実行します。

    rm -rf node_modules yarn.lock && yarn cache clean && yarn

    再度yarn listします。1階層になっていれば大丈夫だと思います。

    yarn list --pattern @types/react

    ただReactの昔のバージョンを使ってるときは、これでは駄目な気がします。その時が訪れたら追記するかもしれません。