React - Notion (1)

김정욱·2020년 11월 28일
0

React

목록 보기
19/22
post-custom-banner

Notion 만들기

: 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관련 라이브러리 참조

profile
Developer & PhotoGrapher
post-custom-banner

0개의 댓글