Reactでコンポーネントをたくさん作っていると各コンポーネントに共通処理が出てきてうっとおしい場合がありませんか?
例)初回レンダリング時にログを出力する
例)特定の属性の値(valueとする)が設定されているかをrequired属性の有無によってチェックするかを切り替える
オブジェクト指向だとこういった共通する処理は親クラスで実装し、親を継承した子クラスに独自の処理を記載するということが行われます。
ReactではComposition機能が用意されているのでそれを使って実装したいと思います。
・CustomComponentAには1つの入力があり、required付加時には未入力の場合は必須エラー
・CustomComponentBには2つの入力があり、required付加時にはどちらも未入力の場合は必須エラー
App.tsxで以下を呼び出してます。
CustomComponentA 必須
CustomComponentB 必須
CustomComponentB 任意
import { useState } from 'react'
import CustomComponentA from './CustomComponentA'
import CustomComponentB from './CustomComponentB'
function App() {
const [value,setValue] = useState<string>('')
const [address,setAddress] = useState<string>();
const onChange = (e:ChangeEvent<HTMLInputElement>) =>{
setValue(e.target.value)
}
return (
<div className="App">
<CustomComponentA required/>
<CustomComponentB required/>
<CustomComponentB />
</div>
)
}
export default App
共通処理を記述するCompositionです
import React, { ReactNode, useEffect, useState } from 'react'
type propsType = {
children:ReactNode,
childprop?:any,
value?:string
}
const SampleComposition = (props:propsType) => {
const [errormessage,setErrormessage] = useState('');
const [value,setValue] = useState(props.value);
useEffect(()=>{
//初回時にログを出すとか・・
console.log('LOG:I am parent.');
},[])
useEffect(()=>{
setValue(props.value);
},[props.value])
const onBlur = () =>{
//blur時に必須チェックするとか・・・
if(!!props.childprop && !!props.childprop.required){
console.log('LOG:Child is required!!');
if(!value){
setErrormessage('★必須入力エラー(value未設定)')
}else{
setErrormessage('')
}
}
}
return (
<fieldset onBlur={onBlur}>
<legend>Parent</legend>
{props.children}
<p>{errormessage}</p>
</fieldset>
)
}
export default SampleComposition;
CustomComponentAのポイントはここではrequiredの実装はしていません。
※微妙な点:
・Compositionコンポーネントと呼出しコンポーネントの結合度が高い気がする。共通化を主眼においてしかたないとするか?
・汎用性を持たせる観点からSampleCompositionのchildpropをanyにしてしまっている点でtypescriptの恩恵にあずかれない。
※これは共通のtypeやらinterfaceやらを定義すればよいかな?
import React, { ChangeEvent, ChangeEventHandler, useEffect, useState } from 'react'
import SampleComposition from "./SampleComposition";
const CustomComponentA = (props: any) => {
const [value, setValue] = useState(props.value);
useEffect(() => {
console.log('LOG:I am CustomComponentA.');
}, [])
const onChange = (e:ChangeEvent<HTMLInputElement>) =>{
setValue(e.target.value)
}
return (
<>
<SampleComposition childprop={props} value={value}>
<input value={value} onChange={onChange}/>
</SampleComposition>
</>
)
}
export default CustomComponentA;
import React, { ChangeEvent, ChangeEventHandler, useEffect, useState } from 'react'
import SampleComposition from "./SampleComposition";
const CustomComponentB = (props: any) => {
const [valueA, setValueA] = useState('');
const [valueB, setValueB] = useState('');
useEffect(() => {
console.log('LOG:I am CustomComponentB.');
}, [])
const onChangeA = (e:ChangeEvent<HTMLInputElement>) =>{
setValueA(e.target.value)
}
const onChangeB = (e:ChangeEvent<HTMLInputElement>) =>{
setValueB(e.target.value)
}
return (
<>
<SampleComposition childprop={props} value={valueA + valueB}>
<input value={valueA} onChange={onChangeA}/>
<input value={valueB} onChange={onChangeB}/>
</SampleComposition>
</>
)
}
export default CustomComponentB;
実行結果
blur時に無事エラーメッセージが出力されました。
子コンポーネントにrequiredを定義せずchildpropとして渡すことでかなり力技ですが実装はできました。
まぁCompositionも使い方色々ですかね。
以上