この記事は and factory Advent Calendar 2020 の17日目の記事です。
昨日は @go_sagawaさんのデータベース設計書を常に最新する仕組みを整えるでした。
データベース設計書などの開発ドキュメントと本環境の状況の整合性がなくなり、ドキュメントがゴミになっていくのはサービス開発の中で広く一般的にありがちなことだと思いますが、上記記事で紹介されている自動で差分をGoogleスプレッドシートに反映できる仕組みはドキュメント問題の「整合性を保つ」を圧倒的な確度と労力のなさで実現できます。多くのチームで検討されるべき方法だと思いますので是非ご確認ください。(Googleのサービスが止まることなどめったにないわけですし1)
概要
この記事は、React, React Hooks入門者による入門メモです。
表題にあるように「Component から stateを伴ったロジックを切り出す技術たち」を扱います。
対象者は、React, React Hooks入門者です。
具体的には、関連技術の簡単な歴史的経緯の整理とそれを踏まえたコード確認をおこないます。
背景
以前触っていた頃(2017頃)より、JavaScript自体もさらに進化し、TypeScriptがより浸透し、Reactにも新機能が追加されていて、弊サービスのフロントをReactに一新するリニューアル計画もあり、最近React再入門していました。
今回は、一見するだけで再利用性や可読性の向上などに大きな影響がありそうな2019年に出たばかりのReactの新機能Hooksと関連する技術について整理します。
Component から stateを伴ったロジックを切り出す技術たち
Hooks というのは、「コンポーネントから状態やロジックの分離」を行うための機能です。
Hooks は2018年に発表され、2019年から使われている技術です。つまり、それより以前は「コンポーネントから状態やロジックの分離」する場合、別の手法が取られていたということです。歴史上、いくつかの主流な方法があり、それらが利用される中で重ねられた議論を経て、HooksがReact標準機能として実装されるに至ったようです。
以下にそれらの技術を mixins, HOC, render props, Hooks の順に整理していきます。
mixins
mixins | |
---|---|
特徴 | 1. React.createClass()でクラスコンポーネント生成時にmixinsプロパティに状態処理を切り出した各mixinオブジェクトを登録する。 2. コンポーネントとメソッドに結局依存が発生しやすく、各mixin間での名前衝突も起きやすい 3. Vue は3.0でもmixinsが継続していて、同じ課題を抱えている |
わかりやすい。名前衝突が起きやすいらしい。大規模サービスには向いてなさそう。
↓mixinsプロパティを使ったいにしえのカウンターのコンポーネントと状態管理・処理
// mixins
const CounterMixin = {
getInitialState: () => ({ count: 0 }),
reset: () => {
this.setState({ count: 0 });
},
increment: () => {
this.setState((state) => ({ count: state.count + 1 }));
},
};
const CounterComponent = React.createClass({
mixins: [CounterMixin],
render: () => {
const {count} = this.state;
return (
<div>
<p>
{count}
</p>
<button onClick={reset} type="button">
Reset
</button>
<button onClick={increment} type="button">
+1
</button>
</div>
);
},
});
HOC
HOC | |
---|---|
特徴 | 1. 高階コンポーネント(Higher Order Component) コンポーネントを引数に受けて、戻り値にコンポーネントを返すもの。 2. mixins とちがって状態とロジックをきれいに切り分けられる 3. HOC ライブラリ"Recompose"を使うと、クラスコンポーネントではなく(隠蔽した状態で)関数コンポーネントでstateやライフサイクルメソットを利用できる。 |
ライブラリ"Recompose"を使うと、クラスコンポーネントをいちいち自分で書かずに関数コンポーネントで実装できる。という辺りが Hooks に似ている。
// HOC
function withCounter(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.reset = this.reset.bind(this);
this.increment = this.increment.bind(this);
}
reset() {
this.setState({ count: 0 });
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<WrappedComponent
count={this.state.count}
increment={this.increment}
reset={this.reset}
{...this.props}
/>
);
}
};
}
function WrappedComponent({ count, increment, reset }) {
return (
<div>
<p>{count}</p>
<button onClick={reset} type="button">
Reset
</button>
<button onClick={increment} type="button">
+1
</button>
</div>
);
}
const Counter = withCounter(WrappedComponent);
function App() {
return (
<div>
<Counter />
</div>
);
}
Render Props
Render Props | |
---|---|
特徴 | 1. React Elementsを返す関数をprops(=render props)として受け取ってそれをレンダリングするコンポーネント利用する方法 2. mixins とちがって状態とロジックをきれいに切り分けられる 3. HOCより優位性がある設計パターンだと主張されていたものの,大きな差異がなく論争が続いた |
Render Props は HOC の対抗馬として、提唱された設計パターン2
この時期サーバーサイドをメインにしていて、React界隈をちらちら見るたびに「入門コードの書き方違うな」「フロントは動きが速いな」と感じていたのですが、この頃はコミュニティ内でも設計パターンで論争があり、一筋縄にはいかない状況だったのだなと今回知りました。
↓根本の発想は HOC と同じで count, increment, reset という props に外側のコンポーネントから状態やロジックを注入できるようにして、状態とその処理を切り分ける。
// Render Props
class CounterProvider extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.reset = this.reset.bind(this);
this.increment = this.increment.bind(this);
}
reset() {
this.setState({ count: 0 });
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
{this.props.children({
count: this.state.count,
reset: this.reset,
increment: this.increment,
})}
</div>
);
}
}
function Counter() {
return (
<CounterProvider>
{({ count, increment, reset }) => (
<div>
<p>{count}</p>
<button onClick={reset} type="button">
Reset
</button>
<button onClick={increment} type="button">
+1
</button>
</div>
)}
</CounterProvider>
);
}
function App() {
return (
<div>
<Counter />
</div>
);
}
Hooks
Hooks | |
---|---|
特徴 | 1. render props や HOC のような設計パターンでなく、Reactの機能。 2. コンポーネント内で、Hooks という関数群3を利用し、状態とその処理を完全にコンポーネントの外に配置できる。かねてからの依存性が排除され、再利用性が高まった。 3. クラスコンポーネントを用いず、関数コンポーネントだけでアプリケーションをつくれる。公式的にも、Reactの実装は今後クラスコンポーネントから関数コンポーネントがメインになる方針になった。 |
↓ Hooks である useState関数を使った実装。useState は戻り値として、[ state変数,state更新関数 ] を返す。引数は state変数の初期値となる。
戻り値 [state変数, state更新関数] を利用して、状態とその処理を以下のように圧倒的にシンプルに記述できる。
状態と処理を外出しして再利用性を高めるために render props や HOC のような工夫を行うとより複雑なコーディングがなりがちだったが、比較するとHooksはかなり読みやすいことがわかる。
// Hooks
function useCounter() {
const [count, setCount] = useState(0);
const reset = () => { setCount(0); };
const increment = () => {setCount(count + 1);};
return {
count,
reset,
increment,
};
}
function Counter() {
const { count, increment, reset } = useCounter();
return (
<div>
<p>{count}</p>
<button onClick={reset} type="button">
Reset
</button>
<button onClick={increment} type="button">
+1
</button>
</div>
);
}
function App() {
return (
<div>
<Counter />
</div>
);
}
まとめ
- Hooks の導入によりReactのコードは劇的にわかりやすくなる。再利用性も高くなった。
- 既存コードには,mixins, HOC, render props が歴史的経緯により含まれていることがあるので、注意が必要。mixins は記述はシンプルだが、状態とその処理は特定のコンポーネント依存する。名前衝突の温床にもなりやすい。HOC, render props はHooksほど記述がシンプルでなく、かつあくまで設計パターンであるなどするので、依存性の切り分けは完全ではない部分がある。
- React はクラスコンポーネント必須の世界から関数コンポーネントメインに移行していく。
参照
- 『りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅱ. React基礎編】』著・大岡由佳
- React、Hooks、それからそれから。
- 単純に比べるHOC, Render prop, Hooks
-
『Use a Render Prop!』← 有名なアジテーションらしいです ↩