React の核心思想はすべてがコンポーネントであること
React では、すべてがコンポーネントです。コンポーネントは UI を一連の独立した再利用可能な部品に分割することができ、これにより私たちは各部品の構築に集中できます。
React のさまざまなコンポーネントを理解することは、React をより良く学ぶために特に重要です。UI コンポーネント、コンテナコンポーネント、無状態コンポーネント、関数型コンポーネントなど、React についての理解が不十分な人は、これらの概念を明確に理解することが難しく、高品質な React コンポーネントを書くことが困難です。本記事は、私の React のコンポーネントに関する理解の一部です。
コンポーネント#
ここでのコンポーネントは React.Component を指します。ほとんどの人が最初に React を学ぶときに最もよく理解するのは Component です。Component を使用すると、コンポーネントを非常に便利に定義できます。
ES6 の class を使用してコンポーネントを定義する方法は次のとおりです:
class Welcome extends React.Component {
render() {
return <h1>こんにちは、{this.props.name}</h1>;
}
}
PureComponent#
PureComponent はここで React.PureComponent を指します。PureComponent は Component とほぼ完全に同じですが、PureComponent は props と state の浅い比較を通じて shouldComponentUpdate () を実現します。
class Welcome extends React.PureComponent {
render() {
return <h1>こんにちは、{this.props.name}</h1>;
}
}
React のライフサイクルにおいて、コンポーネントの props と state が変更されると、コンポーネントの shouldComponentUpdate 関数が実行されます。この関数が true を返すとコンポーネントは再レンダリングされ、false を返すと再レンダリングされません。Component で定義されたコンポーネントでは、shouldComponentUpdate はデフォルトで true を返します。通常、コンポーネントがパフォーマンスの問題に直面したとき、shouldComponent で新旧の属性と状態を等しいかどうかを判断して、不必要な再レンダリングを減らすことができます。
PureComponent は react v15.3.0 で新たに追加された機能で、デフォルトで shouldComponentUpdate における新旧の属性と状態の等しい比較を実装し、不必要な再レンダリングを減らしてパフォーマンスを向上させます。
注意:PureComponent の shouldComponentUpdate はオブジェクトに対して浅い比較のみを行います。オブジェクトの階層が深い場合や構造が複雑な場合、オブジェクトの深層データが変更されてもビューが更新されない問題が発生する可能性があるため、使用時には注意が必要です。
関数型コンポーネント#
上記の Component と PureComponent で定義されたコンポーネントは宣言型コンポーネントと呼ばれます。React では、関数を使用してコンポーネントを定義することもでき、これを関数型コンポーネントと呼びます。以下のように:
const Text = ({ children = 'こんにちは、世界!' }) =>
<p>{children}</p>
関数型コンポーネントは無状態(stateless)コンポーネントとも呼ばれ、コンポーネント内で状態(state)を操作することはできません。関数型コンポーネントを使用することで、React のコードはより可読性が高まり、class や constructor などの冗長なコードを減らし、コンポーネントの再利用を促進します。
関数型コンポーネントには以下の特徴があります:
- 関数型コンポーネントはインスタンス化されないため、全体のレンダリング性能が向上します。
関数型コンポーネントは class 宣言がなく、render メソッドのみを使用して実装され、コンポーネントのインスタンス化プロセスが存在しないため、余分なメモリを割り当てる必要がなく、レンダリング性能が向上します。
- 関数型コンポーネントは this オブジェクトにアクセスできません。
関数型コンポーネント内では this.state にアクセスできず、状態を操作することはできません。
3. 関数型コンポーネントにはライフサイクルがありません。
4. 関数型コンポーネントは props と context パラメータのみを受け取り、副作用はありません。
UI コンポーネントとコンテナコンポーネント#
Redux を使用してデータフローを管理するアプリケーションでは、Redux はコンポーネントを UI コンポーネント(プレゼンテーショナルコンポーネント)とコンテナコンポーネント(コンテナコンポーネント)に分けます。UI コンポーネントは展示型コンポーネントとも呼ばれます。初期の Redux のバージョンでは、著者はこれらの 2 種類のコンポーネントをスマート(Smart)コンポーネントとダム(Dumb)コンポーネントとして定義しました。両者の違いはデータ操作があるかどうかです。ダムコンポーネントは UI コンポーネントに対応し、スマートコンポーネントはコンテナコンポーネントに対応します。UI コンポーネントは UI の表示のみを担当し、ビジネスロジックを担当しません。内部に状態はなく、データは this.props パラメータから提供されます。コンテナコンポーネントはビジネスロジックを処理し、UI の表示を担当しません。内部状態を持ち、Redux の API を使用して UI コンポーネントに接続します。
通常、プロジェクトのファイル構造では、components フォルダーには UI コンポーネントが含まれ、containers フォルダーにはコンテナコンポーネントが含まれます。
純粋コンポーネント#
純粋コンポーネントについて言及する際、まず関数型プログラミングにおける「純粋関数」を振り返ってみましょう。純粋関数は 3 つの主要な原則から成り立っています:
- 同じ入力が与えられた場合、常に同じ出力を返す;
- 関数の実行中に副作用(side effect)を生じない;
- 追加の状態依存がない;
React では、純粋コンポーネントを使用することで Virtual DOM の実行効率を向上させることができますが、どのようなコンポーネントが純粋コンポーネントなのでしょうか?
まず、PureComponent は純粋コンポーネントではありません。なぜなら、コンポーネント内にライフサイクルが存在し、内部状態 state を持ち、副作用を生じる可能性があるからです。
通常、関数型コンポーネント、UI コンポーネント、無状態コンポーネントなど、props パラメータのみを受け取ってデータをレンダリングするコンポーネントは純粋コンポーネントと呼ばれます。これらは同じ入力パラメータ props を受け取り、同じ出力を返し、副作用を持ちません。
コントロールコンポーネント#
HTML では、input、textarea、select などのフォーム要素は自身の状態を維持し、ユーザーの入力に基づいて更新されます。しかし、React では、可変の状態は通常コンポーネントの状態属性に保存され、setState () メソッドを使用してのみ更新されます。
React では、state を使用してフォーム要素のデータの表示を制御し、フォーム要素の入力によって値の変化を引き起こし、state を更新してフォーム要素のデータを更新します。React によって制御される入力フォーム要素は「コントロールコンポーネント」と呼ばれます。
以下のコードを考えてみましょう:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('名前が送信されました: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名前:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="送信" />
</form>
);
}
}
上記のコードはコントロールコンポーネントの例です。value 属性が私たちのフォーム要素に設定されているため、表示される値は常に React データソースの this.state.value の値になります。キーを押すたびに handleChange が呼び出され、現在の React の state が更新されるため、表示される値は異なるユーザーの入力に応じて更新されます。
非コントロールコンポーネント#
ほとんどの場合、フォームを実装するためにコントロールコンポーネントを使用することを推奨します。コントロールコンポーネントでは、フォームデータは React コンポーネントによって処理されます。フォームデータを DOM が処理する場合の代替手段は、非コントロールコンポーネントを使用することです。
非コントロールコンポーネントを作成するには、各状態更新のためのイベントハンドラを作成するのではなく、ref を使用して DOM からフォーム値を取得できます。
以下のコードを考えてみましょう:
class Form extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('名前が送信されました: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名前:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="送信" />
</form>
);
}
}
非コントロールコンポーネントは実際のデータを DOM に保存するため、非コントロールコンポーネントを使用する場合、React と非 React コードを同時に統合することが容易になります。迅速かつ自由に行いたい場合、これによりコード量を減らすことができます。そうでなければ、コントロールコンポーネントを使用するべきです。
通常、非コントロールコンポーネントの使用は推奨されません。
高階コンポーネント#
高階コンポーネント(HOC)は、React におけるコンポーネントロジックの再利用のための高度な技術です。しかし、高階コンポーネント自体は React API ではありません。それは単なるパターンであり、このパターンは React 自身の組み合わせの特性から必然的に生じます。
具体的には、高階コンポーネントは高階関数に似ており、その関数は React コンポーネントを引数として受け取り、新しいコンポーネントを返します。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
あなたは高階コンポーネントを聞いたことがないかもしれませんが、すでに使用したことがあるかもしれません。React のいくつかのサードパーティライブラリ、例えば react-redux の connect や react-router-dom の withRouter などです。
高階コンポーネントは、コードの再利用性、論理性、抽象性を高めることができます。render メソッドをハイジャックしたり、props や state を制御したりすることができます。
以下のコードを考えてみましょう:
export default function Form(Comp) {
return class WrapperComp extends Component {
constructor(props) {
super(props);
this.state = {
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(key, val) {
this.setState({
[key]: val
})
}
render() {
return <Comp {...this.props} handleChange={this.handleChange} state={this.state}/>
}
}
}
上記のコードは高階コンポーネントであり、フォームコンポーネントを使用する際に頻繁に時間処理関数を記述してフォームデータを管理する問題を解決するためのものです。使用方法は次のとおりです:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(key, val) {
this.setState({
[key]: val
})
}
render() {
return (
<div className="login">
<input
type="text"
value={this.props.state.user}
onChange={(e) => this.props.handleChange('user', e.target.value)}
/>
<input
type="password"
value={this.props.state.psw}
onChange={(e) => this.props.handleChange('psw', e.target.value)}
/>
</div>
)
}
}
export default Form(Login);
このように、多くの場所でフォームを使用する必要があるコンポーネントに対して、コンポーネントの再利用性を提供し、ボイラープレートコードを減らすことができます。