: SOPT 27th WEB 파트의 파트원 소개 페이지를 만들어 보기
[ 결과물 미리보기 ]
[ 구현 사항 ]
1)
/
,/members
,/members/:id
페이지 라우팅 설정
2) Grid를 이용한 Card배치
3) 데이터 로딩중 설정
4) 게시글 CRUD(Create / Read / Update / Delete)
[ 프로젝트 구조 ]
1) /src/components : 각 page에 들어가는 컴포넌트들2) /src/lib/api : 서버와 통신하기 위한 api관련 코드
3) /src/pages/members
-Member.js :/members
라우팅 처리하기 위한 파일
-MemberList.js : 파트원 구성을 보는 전체 페이지
-MemberDetail.js : 특정 카드의 상세 정보를 보는 페이지
[ 페이지 라우팅 설정 ]
: 주목할 점은 members와 관련된 라우팅은 Member.js로 따로 분리해서
처리한 다는 점이다
(App.js)
import './App.scss'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import MainHeader from './components/header/MainHeader'; import Member from './pages/members/Member'; function App() { return ( <Router> <div className="App"> <Route component={MainHeader}></Route> <Switch> <Route exact path='/'> <div className="main"> SOPT27th <br />WEB PART NOTION </div> </Route> <Route path='/members' component={Member}></Route> <Route path='/*'>404 NOT FOUND</Route> </Switch> </div> </Router> ); } export default App;
(Member.js)
import { Switch, Route } from 'react-router-dom'; import MemberDetail from './MemberDetail'; import MemberList from './MemberList'; function Member({match}) { console.log(match); return ( <section> <Switch> <Route exact path={match.path} component={MemberList}></Route> <Route path={`${match.path}/:id`} component={MemberDetail}></Route> </Switch> </section> ) } export default Member
:
/members
로 라우팅 된 경로가 하나의 컴포넌트에서 처리된다
[ Grid를 이용한 카드 배치 ]
(MemberList.js) 에서 Card를 배치하는 부분
: 개수는 auto-fill / minmax()를 통해 최소 하나의Card 크기를 200px 최대 1fr로 지정
[ Loading 설정 ]
: 받고자 하는 members데이터에 status를 추가한 object로 선언하여 데이터를 받아오는 과정을
promise상태처럼 idle / pending / rejected / resolved로 유지하여 조건부 렌더링을 한다function MemberList({ history, match }) { /* status가 추가된 object로 state 선언 */ const [membersState, setMembersState] = useState({ members: null, status: 'idle', }); useEffect(() => { /* 값을 가져오려고 하는 상태를 pending으로 지정 */ setMembersState({members: null, status: 'pending'}); try{ (async () => { /* 정보를 가져오는 api를 처리하는 부분 */ const result = await getMembers(); /* 값을 성공적으로 가져오면 resolved로 성공 status */ setTimeout(()=> setMembersState({members: result, status: 'resolved'}),800) })(); }catch(e){ /* 값을 가져오지 못하면 rejected로 실패 status */ setMembersState({members:null, status: 'rejected'}); } }, []); /* memberState.status에 따라서 조건부 렌더링 실시 */ switch (membersState.status) { case 'pending': /* 값을 가져오는 중이면 Loading을 출력 */ return <Loading />; case 'rejected': return <div>데이터 로드 실패!</div> case 'resolved': return ( <> <div className="member-list"> <div className="member-list__title">⭐️ 파트원 소개 *</div> <div className="member-list__header member-list-header"> <div className="member-list-header__nav"> Gallery View </div> <div className="member-list-header__empty"></div> <Button text="Properties" textColor="#777"></Button> <Button text="Filter" textColor="#777"></Button> <Button text="Sort" textColor="#777"></Button> <Button text="Search" textColor="#777" icon="search"></Button> <Button text="..." textColor="#777"></Button> </div> <hr /> <div className="member-list-content-wrapper"> {membersState.members.map((member, i) => <Card key={"card-" + i} memberData={member} onRemoveCard={removeCard} />)} <CardEmpty onClick={onCreateCard}> <CardEmptyText>+ New</CardEmptyText> </CardEmpty> </div> </div> </> ); case 'idle': default : return <div>idle 입니다</div> } } export default MemberList;
(Loading.js)
import { Spin, Alert } from 'antd'; import { LoadingOutlined } from '@ant-design/icons'; const antIcon = <LoadingOutlined style={{ fontSize: 40 }} spin />; function Loading() { return ( <Spin indicator={antIcon} style={{ display: "flex", alignItems: "center", justifyContent:"center", margin: "300px"}}> </Spin> ) } export default Loading;
: ant-design에서 Loading관련 라이브러리 참조