LaravelとReactで作るSPAアプリ第8段です。
続いて関数での切り分けを行っていきましょう。
今回は共通部分、登録、更新、削除を関数化します。
Laravel×ReactでつくるSPAスケジュールアプリ【⑧関数化Ⅰ】
肥大化したソースを分かりやすく管理しよう
前回まででアプリの全体像は完成しています。
しかし「Example.js」ファイルを見てみると300行以上も行数があり、
表示、登録、更新、削除と処理が入り乱れています。
この程度のアプリならば1つのファイルで良いですが、今後どんどん増えていくと管理も大変になりますね。
どうせならそれぞれの役割別に、関数として処理を切り分けたいところです。
そこで今回は「Example.js」を以下のように分割して、管理しやすくします。
- comon:共通で使いまわせる関数群
- delete:削除処理
- navigation:月ナビゲーション機能
- register:登録機能
- schedule:スケジュール表示機能
- update:更新機能
まずはcommonから作成していきましょう。
commonを関数化する
Example.jsと同じ階層に「common」というフォルダを作成します。
その中に「Common.js」というファイルを作成しましょう。
1 2 3 |
export function zeroPadding(num){ return ('0' + num).slice(-2); } |
今回のアプリケーションではzeroPaddingだけですが、実際のアプリでは多数の関数が登録されます。
その場合には全体をコピペして新しい関数を作成すると良いでしょう。
別jsファイルが作成したので、今度は「Example.js」から「Common.js」を呼び出すようにします。
その際にはimport宣言を利用します。
1 2 3 4 5 6 7 8 |
import {zeroPadding} from './/common/Common'; //追記 --省略-- function zeroPadding(num){ //削除 return ('0' + num).slice(-2); //削除 } //削除 }); |
.jsは必要ありません。またimportで外部参照をしているので、Example.jsのzeroPaddingも不要です。
これで共通部分の関数化は完了です。
ビルドをして、エラーが出てないことを確認しておきましょう。
propsで登録機能を切り分ける
まだまだ切り分けられる部分は多そうですね。
続いて登録機能を切り分けましょう。この際にはpropsを使用します。
propsは関数に対する引数のように考えると分かりやすいですね。
まずはディレクトリを増やします。
registerフォルダの中に「Register.js」と「Registerdis.js」の2ファイルを作成してください。
「Register.js」は登録処理、「Registerdis.js」は登録画面として使用します。
それぞれの中身は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import React from 'react'; import axios from 'axios'; import Button from '@mui/material/Button'; function Register(props){ const{formData}=props; console.log(formData); //ダイアログデータを登録 const createSchedule = async() => { //タイトルが空なら弾く if(formData.sch_title==''){ return; } //入力値を投げる await axios .post('/api/posts/create',{ sch_category:formData.sch_category, sch_contents:formData.sch_contents, sch_date:formData.sch_date, sch_time:formData.sch_hour + ':' + formData.sch_min }) .then((res)=>{ //戻り値をtodosにセット const tempPosts = post; tempPosts.push(res.data); setPosts(tempPosts) setFormData(''); }) .catch(error=>{ console.log(error); }) } return ( <Button href="/dashboard" onClick={createSchedule}>Subscribe</Button> ); } export default Register; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import React from 'react'; import PropTypes from 'prop-types'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import TextField from '@mui/material/TextField'; import DialogContentText from '@mui/material/DialogContentText'; import Button from '@mui/material/Button'; import DialogTitle from '@mui/material/DialogTitle'; import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import Select from '@mui/material/Select'; import Register from './Register'; function Registerdis(props){ const{onClose,open,data,setFormData}=props; const handleClose = () =>{ onClose(); }; //入力値を一時保存 const inputChange = (e) =>{ const key = e.target.name; const value = e.target.value; data[key] = value; let datas = Object.assign({},data); setFormData(datas); } return ( <Dialog onClose={handleClose} open={open}> <DialogTitle>Subscribe</DialogTitle> <DialogContent> <DialogContentText> スケジュール登録 </DialogContentText> <TextField margin="dense" id="sch_date" name="sch_date" label="予定日" type="text" fullWidth variant="standard" onChange={inputChange}/> <InputLabel id="sch_time_label">時刻</InputLabel> <Select labelId="sch_hour" id="sch_hour_select" name="sch_hour" label="Hour" variant="standard" defaultValue="00" onChange={inputChange}> <MenuItem value="00">00</MenuItem><MenuItem value="01">01</MenuItem> </Select> <Select labelId="sch_min" id="sch_min_select" name="sch_min" label="Min" variant="standard" defaultValue="00" onChange={inputChange}> <MenuItem value="00">00</MenuItem><MenuItem value="01">01</MenuItem> </Select> <InputLabel id="sch_category_label">カテゴリー</InputLabel> <Select labelId="sch_category" id="sch_category_select" name="sch_category" label="Category" variant="standard" defaultValue="勉強" onChange={inputChange}> <MenuItem value="勉強">勉強</MenuItem> <MenuItem value="案件">案件</MenuItem> <MenuItem value="テスト">テスト</MenuItem> </Select> <TextField margin="dense" id="sch_contents" name="sch_contents" label="内容" type="text" fullWidth variant="standard" onChange={inputChange}/> </DialogContent> <DialogActions> <Button onClick={handleClose}>Cancel</Button> <Register formData={data} /> </DialogActions> </Dialog> ); } Registerdis.propTypes = { onClose:PropTypes.func.isRequired, open:PropTypes.bool.isRequired, }; export default Registerdis; |
さらに登録ボタンの部分もRegisterを読み込むようにしています。
これで「Example.js」→「Register.js」→「Register.js」という流れが完成します。
ダイアログをRegisterdisに移したので、Exampleからダイアログ関連を削除しておきましょう。
またPropsでRegisterdisへの連携も行っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import Registerdis from './/register/Registerdis'; //追加 --省略-- //登録用ポップアップ開閉処理 const[open,setOpen] = useState(false); const handleClickOpen = (e) =>{ setOpen(true); }; const handleClose = () =>{ setOpen(false); }; //新規登録用データ配列 const [formData,setFormData] = useState({sch_category:'',sch_contents:'',sch_date:'',sch_hour:'',sch_min:''}); --この下にあった登録関連の処理は削除-- --省略-- </table> <Registerdis open={open} onClose={handleClose} data = {formData} setFormData = {setFormData} /> --省略-- export default Register; |
それにより、RegisterdisとRegisterに移行した表示処理や登録処理は削除しました。
最後にreturn部分のダイアログを丸ごと書き換えています。
かなりシンプルになったことが分かりますね。
同じように更新、削除も関数化してしまいましょう。
登録、更新、削除を関数化したところで、コミットを行い、これまでの処理ができるかを確認しましょう。
OKならば、一旦SourceTreeでコミットしておくと良いですね。
これで関数化の第一弾は終了です。第二弾ではスケジュール表示の部分を弄っていきます。
少し処理が変わってくるので、一つ一つ丁寧に見ていきましょう。
ここまで削減し終わったExample.jsの全体は以下に折り畳み式で表示しておきますね。
かなり削減できていることが分かるはずです。
- クリックでExample.jsの全ソース表示
-
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184import React,{Fragment,useState,useEffect} from 'react';import ReactDOM from 'react-dom';import axios from 'axios';import {zeroPadding} from './/common/Common';import Registerdis from './/register/Registerdis';import Updatedis from './/update/Updatedis';function Example(){const [year,setYear] = useState(new Date().getFullYear())const [month,setMonth] = useState(new Date().getMonth()+1)const last = new Date(year,month,0).getDate()const prevlast = new Date(year,month-1,0).getDate()const calendar = createCalendear(year,month)const onClick = n => () => {const nextMonth = month + nif (12 < nextMonth) {setMonth(1)setYear(year + 1)} else if (nextMonth < 1) {setMonth(12)setYear(year - 1)} else {setMonth(nextMonth)}}//スケジュールのデータconst [schedules,setSche] = useState([])//画面読み込み時に、1度だけ起動useEffect(()=>{getPostData();},[])//バックエンドからデータ一覧を取得const getPostData = () =>{axios.post('/api/posts').then(response=>{setSche(response.data); //バックエンドからのデータをセットconsole.log(response.data);}).catch(()=>{console.log('通信に失敗しました');});}//データ格納の空配列を作成let rows = [];//スケジュールデータをrowに格納するschedules.map((post)=>rows.push({sch_id:post.id,sch_category:post.sch_category,sch_contents:post.sch_contents,sch_date:post.sch_date,sch_time:post.sch_time}));//登録用ポップアップ開閉処理const[open,setOpen] = useState(false);const handleClickOpen = (e) =>{setOpen(true);};const handleClose = () =>{setOpen(false);};//新規登録用データ配列const [formData,setFormData] = useState({sch_category:'',sch_contents:'',sch_date:'',sch_hour:'',sch_min:''});//更新用ダイアログ開閉機能const[editopen,setEditOpen] = useState(false);const editHandleClickOpen = (e) =>{e.stopPropagation();setEditOpen(true);getEditData(e);};const editHandleClose = () =>{setEditOpen(false);};//更新用のデータ配列const [editData,setEditData] = useState({id:'',sch_category:'',sch_contents:'',sch_date:'',sch_hour:'',sch_min:''});//バックエンドからデータ一覧を取得function getEditData(e){axios.post('/api/edit', {id: e.currentTarget.id}).then(res => {setEditData({id:res.data.id,sch_category:res.data.sch_category,sch_contents:res.data.sch_contents,sch_date:res.data.sch_date,sch_hour:res.data.sch_time.substr(0,2),sch_min:res.data.sch_time.substr(3,2)});}).catch(() => {console.log('更新の通信に失敗しました');});}console.log(editData);return (<Fragment><div className="calender-header"><h1>{`${year}年${month}月`}</h1><div className="calender-nav"><button onClick={onClick(-1)}>{'<先月'}</button><button onClick={onClick(1)}>{'翌月>'}</button></div></div><table className="calender-table"><thead><tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr></thead><tbody>{calendar.map((week,i) => (<tr key={week.join('')}>{week.map((day,j) => (<td key={`${i}${j}`} id={day} onClick={handleClickOpen}><div><div>{day > last ? day - last : day <= 0 ? prevlast + day : day}</div><div className="schedule-area">{rows.map((schedule,k) => (schedule.sch_date == year + '-' + zeroPadding(month) + '-' + zeroPadding(day) &&<div className='schedule-title' key={k} onClick={editHandleClickOpen} id={schedule.sch_id}>{schedule.sch_contents}</div>))}</div></div></td>))}</tr>))}</tbody></table><Registerdisopen={open}onClose={handleClose}data = {formData}setFormData = {setFormData}/><Updatedisopen={editopen}onClose={editHandleClose}data = {editData}setEditData = {setEditData}/></Fragment>);}function createCalendear(year,month){const first = new Date(year,month - 1,1).getDay()return [0,1,2,3,4,5].map((weekIndex) => {return [0,1,2,3,4,5,6].map((dayIndex) => {const day = dayIndex + 1 + weekIndex * 7return day - first})})}export default Example;if (document.getElementById('app')) {ReactDOM.render(<Example />, document.getElementById('app'));}
Laravel×Reactでつくるスケジュールアプリ | |
環境構築 | Git、Reactの導入 |
カレンダー表示 | スケジュール表示 |
スケジュール登録 | スケジュール更新 |
スケジュール削除 | 関数化Ⅰ |
関数化Ⅱ |