Reactコンポーネントを単独で使うための細かいテク
前回のReactコンポーネントを単独で使うに書いたようにしばらくやってみて、細かいところのいい感じのやり方がわかってきた。
ディレクトリ構成
└── src/ ├── components/ │ ├── react/ │ │ ├── AwesomeApp.js │ │ └── Disclosure.js │ ├── AwesomeApp.js │ ├── Disclosure.js │ └── GlobalNavigation.js └── main.js
初期化を担うファイルはcomponents/
の直下に、Reactコンポーネントはcomponents/react/
に配置する。Reactコンポーネントを初期化する処理は、components/
にファイルを作成した上でそこに書く。Reactじゃないコンポーネントも同じディレクトリに配置する。
stateを制御するコンポーネントでラップする
プレゼンテーショナルなReactコンポーネントには、トップダウンで状態を渡すことが多い。
const Disclosure = ({ isExpanded, onToggle }) => ( <div className="Disclosure"> <button className="Disclosure__toggle" type="button" aria-expanded={String(isExpanded)} onClick={() => onToggle(!isExpanded)} > toggle </button> <div className="Disclosure__content" hidden={!isExpanded} > {children} </div> </div> )
これを単独で機能させるというのをやりやすくするために、簡単にstateを管理できるコンポーネントを作る。あるいはreact-valueがちょうど良い。
import React from 'react' import ReactDOM from 'react-dom' import { Value } from 'react-value' import Disclosure from './react/Disclosure' export const init = () => { document.querySelectorAll('.react-Disclosure').forEach((containerEl) => { const initialExpanded = containerEl.dataset.open === 'true' ReactDOM.render( <Value defaultValue={initialExpanded} render={(value, onChange) => ( <Disclosure isExpanded={value} onToggle={onChange} /> )} /> containerEl, ) }) }
クラス名の命名規則
ReactコンポーネントをReactDOM.render()
で描画する以上、マウントする対象の要素が必要になる。そのため、単独で利用するReactコンポーネントには必ずラッパー要素ができる。
コンポーネントのクラス名が.Disclosure
の場合、そのラッパー要素のクラス名は.react-Disclosure
にする。このラッパー要素は、利用方法によって存在したりしなかったりするので、あってもなくても同じ振る舞いをするように実装する。
ラッパー要素のスタイル宣言は、内包するコンポーネントと同じファイルに記述する。
_Disclosure.scss
:
.react-Disclosure { ... } .Disclosure { ... }
data-*
属性をコンポーネントに渡すpropsとして利用する
上記の例でもそうしたけど、ラッパー要素のdata-*
属性にReactコンポーネントへ渡すpropsを設定しておくと便利。
<div class="react-Disclosure" data-open="true"></div>
propsへHTMLを渡したいときは、例えばPugなら次のようにすればいい。
div.react-Disclosure(hidden) ul li foo li bar
innerHTML
をpropsに渡しつつ、描画が終わったらhidden
属性を取り除く。あまりきれいじゃないけど。
配列やオブジェクトを渡したいときは次のようにする。
- const array = JSON.stringify(['foo', 'bar', 'baz']) - const object = JSON.stringify({k: 'val'}) div.react-MyComponent(data-array=array data-object=object)
data-*
属性の値をJSON.parse()
してpropsに渡す。無駄感はあるけど、テンプレートの管理の楽さとかを考えるとまあこれでいいかなという感想。