• ..

Python

    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 />のように使う必要があります。

    TypeScript と共に使う時に className にリテラル型のユニオンを設定する

    jsx で何の気なしにclassName属性を使っている時の型はReact.HTMLAttributesの中にclassName?: stringと定義(@types/react@16.9.2では@types/react/index.d.ts:1628辺り)されています。

    また例えば<div>タグを使った時の型はdiv: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;と定義(JSX.IntrinsicElements@types/react/index.d.ts:2858辺り)されています。

    ということで、実質React.HTMLAttributesが使われているということで、それを自分で上書きしてあげれば良さそうに感じますがdelare module 'react'で定義したとしても既存のプロパティの解決はデフォルトのもの(@types/react)が優先して使われるようなので無理です。

    ではどうするかと言うと、JSX.IntrinsicElementsからすべて再定義してあげます。まずは既存のJSX.IntrinsicElementsをすべてコピーしdelare module 'react'内に置きます。

    declare module 'react' {
      namespace JSX {
        interface IntrinsicElements {
          a: React.DetailedHTMLProps<
            React.AnchorHTMLAttributes<HTMLAnchorElement>,
            HTMLAnchorElement
          >;
        }
      }
    }

    あとはこのReact.AnchorHTMLAttributesを更に再定義してあげてあげれば目的は果たせそうです。ですがReact.AnchorHTMLAttributesaの場合だけで、tableだったらReact.TableHTMLAttributesinputだったらReact.InputHTMLAttributesだったりと数が多くすべてに対応していたらそれだけで一苦労です。

    TypeScript には、

    interface A {
        foo?: string;
    }
    
    interface B {
        foo: 'foo'
    }
    
    type C = A & B;
    // C = {foo: 'foo'};

    になるという法則があります。そしてすべてのReact.*HTMLAttributesReact.HTMLAttributesを拡張したものなので、React.HTMLAttributesを継承したものをジェネリクス型として受け取れる何か型を定義し、{className: 自分の型;}とすればいけそうです。

    declare module 'react'の中にこのようなものも定義してみました。

    type MyHTMLAttributes<T extends React.HTMLAttributes> = T & {
      className?: 'foo' | 'bar';
    }

    やっていることは前のコードと同じstring | undefined'foo' | 'bar'へ変換することです。あとはすべてのReact.*HTMLAttributesMyHTMLAttributesで囲めば目的が果たせそうです。

    VSCode 以外はできるか分かりませんが、できるなら以下の組み合わせで一気に変換すると楽かなと思います。

    
    \s(React.*HTMLAttributes(?!>)<[^,]*)
    # から
     MyHTMLAttributes<$1>
    # 先頭にスペース注意

    React と AtomicDesign - Atom(原子)編

    すべて個人的な考えです。都度更新。

    使用される要素の最小サイズが Atom(原子)

    よく例にあるのがボタン(<button>ボタン</button>)です。これは「クリックで何かしらアクションを起こす」という役割だけの要素だからです。

    <pre><code>...</code></pre>はコードを表示するための構成で、2つの HTML 要素で構成されています。これは Atom なのかすが、僕は Atom に分類しています。2つの要素を使っていますが、単にコードの入れる場所って役割以外に使うことないからです。

    Atom と Atom に分類し使うこともできると思いますが、それらを単体で使わないのであれば恐らく単に細分化しすぎで、やはりatom + atom = atomとなるんじゃないかと思います。「この役目を果たすための最小サイズのラインはどこだろう?」と分類する感じで考えています。

    OOCSS

    OOCSSというのはオブジェクト指向CSSと呼ばれるCSS設計方法で、スタイル付けをスキン(見た目)とストラクチャ(構造)に分けて考えようという設計方法です。スキンは色とか装飾系だけをまとめたセレクタのことで、ストラクチャは幅、高さ、余白とか見た目以外のスタイルをまとめたセレクタです。

    Atom では「できる」を保証します。実際にどんな見た目になるかは上位層が決めます。
    フォームの中では幅100%だけどヘッダーでは300pxで使いたい<input>など、こういうものは上位層でそういった構造スタイルを付けるようにします。

    // molecule
    const SearchOfTopMenu = () => {
      return (
        {/* ... */}
        <Input structure="fluid" skin="standard" />
        {/* ... */
      );
    }

    Atomic では上位層に行くほど使いまわしの回数は減るはずで、どんどん具体的な内容にもなっていくかなと思うので、具体的になってから初めて構造は付ければ良いかなと思います。

    大抵デザインから Atomic を作ろうとするとあるレベルで要素をどんどん分解して最後に組み立てるだけとなってしまいがちですが、コンポーネントを作る段階ではなるべく「この要素はここで使うから」みたいな具体的な背景も頭の中から落とすよう(考えないよう)にするといいかもしれないなと思います。

    ロジックを持って、状態は持たない

    これはHTMLの要素の動きに近いです。

    ロジックは持ってもいいです。disabledという属性値を持たせて、これが真ならアクションを起こせなくするや(まぁdisabledなら CSS で制御できそうですが)、loadingtrueならテキストをローディングアイコンにするような簡単なものなど。ただしそれらを状態として保持しないようにします。

    const PostButton = ({loading = false, disabled = false, children}) => {
      return (
        <button className="..." disabled={disabled}>
          {loading ? <svg /* ローディングアイコン */ /> : children}
        </button>
      );
    };
    //
    // 使う
    // <PostButton loading disabled>投稿</PostButton>

    HTML でも<input disabled placeholder="...">などで Atom 単位である程度動的に制御できるように作られています。disabledを消せばまた入力できるようになるでしょう。ロジックを持って状態は持たないのは自然だと思います。

    Functional Component で定義

    実装する際は、あとで拡張しやすいように最低でも Functional Component として定義するといいかと思います。あと、TypeScript を使っている場合は自分の型で完結させたほうが後々変にならないだろう(少しは)とも思います。


    ここから2019-06までの内容(アーカイブ)


    <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)をよしなに組み合わせるための Molecule (分子)

    よくフォームで例えられていたりしますね。例えばこんな構造だとします。

    <SearchForm structure="top-menu" skin="standard">
      <Input structure="round" skin="standard" />
      <Button structure="round" skin="standard">検索</Button>
    </SearchForm>

    SearchFieldInputButtonが Atom です。OOCSS で考えてるのでstructureskinを持っています。しかし、それぞれのコンポーネントは子要素や兄弟要素との関連性をよく分かっていない要素達です。そういった要素たちのスタイルをよしなに組み合わせる為に Molecule 層で整えます。

    例えばInputButtonは今の状態ではピタッとくっついている状態だとして、間にスペースを開けたいんだという場合はこんな感じにします。(ちなみに、styled-componentsです)

    const Molecule = props => {
      const buttonSkin = React.useMemo(() => {
        if (props.value === '') {
          return 'disabled'
        }
    
        return 'enabled';
      }, [props.value]);
    
      return (
        <div style={{marginTop: 10}}>
          <SearchForm structure="fluid" skin="standard">
            <Input structure="round" skin="standard" value={props.value} />
            <div style={{marginLeft: 10}}>
              <Button structure="round" skin={buttonSkin}>
                検索
              </Button>
            </div>
          </SearchForm>
        </div>
      )
    };

    マージン用のdivでラップして空間を整えています。(これはstyleにしてますが、実際はコンポーネント化したものを使います)
    この辺は人の好みはあると思いますが、個人的にはマージンは要素外の要因だと思っているのでそれぞれの Atom で持つのはちょっと...という考えだからです。ちゃんとしたデザインならstructureで持たせるのもありがと思いますが、来るデザインによってはマージンとかバラバラだしうまく使い回せない事が多いのでこうなりました。

    ともかくこれでInputButtonの間には10pxのスペースができました。

    Atom は要素の中に対して、Molecule は要素の外、つまりマージンや位置情報など他要素と関連し合うものをはここで設定しましょう。と考えると Template に近いものがあります。

    計算済みの値を生成してもいい

    例えば上記のサンプルコードのskin={buttonSkin}辺りがそれで、input用に渡した値を元にbuttonの見た目を変更しています。propsで渡した値をskinに渡せるようにしているのでアダプタ的な動きが近いかもしれません。

    要素間のレイアウトを整えるものなので、それら要素間の依存する関係の値は持っても混乱は少ないと考えてます。またここでも状態を持たないので、Molecule と Atom の区別はそれほどしなくてもいいかもしれません。

    Functional Component で定義

    実装する際は最低でも Functional Component として定義するといいかと思います。複数の要素を扱うようになってくるのでプロパティの更新がなければ再レンダリングしないなどの制御も必要になってくるかなと思います。

    React と AtomicDesign - Organizm(有機体)編

    すべて個人的な考えです。

    Atom や Molecule (や Organizm)をよしなに組み合わせるための Organizm (有機体)

    Molecule と同じように。Atom や Molecule そして Organizm 要素をよしなに組み合わせて配置できるようにします。

    この層では中に Atom や Molecule そして Organizm を配置できます。

    データを取ってきて小要素に分配する

    この層では子要素(Molecule や Atom)にレンダリングに必要なデータを渡してあげる役割もあります。従って、この層は小要素は何を欲しているかすべて知っている必要があり、それを元にデータを取ってくる必要があります。

    それは例えばre-ducksの考えを取り入れていてreduxを使っているプロジェクトであれば、useSelectorを使って直接持ってくることができます。

    データを取ってくるのは Page じゃないのかみたいな考えもあると思いますがコンポーネントの大きさによっても変わってくるかなと思います。例えば、全体をぼんやり見ているだけじゃただの石だけど、近くでじっくり見るとキラキラしていて綺麗みたいな(耳をすませばのアレ)

    もちろん Page でもデータを取ってきていいのですが、Page は Page で必要なものだけ、それ以外のじっくり見た時の追加情報はそれぞれ Organizm 層でも取ってきてもいいんじゃないかと思います。

    これは Page 層の肥大化を避けるという目的も大きいです。それは、Page 層でのレンダリングの制御が複雑になる事や、データを細部にまで行き渡す為にかなりの階層に伝えていかなければならない事を避ける狙いがあります。

    React と AtomicDesign - Template編

    すべて個人的な考えです。

    Template は 間取り

    Template 層は間取りです。例えば台所で考えると「冷蔵庫はここに置こう」「テーブルはここで」みたいに事前に決め、実際に電化製品から冷蔵庫が届いたら決められた場所に置いてもらうだけです。ただ間取りを考える人と実際に運んでいる置いてもらう人は別人です。個人的に Template 層は配置する人が迷わなくても決められた所に置けるようにすることだと思います。

    Organizm 以前との違いですが、個人的に Organism などは順番に埋まっていく、 Template は狙った位置にストンと入れるみたいなイメージの違いがあると思います。

    Template は何で定義してもいい

    ただ僕は TypeScript にしかないですが、abstract classを使う方法がお気に入りです。

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

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

    この層はデータに依存することがあまりないようにできると思うので、狙った位置に置けるJSX.Elementを返すただの関数でもいいと思います。

    React と AtomicDesign - Page編

    すべて個人的な考えです。

    Template を使って適切な位置に要素を配置する

    例えば以下のDetailTemplateは Abstract Class で、HeaderMainは Abstract Property です。そしてそれらコンポーネントは、間取りはどんな感じか分からないですが、とりあえず適切な位置に配置されるようになっています。

    class FooPageContents extends DetailTemplate {
      // H1 は Atom
      Header = () => <H1>タイトル</H1>;
    
      // Article は Organizm
      Main = () => <Article in="foo" />;
    }
    
    const FooPage = () => {  
      return <FooPageContents />;
    }

    様々な副作用を担当

    例えば、<header>の中身を変更するとかもこの層の役目です。

    const FooPage = () => {
      React.useEffect(() => {
        document.title = 'foo - site'
      }, []);
    
      return <FooPageContents />;
    }

    ちなみにNextJSならマークアップだけで済みます。

    import Head from 'next/head';
    
    const FooPage = () => {  
      return (
        <>
          <Head>
            <title>foo - site</title>
          </Head>
          <FooPageContents />
        </>
      );
    }

    Redux を使っているなら Observer や共通するイベントの処理はこの層で適切にdispatchすることで管理し、各 Organizm や Molecule でそれらを乱立しあまり負荷を掛けない作りにすると良いと思います。

    import ObserverContext from '...';
    // const ObserverContext = React.createContext();
    
    const FooPage = () => {
      const observer = React.useMemo(() => {
        return new IntersectionObserver(entries => {
          /*...*/
        });
      }, []);
    
      React.useEffect(() => () => {
        observer.disconnect();
      });
    
      return (
        <ObserverContext.Provider value={observer}>
          <FooPageContents observer={observer} />
        </ObserverContext.Provider=>
      )
    }

    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の昔のバージョンを使ってるときは、これでは駄目な気がします。その時が訪れたら追記するかもしれません。

    react-dnd の使い方

    準備

    HOC なDragDropContextで親コンポーネントをラップするか、DragDropContextProviderを親コンポーネントマークアップに含める必要があります。ただし、DragDropContextProviderは、react >= 16.0.0かつreact-dnd >= 4.0.0なバージョンである必要があります。

    またブラウザ上で動作させる場合react-dnd-html5-backendというパッケージも必要です。イベントハンドラーのようなものがまとまったもので、これを使って色々解決する感じのようです。react-dndreact-dnd-html5-backendは基本同じバージョンを使うようにしましょう。

    ルートとしては以下のようになります。

    DragDropContextProvider

    import React from 'react';
    import {DragDropContextProvider} from 'react-dnd';
    import HTML5Backend from 'react-dnd-html5-backend';
    
    class App extends React.Component {
      render() {
        return (
          <DragDropContextProvider backend={HTML5Backend}>
            <div>なんでもいい</div>
          </DragDropContextProvider>
        );
      }
    }
    
    export default App;

    DragDropContext

    import React from 'react';
    import {DragDropContext} from 'react-dnd';
    import HTML5Backend from 'react-dnd-html5-backend';
    
    class App extends React.Component {
      render() {
        return <div>なんでもいい</div>;
      }
    }
    
    export default DragDropContext(HTML5Backend)(App);

    少し分かりづらいですが、this.propsの値などは増えたりしないので注意です。

    Drag and Drop (dnd)

    Drag イベントを設定したいならDragSource、 Drop イベントを設定したいならDropTargetHOCを使います。どちらもreact-dndから提供されています。

    共通項目

    どちらとも第1引数にはtypeを指定します。これは処理をつなげる為に使われます。Fooというタイプの Draggable 要素をFooというタイプの Droppable 要素へドロップした時に初めてドラッグからドロップという一連の処理が実行されます。

    第3引数のconnectreact-reduxconnectと同じような動きです。react-dndで管理している状態(state)をコンポーネントのpropsへ渡す為に使います。

    第2引数のspecは関数をまとめたオブジェクトを渡します。これらのキー名はreact-dndが指定している名前にする必要があり、その一部は必須項目になっています。

    react-dndの関数の引数にあるpropsは HOC の対象となるコンポーネントのpropsの値、monitorsは、react-dnd.github.io/r/d/a/drag-source-monitorにある内容がオブジェクトになった形のものが入っています。
    propsなどは対象先ですが、monitorsには対象元が入っているような感じです。

    DragSource

    specには、beginDrag(props, monitor, component), endDrag(props, monitor, component), canDrag(props, monitor), isDragging(props, monitor)が設定でき、beginDragだけは必須項目です。それぞれ、

    beginDragはドラッグが始まったタイミングに発火し、endDragはドラッグが終わったタイミングに発火します。beginDragendDrag戻り値は、monitor.getItem()で取り出せるようになります。

    canDragはドラッグできるかの動的制御に使います。

    HOC の対象となるコンポーネントでは最小限、connectconnect.dragSource()を受け取り Drap 対象とするJSX.Elementをその関数でラップする必要があります。

    import {DragSource} from 'react-dnd';
    
    DragSource(
      'BLOCK',
      {
        beginDrag(props) {
          console.log('beginDrag');
          return props;
        }
      },
      connect => {
        return {
          connectDragSource: connect.dragSource()
        };
      }
    )(
      class extends React.Component {
        render() {
          return this.props.connectDargSource(
            <div style={{style: 'orange', width: 100, height: 100}} />
          );
        }
      }
    );

    DropTarget

    先程 Drop 側のtypeBLOCKで指定したのでコチラ側もBLOCKを指定します。

    こちらのspecは、drop(props, monitor, component), hover(props, monitor, component), canDrop(props, monitor)となっており、canDropは概ねcanDragのものと同じです。
    こちらには必須となっている項目はありません。

    dropは Dragable 要素を Droppable 要素の上で話した時に実行され、hoverは重なっている間連続で実行されます。

    またこちらでは最小限、connectconnect.dropTarget(),を受け取り Drop 対象とするJSX.Elementをその関数でラップする必要があります。

    import {DropTarget} from 'react-dnd';
    
    DropTarget(
      'BLOCK',
      {
        drop(props) {
          console.log('drop');
          // onDrop は `<Foo onDrop={() => {}} />`
          // のように渡したもの
          // props.onDrop();
        }
      },
      connect => {
        return {
          connectDrogTarget: connect.dropTarget()
        };
      }
    )(
      class extends React.Component {
        render() {
          return this.props.connectDrogTarget(
            <div style={{style: 'orange', width: 100, height: 100}} />
          );
        }
      }
    );

    Drag も Drop もできる要素

    connect.dragSource()connect.drogTarget()2つでラップしてあげればいけます。

    const Draggable = DragSource(/* ... */);
    const Droppable = DropTarget(/* ... */);
    
    const DNDCompoennt = Droppable(
      Droppable(
        class extends React.Component {
          render() {
            const {connectDrogTarget, connectDragSource} = this.props;
    
            return connectDropTarget(
              connectDragSource(
                <div style={{style: 'orange', width: 100, height: 100}} />
              )
            );
          }
        }
      )
    );

    仕上げ

    DragDropContextProviderより深い部分かDragDropContextを適用したコンポーネント以下の部分で使うだけです。

    class App extends React.Component {
      render() {
        return (
          <DragDropContextProvider backend={HTML5Backend}>
            <DNDComponent
              onDrop={() => {
                console.log('drop');
              }}
            />
          </DragDropContextProvider>
        );
      }
    }

    react-transition-group の使い方

    react-transition-group@^2で提供されているコンポーネントは

    • Transition
    • CSSTransition
    • TransitionGroup

    の3つです。

    Transition

    Transitionは以下のようなプロパティを取ります。

    childrenには(status: 'entering' | 'entered' | 'exiting' | 'exited') => JSX.Elementが渡されます。このstatusの状態を使ってクラス名や何かしらの属性を内部の要素に設定し、CSSなどを使ってアニメーションさせることができます。

    inは内部コンポーネントを表示するかどうかです。infalseの時はexitingからexitedに向かい、trueの時はenteringからenteredに向かいます。1番最初は単に-ingなしでenteredexitedが直接設定されます。

    timeoutは、status-ingである状態の時間(msec)を指定します。これはaddEndListenerを設定しない場合は必須になります。
    addEndListenerは動的にtimeout時間を設定したい場合に使えます。例えば CSS のtransitionでアニメーションするような場合、その終わりにtransitionendイベントが発火しますが、その発火するまでの時間を使うことでtimeoutの代わりとすることができます。(修正箇所も減る)これは第一引数にchildren関数での戻り値の要素が渡り、第二引数では-ingの終わりということになるdone関数が渡ってきます。この渡ってきたルート要素からアニメーションする要素を取得し、transitionendイベントでdoneを読んで、-ingを終えるといったことができます。注意点として、このaddEndListenerinが変わるたびに毎回実行されます。そのためイベントの登録などをしている場合、実行回数分登録することになってしまうので、毎回doneを呼ぶタイミングでイベントの解除模するといいと思います。

    mountOnEnterintrueになるまでマウントしなかったり、unmountOnExitexitedになる度アンマウントする為のプロパティです。消える時はアニメーションしなくていいみたいな場合に使うといい感じです。

    またonEnteredonExitedなどを渡して完了した時に連動してある処理を行うこともできます。ほかに、

    • 直前の onEnter, onExit
    • -ingonEntering, onExiting

    があります。

    CSSTransition

    内部的にはある程度設定済みのTransitionです。与えられたTransitionと同様のintimeoutやこのコンポーネント独自のclassNamesを元に主により高機能なクラス名の管理を行ってくれます。与えたclassNamesにサフィックスとして-enter, -enter-active, -enter-done, -exit, -exit-active, -exit-done, -appear, -appear-activeがルート要素に付くようになるので、それらのクラス名を使って CSS でアニメーションを設定します。

    サフィックスの名前は以下のような状態だと思うといいかなと思います。

    • -enter-exitTransition-ingが付く直前に付き、-doneが付く時取り除かれる
      • またはonEnteronExitは呼ばれたタイミングからonEnteredonExitedが呼ばれるタイミングまで
    • -enter-active-exit-activeTransition-ingの別名
    • -enter-done-exit-doneTransition-edの別名

    TransitionGroup

    1つ1つがTransitionを持つような配列要素をレンダリングしたい時にそれらをラップするように使うだけです。注意点は2つあります。まず、各配列のルート要素はTransitionである必要があります。2つ目はTransitionにはinではなくkeyが必須です。またこれはユニークな値である必要があります。inTransitionGroupkeyから有無を判定して自動で設定してくれるので考えないで済みます。

    import {TransitionGroup} from 'react-transition-group';
    
    (() => {
      <TransitionGroup>
        {items.map(item => {
          return 
            <CSSTransition key={item.id}>{
              status => {
                /* ... */
              }
            }</CSSTransition>
          );
        })
      </TranitionGroup>
    })();

    svgr の使い型

    svgr.svgファイルから React コンポーネントに楽に変換できる CLI ツール(モジュール)です。

    使う準備

    とりあえず以下で bin のインストールが必要です。

    yarn add -D @svgr/cli

    Twitter ロゴの SVG を変換

    こんな<svg>twitter.svgという名前で用意しました。

    <svg
      aria-hidden="true"
      data-prefix="fab"
      data-icon="twitter"
      class="svg-inline--fa fa-twitter fa-w-16"
      role="img"
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 512 512"
    >
      <path
        fill="currentColor"
        d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
      ></path></svg
    >
    

    このファイルに対して変換を行ってみます。

    2つのオプションがついています。   --icon<svg>width:1emheight:1emを付けるオプションで、--replace-attr-valuesはマッチした属性値を渡した値で上書きするオプションです。

    npx @svgr/cli --icon --replace-attr-values "currentColor=#46a1eb" twitter.svg
    
    # ファイルに出力
    # yarn add -D @svgr/cli
    # yarn --silent svgr --icon --replace-attr-values "currentColor=#46a1eb" twitter.svg > svg-twitter.svg

    以下のような結果になりました。

    import React from "react";
    
    const SvgTwitter = props => (
      <svg
        aria-hidden="true"
        data-prefix="fab"
        data-icon="twitter"
        className="twitter_svg__svg-inline--fa twitter_svg__fa-twitter twitter_svg__fa-w-16"
        viewBox="0 0 512 512"
        width="1em"
        height="1em"
        {...props}
      >
        <path
          fill="#fff"
          d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
        />
      </svg>
    );
    

    svg の中身を React 内で触りたい場合

    例えば Snapsvg などで SVG の中の要素を触りたい時に必要かなという設定です。

    svgrコマンドをそのまま使うとsvgoによる最適化が行われてしまい、望んだ形で変換できない時があるかもしれません。その場合は.svgo.ymlという設定ファイルをプロジェクトルートへ置き、設定を書くことで直すことができるかもしれません。

    一部タグがおかしい

    デフォルトでは<polygon points="...">といったタグは<path d="...">に変換されます。これを防ぐには、convertShapeToPathを切ります。

    plugins:
      - convertShapeToPath: false

    一部タグが消える

    僕が遭遇したものだと、<clip-path>が消えました。これは以下の2行を設定することで消されることを防げました。

    plugins:
      - cleanupIDs: false
      - removeUnknownsAndDefaults: false

    TypeScript で出力した .jsx を読めるように

    以下のようなファイルをaaa.jsxであれば、aaa.d.tsとなるように作成し、SVGComponentexportしている名前に変換します。

    import React from 'react';
    
    declare const SVGComponent: React.SFC;
    export default SVGComponent;

    これでimport Aaa from './aaa';などで.tsxに読み込めます。