Propsをよりスマートに渡したい!
はじめに
Reactのpropsを渡す方法にフォーカスを当てて、ダサいコードをシュッとしたコードに改善していきます!(語彙力...)
プロジェクトの概要
プロジェクトは大人気マンガONE PIECE
の「麦わらの一味」のキャラ名をリストにした簡単なものです。スタイルにはMaterial-Ui
を用いています。
改善前のファイル構成
├── src/
├── components/
└──Charactor.tsx
└──CharactorList.tsx
└──CharactorHead.tsx
└── index.tsx
└──App.tsx
ルートとなるApp.tsxではデフォルトとなるカラーをcreateMuiThemeで表現しています。
import React from 'react';
import { createMuiTheme, ThemeProvider } from '@material-ui/core';
import './App.css';
import Charactor from './components/Charactor';
const theme = createMuiTheme({
palette: {
primary: {
main: '#ff6347',
light: '#d2b48c',
},
background: {
default: '#2f4f4f',
},
},
overrides: {
MuiAppBar: {
root: {
transform: 'translateZ(0)',
},
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<div className="App">
<header className="App-header">
<Charactor />
</header>
</div>
</ThemeProvider>
);
}
export default App;
コンポーネントを利用する側である親コンポーネントはCharacter.tsxファイルとします。
import {
makeStyles,
Paper,
Table,
TableBody,
TableHead,
Theme,
} from '@material-ui/core';
import React from 'react';
import CharaHead from './CharaHead';
import CharactorList from './CharactorList';
const useStyles = makeStyles((theme:Theme) => ({
pageContent: {
backgroundColor: theme.palette.background.default,
margin: theme.spacing(5),
padding: theme.spacing(3),
},
table: {
marginTop: theme.spacing(3),
'& thead th': {
fontWeight: '600',
color: theme.palette.primary.main,
},
'& tbody td': {
fontWeight: '400',
},
'& tbody tr:hover': {
backgroundColor: '#fffbf2',
cursor: 'pointer',
},
},
}));
const Charactor = () => {
const classes = useStyles();
return (
<>
<Paper className={classes.pageContent}>
<Table className={classes.table}>
<TableHead>
<CharaHead
firstName="firstName"
middleName="middleName"
lastName="lastName"
/>
<TableBody>
<CharactorList
firstName="Monkey"
middleName="D"
lastName="Luffy"
/>
<CharactorList
firstName="Roronoa"
middleName=""
lastName="Zoro"
/>
<CharactorList firstName="" middleName="" lastName="Nami" />
<CharactorList firstName="" middleName="" lastName="Usopp" />
<CharactorList
firstName="Vinsmoke"
middleName=""
lastName="Sanji"
/>
<CharactorList
firstName="Tony Tony"
middleName=""
lastName="Chopper"
/>
<CharactorList firstName="Nico" middleName="" lastName="Robin" />
<CharactorList firstName="" middleName="" lastName="Franky" />
<CharactorList firstName="" middleName="" lastName="Brook" />
<CharactorList firstName="" middleName="" lastName="Jimbei" />
</TableBody>
</TableHead>
</Table>
</Paper>
</>
);
};
export default Charactor;
またコンポーネントを利用される側である子コンポーネントはCharaHead.tsx、CharactorList.tsxファイルとします。
import { TableCell, TableHead } from '@material-ui/core';
import React from 'react';
export interface Props {
firstName: string;
middleName: string;
lastName: string;
}
const CharaHead: React.FC<Props> = (props) => {
const { firstName, middleName, lastName } = props;
return (
<TableHead>
<TableCell> {firstName}</TableCell>
<TableCell> {middleName}</TableCell>
<TableCell> {lastName}</TableCell>
</TableHead>
);
};
export default CharaHead;
import { TableRow, TableCell,Theme, makeStyles } from '@material-ui/core';
import React from 'react';
const useStyles = makeStyles((theme:Theme) => ({
table: {
color: theme.palette.primary.light,
},
}));
export interface CharaProps {
firstName: string;
middleName: string;
lastName: string;
}
const CharactorList: React.FC<CharaProps> = (props) => {
const classes = useStyles();
const { firstName, middleName, lastName } = props;
return (
<TableRow>
<TableCell className={classes.table} align="center">
{firstName}
</TableCell>
<TableCell className={classes.table} align="center">
{middleName}
</TableCell>
<TableCell className={classes.table} align="center">
{lastName}
</TableCell>
</TableRow>
);
};
export default CharactorList;
改善後のファイル構成
├── src/
├── components/
└──Charactor.tsx
└──charaList.ts
└──characterType.ts
└──useCharacter.tsx
└── index.tsx
└──App.tsx
改善すべきポイント①
子コンポーネントを一つの関数としてラップする
改善前では子コンポーネントは2つに分かれていましたが、一つのファイル(useCharacter.tsx)にまとめます。
import React from 'react';
import { TableHead, TableCell, TableRow } from '@material-ui/core';
import { HeadCells, CharaList } from './charaType';
interface Props {
firstName: string;
middleName: string;
lastName: string;
}
const useCharactor = (headCells: HeadCells, charaLists: CharaList) => {
const CharaHeads: React.FC = () => {
return (
<TableHead>
{headCells.map((headCell) => (
<TableCell key={headCell.id}>{headCell.label}</TableCell>
))}
</TableHead>
);
};
const CharaLists: React.FC<Props> = (props) => {
const { firstName, middleName, lastName } = props;
return (
<TableRow>
<TableCell align="center">{firstName}</TableCell>
<TableCell align="center">{middleName}</TableCell>
<TableCell align="center">{lastName}</TableCell>
</TableRow>
);
};
return { CharaHeads, CharaLists };
};
export default useCharactor;
useCharacter関数の中で子コンポーネントを作成し、返り値としてその子コンポーネントを持っているといった具合です。
また、アロー関数の中身にreturn文しかない場合はreturnを省略できます。
const useCharacter = (headCells: HeadCells, charaLists: CharaList) => {
const CharaHeads: React.FC = () => (
<TableHead>
{headCells.map((headCell) => (
<TableCell key={headCell.id}>{headCell.label}</TableCell>
))}
</TableHead>
);
const CharaLists: React.FC<Props> = ({ firstName, middleName, lastName }) => (
<TableRow>
<TableCell align="center">{firstName}</TableCell>
<TableCell align="center">{middleName}</TableCell>
<TableCell align="center">{lastName}</TableCell>
</TableRow>
);
return { CharaHeads, CharaLists };
};
export default useCharacter;
useCharacter関数の2つの引数であるheadCellsとcharaListsは別途ファイルに分けて、親コンポーネントであるCharacter.tsxに受け渡します。
export const headCells = [
{ id: 0, label: 'firstName' },
{ id: 1, label: 'middleName' },
{ id: 2, label: 'lastName' },
];
export const charaLists = [
{ firstName: 'Monkey', middleName: 'D', lastName: 'Luffy' },
{ firstName: 'Roronoa', middleName: '', lastName: 'Zoro' },
{ firstName: '', middleName: '', lastName: 'Nami' },
{ firstName: '', middleName: '', lastName: 'Usopp' },
{ firstName: 'Vinsmoke', middleName: '', lastName: 'Sanji' },
{ firstName: 'Tony Tony', middleName: '', lastName: 'Chopper' },
{ firstName: 'Nico', middleName: '', lastName: 'Robin' },
{ firstName: '', middleName: '', lastName: 'Franky' },
{ firstName: '', middleName: '', lastName: 'Brook' },
{ firstName: '', middleName: '', lastName: 'Jimbei' },
];
改善すべきポイント②
propsを配列処理として渡す
改善前の親コンポーネントのコードではpropsを一つ一つ渡していましたが、配列処理(mapメソッド)を用いてよりスマートにpropsを渡すよう改善しました。
import {
makeStyles,
Paper,
Table,
TableBody,
TableHead,
} from '@material-ui/core';
import React from 'react';
import { headCells, charaLists } from './charaList';
import useCharacter from './useCharacter';
const useStyles = makeStyles((theme) => ({
pageContent: {
backgroundColor: theme.palette.background.default,
margin: theme.spacing(5),
padding: theme.spacing(3),
},
table: {
marginTop: theme.spacing(3),
'& thead th': {
fontWeight: '600',
color: theme.palette.primary.main,
},
'& tbody td': {
fontWeight: '400',
color: theme.palette.primary.light,
},
'& tbody tr:hover': {
backgroundColor: '#fffbf2',
cursor: 'pointer',
},
},
}));
const Character = () => {
const classes = useStyles();
const { CharaHeads, CharaLists } = useCharacter(headCells, charaLists);
return (
<>
<Paper className={classes.pageContent}>
<Table className={classes.table}>
<TableHead>
<CharaHeads />
<TableBody>
{charaLists.map((charaList) => {
const { firstName, middleName, lastName } = charaList;
return (
<CharaLists
firstName={firstName}
middleName={middleName}
lastName={lastName}
></CharaLists>
);
})}
</TableBody>
</TableHead>
</Table>
</Paper>
</>
);
};
export default Character;
返り値のJSX.Elementを比較してもらえれば一目瞭然ですが、かなりコードの量が削減されています。
親コンポーネントでは、子コンポーネントのラッパー関数であるuseCharacterから分割代入を用いて子コンポーネントを取り出しています。後は子コンポーネントをJSX.Elementとして適切な場所に配置するといった具合です。
最後に親コンポーネントの記述を更に簡潔に表記できるため、そちらをお見せしてこの記事を締めくくりたいと思います。
const Character = () => {
const classes = useStyles();
const { CharaHeads, CharaLists } = useCharacter(headCells, charaLists);
return (
<>
<Paper className={classes.pageContent}>
<Table className={classes.table}>
<TableHead>
<CharaHeads />
<TableBody>
{charaLists.map((charaList) => {
return <CharaLists {...charaList}></CharaLists>;
})}
</TableBody>
</TableHead>
</Table>
</Paper>
</>
);
};
export default Character;
改善を行ったのは子コンポーネントであるCharaListsを配列処理で表示させている部分です。mapメソッドの引数(コールバック関数)の更にその引数であるcharaListにはfirstName,middleName,lastNameをプロパティーにもつオブジェクトが内包されています。そのcharaListをCharaListsコンポーネントの属性としてスプレッド構文を利用することで、さらにコードを簡潔に書くことを可能にしています。
以上になります。ここまで読んでいただきありがとうございました!!
Discussion