そもそもpropsとは
Reactにおいて、propsとは親コンポーネントから子コンポーネントに渡す情報のことです。
私は「関数・メソッドの実行時に引数を渡すことがあるのと同じで、画面の要素を関数にして分割しているReactでも、引数のような位置づけとしてpropsを渡すことがある」といったような理解をしています。
※以下、React公式ドキュメントより引用
React コンポーネントは互いにやりとりをする際に props というものを使います。親コンポーネントは子コンポーネントに props を渡すことで情報を伝えることができるのです。props は HTML の属性と似ていると思われるかもしれませんが、props ではオブジェクトや配列、関数などのあらゆる JavaScript の値を渡すことができます。
基本的なPropsの渡し方
まずは基本的なpropsの渡し方をおさらいします。
こちらもReact公式ドキュメント内のコードを使用しますので、もし実行結果が気になる方が居れば、公式ドキュメントの方を閲覧ください。
変数を渡す
以下の公式ドキュメントのコードには、2つの関数があります。
まず「Profile」関数には、最終的に画面に表示する要素が全体的に記述されています。もう一つの関数である「Avatar」関数はこちらから呼び出しています。
次に「Avatar」関数は、Propsから受け取った値を基に画像を表示しています。「person」の値を基に表示する画像を決定し、「size」を基に表示する画像のサイズを決定しています。
最終的に、画面には3つの正方形の画像が表示されます。
// pesonの情報を基に画像のパスを取得する関数があると思ってください
import { getImageUrl } from './utils.js';
function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)} // pesonの情報を基に画像のパスを取得する関数
alt={person.name}
width={size}
height={size}
/>
);
}
export default function Profile() {
return (
<div>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
<Avatar
size={80}
person={{
name: 'Aklilu Lemma',
imageId: 'OKS67lh'
}}
/>
<Avatar
size={50}
person={{
name: 'Lin Lanying',
imageId: '1bX5QH6'
}}
/>
</div>
);
}
Propsに関数を渡してみる
実は、Propsには関数も渡すことができます。
「実は」と言っていますが、私が今回初めて知っただけで、当たり前に使われている手法な気がします。
関数を渡す
今回は、子ポーネントから親コンポーネントの再描画を呼び出すケースを想定しています。
以下に2つのコンポーネントを記述しています。
まず、「ParentComponent(親コンポーネント)」は、コンポーネント内で定義した関数を子コンポーネントに渡しています。この部分は変数を渡す場合と変わりないと思います。
// 一部インポートは省略している。
import { ChildComponent } from './ChildComponent';
export const ParentComponent: React.FC = () => {
const [data, setData] = useState<string[]>([]);
// 初期ロード
const fetchData = useCallback(async () => {
try {
const res = await api.fetch('/data');
setData(res.data);
} catch (error) {
console.error('データ取得に失敗しました', error);
}
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<div>
<h1>データ一覧</h1>
{/*初期ロードで取得したデータを表示している*/}
<ul>
{data.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
{/*ここで子ポーネントに関数を渡している。*/}
<ChildComponent onRefresh={fetchData} />
</div>
);
};
次に、「ChildComponent(子コンポーネント)」は、親コンポーネントから関数をPropsとして受け取り、コンポーネント内で使用しています。大きく変わる点は型定義の部分です。今回の場合は「onRefreshには、引数がなく、戻り値もない」ということを示しています。
これで、子コンポーネントからonRefreshを呼び出すと、親コンポーネントのfetchDataが実行できるようになります。
// 一部インポートは省略している。
type Props = {
onRefresh: () => void;
};
export const ChildComponent: React.FC<Props> = ({ onRefresh }) => {
// もし、jsx外で使用するのであれば、以下のように呼び出す。
// onRefresh();
return (
<button onClick={onRefresh}>
最新のデータを取得
</button>
);
};
Stateの更新関数を渡す
これも今回初めて知ったのですが、Propsには、Stateの更新関数も渡すことができます。
ちなみに、Stateの更新関数とは、以下のような記述がある時の「setState」の部分を指します。
const [state, setState] = useState<string>("");
今回は、ユーザーの新規登録とログインを1画面で管理しているようなケースで、子コンポーネントから表示の切り替えを発火させてみたいと思います。
まず、「ParentComponent(親コンポーネント)」では、stateの値と更新用関数を子コンポーネントにPropsとして渡します。
stateを定義してはいるものの、親コンポーネントから、子コンポーネントへの渡し方に関しては、普通の関数を渡す場合とそう変化はないと思います。
// 一部インポートは省略している。
import ChildComponent from './ChildComponent';
const ParentComponent: React.FC = () => {
const [mode, setMode] = useState<'login' | 'register'>('login');
return (
<div>
<h1>{mode === 'login' ? 'ログイン' : '新規登録'}</h1>
{/*ここで子コンポーネントにstateの値と更新用関数を渡す*/}
<AuthForm mode={mode} setMode={setMode} />
</div>
);
};
export default ParentComponent;
// 一部インポートは省略している。
// Propsの型定義
type Props = {
mode: 'login' | 'register';
// Dispachは戻り値のない関数であることを示している。「=> void」みたいなもの
// SetStateActionはstateの更新用関数であることを示している
setMode: React.Dispatch<React.SetStateAction<'login' | 'register'>>;
};
export const ChildComponent = ({ mode, setMode }: Props) => {
return (
<div>
{mode === 'login' ? (
<>
<form>
<input type="text" placeholder="メールアドレス" />
<input type="password" placeholder="パスワード" />
<button type="submit">ログイン</button>
</form>
<p>
アカウントをお持ちでない方は{' '}
{/*普通にstateを扱うように使用できる*/}
<button onClick={() => setMode('register')}>新規登録</button>
</p>
</>
) : (
<>
<form>
<input type="text" placeholder="ユーザー名" />
<input type="email" placeholder="メールアドレス" />
<input type="password" placeholder="パスワード" />
<button type="submit">登録</button>
</form>
<p>
アカウントをお持ちの方は{' '}
{{/*普通にstateを扱うように使用できる*/}}
<button onClick={() => setMode('login')}>ログイン</button>
</p>
</>
)}
</div>
);
};
一方、「ChildComponent.tsx(子コンポーネント)」には、これまでに見覚えのない記述があります。Propsの型定義の部分です。
type Props = {
mode: 'login' | 'register';
setMode: React.Dispatch<React.SetStateAction<'login' | 'register'>>;
};
一般的な関数では、引数と戻り値の型を定義するだけですが、stateの更新用関数は特殊で、引数に関して「新しい状態」か「前の状態を受け取って新しい状態を返す関数」どちらも使用可能であることを明示する必要があります。
それぞれどのような意味か説明します。
まず「新しい状態」というのはstateの状態を更新する際に、新しい値を直接stateの更新用関数に指定することです。以下に例を示します。
const [count, setCount] = useState(0);
setCount(1); // 「新しい状態」を直接渡している
次に「前の状態を受け取って新しい状態を返す関数」というのは、前の状態を基に演算した値をstateの更新用関数に指定することです。
以下の例のコードでは現在のstateの値をprevCountとして受け取り、それに1加算した値を新たな状態として保持しています。
const [count, setCount] = useState(0);
setCount(prevCount => prevCount + 1); // 「前の状態を受け取って新しい状態を返す関数」
さて、話を子コンポーネントの説明に戻します。
つまり、型定義では、setModeがReactの更新用関数であることと、setModeは「新しい状態」か「前の状態を受け取って演算した新しい状態」のいずれでも実行可能であることを明示しています。
ここまで準備をすれば、あとはコンポーネント内で普通の変数・関数のように親コンポーネントのstateを処理に使用することができます。
処理は以下のように進みます。
➀新規登録・ログインボタンを押下
➁setMode関数が実行され、親コンポーネントのmodeの状態が更新される。
➂modeの値を受け取った子コンポーネントが画面の表示内容を切り替える。
このようにstateの更新用関数を親コンポーネントから、子コンポーネントに渡すことができれば、より柔軟に動的な画面の状態の変化を実装することが可能になります。
所感・まとめ
今回は、ReactのPropsについてまとめてしました。
Propsは、Reactを書く上で欠かせないものですが、個人開発にてハンズオンで学習していると知らない方法が沢山出てきて、びっくりしました。