common / component / container

김현성·2022년 2월 2일
0

study

목록 보기
8/9

🤔 common

여러 프로젝트들에 공통으로 들어가는 컴포넌트들이 있는 폴더

📌 사용 사례

board 같은 곳에 글을 쓸 때 글쓰는 곳을 조금 더 예쁘게 꾸며주는 CKEditor와, 
날짜를 선택하는 DatePicker , 글이 많을 때 페이지 번호를 매기는 Pagination, 
Daum 우편번호 검색 서비스를 제공하는 postcode 등은 한 프로젝트에서만 적용되는 것이 아니라
공통적으로 여러 프로젝트에서 사용되어지는 것들이므로 common 폴더에 담긴다.



🤔 container / component

👉 container는 Smart component 라고도 불리는데, redux와 소통하며 앱의 상태(redux state)를 제어 하는 역할.
redux와 연결하고, redux로부터 받은 상태 데이터를 component로 전달해주면 됨.
(Redux의 store 접근은 오직 container를 통해서만 이루어짐.)

우리 프로젝트에서는 각 화면의 큰 틀이 담기는 느낌
📌 container 사용 사례

파일 : sighty_project/admin/src/container/admin/admin/index.jsx 
→ 관리자 페이지 중 메인 페이지의 가장 하단만 첨부


export default connect((state) => {
    return {
        eSESSION: state.data.eSESSION.eSESSION,
        eCOLUMN: state.data.eCOLUMN.eCOLUMN,
    };
})(withRouter(index));




파일 : sighty_project/admin/src/container/board/faqManage/index.jsx 
→ 게시판 관리의 faq 수정 화면

const index = (props) => {
    const {dispatch, history, eSESSION} = props;
    const imageUrl = Const.getImageUrl();
    
    const [detail, setDetail] = useState({
        faq_sj: '',
        faq_cont: '',
        faq_ctgry_no: '',
        public_yn: 'Y',
    });
    const [categoryList, setCategoryList] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    
    const faq_no = Const.getParameterByName('faq_no');
    
    const checkValidation = [
        {field: 'faq_sj', msg: '제목을 입력해주세요.'},
        {field: 'faq_cont', msg: '내용을 입력해주세요.'},
        {field: 'faq_ctgry_no', msg: '카테고리를 선택해주세요.'},
    ];
    
    useEffect(() => {
        getCategoryList();
    }, []);
    
    useEffect(() => {
        if (faq_no) {
            getDetail();
        } else {
            setIsLoading(false);
        }
    }, [faq_no]);
    
    const getCategoryList = () => {
        let params = {
            skip: 0,
            limit: 20
        };
        
        dispatch(ActionFaq.getCategoryList(params)).then((res) => {
            setCategoryList(res.list);
        }).catch((err) => {
            let errorMsg = err.response.data.msg || err.response.data.error.message;
            alert(errorMsg);
        })
    };
    
    const getDetail = () => {
        dispatch(ActionFaq.getDetail({faq_no: faq_no})).then((res) => {
            console.log(res)
            setDetail(res.detail);
            setIsLoading(false);
        }).catch((err) => {
            console.log(err);
            setIsLoading(false);
        });
    };
    
    const handleStateValue = (field, value, index) => {
        let newState = JSON.parse(JSON.stringify(detail));
        if (field === 'duration') {
            if (index == 1 && dateUtil.getLeftDaysFrom2(newState[field][0], value) < 0) {
                alert('시작일 이후 날짜를 선택해주세요.');
                return;
            }
            if (index == 0 && dateUtil.getLeftDaysFrom2(value, newState[field][1]) < 0) {
                alert('종료일 이전 날짜를 선택해주세요.');
                return;
            }
            if (dateUtil.getLeftDaysFrom2(new Date(), value) < 0) {
                alert('오늘 이후 날짜를 선택해주세요.');
                return;
            }
            newState[field][index] = value;
        } else {
            newState[field] = value;
        }
        setDetail(newState);
    };
    
    
    const handleSubmit = () => {
        for (let item of checkValidation) {
            if (detail[item.field] === '' || !detail[item.field]) {
                alert(item.msg);
                return;
            }
        }
        
        let params = {
            faq_sj: detail.faq_sj,
            faq_cont: detail.faq_cont,
            faq_ctgry_no: detail.faq_ctgry_no,
            public_yn: detail.public_yn
        };
        
        if (faq_no) {
            Object.assign(params, {faq_no: faq_no});
            dispatch(ActionFaq.update(params)).then((res) => {
                alert('FAQ 수정이 완료되었습니다.');
                location.reload();
            }).catch((err) => {
                console.log(err);
                let errorMsg = err.response.data.msg || err.response.data.error.message;
                alert(errorMsg);
            });
        } else {
            dispatch(ActionFaq.create(params)).then((res) => {
                alert('FAQ 등록이 완료되었습니다.');
                window.close();
                window.opener.parent.location.reload();
            }).catch((err) => {
                console.log(err);
                let errorMsg = err.response.data.msg || err.response.data.error.message;
                alert(errorMsg);
            });
        }
    };
    
    return (
        <div className={"faqManageContainer"}>
            {/*타이틀*/}
            <PageTitle pathNames={['게시판 관리', faq_no ? 'FAQ 수정' : 'FAQ 등록']}/>
            
            <div className={"infoBox"}>
                <div className={"infoTitle"}>{faq_no ? 'FAQ 수정' : 'FAQ 등록'}</div>
                <div className={"infoItems"}>
                    <div className={"infoItem"}>
                        <div className={"field"}>카테고리</div>
                        <div className={"value"}>
                            {(categoryList || []).map((item, index) => (
                                <div className={"radio"}
                                     onClick={(e) => Const.handleStateValue(detail, setDetail, 'faq_ctgry_no', item.faq_ctgry_no)}>
                                    <input id={"radio_is_category_y"} type={'radio'} checked={detail.faq_ctgry_no === item.faq_ctgry_no}/>
                                    <label htmlFor={"radio_is_category"}></label>{item.faq_ctgry_nm}
                                </div>
                            ))}
                        </div>
                    </div>
                    <div className={"infoItem"}>
                        <div className={"field"}>제목</div>
                        <div className={"value"}>
                            <input className={"Text"} type={'text'} placeholder={'제목을 입력해주세요'} value={detail.faq_sj}
                                   onChange={(e) => handleStateValue('faq_sj', e.target.value)}/>
                        </div>
                    </div>
                    <div className={"infoItem"}>
                        <div className={"field"}>내용</div>
                        <div className={"value"}>
                            {isLoading ? null : <CKEditor content={detail.faq_cont} onChange={(e, editor) => {
                                Object.assign(detail, {faq_cont: editor.getData()});
                            }}/>}
                        </div>
                    </div>
                </div>
            </div>
            <div className={"fixed"}>
                <div className={"fixedBox"}>
                    <div className={'publish'} onClick={(e) => {
                        e.preventDefault();
                        Const.handleStateValue(detail, setDetail, 'public_yn', detail.public_yn == 'Y' ? 'N' : 'Y')
                    }}>
                        <input id="cb_publish" type={'checkbox'} checked={detail.public_yn == 'N'}/>
                        <label htmlFor="cb_publish"></label>비공개
                    </div>
                    <div className={"submit"} onClick={() => handleSubmit()}>{faq_no ? '수정' : '저장'}</div>
                </div>
            </div>
        </div>
    )
};

export default connect((state) => {
    return {
        eSESSION: state.data.eSESSION.eSESSION,
    };
})(withRouter(index));

👉 component는 Dumb component 또는 Presentional component라고도 불리는데, 상위 component로부터 props를 전달받아 보여주는 역할
즉, 사용자와 실질적으로 상호작용 하는 친구.

우리 프로젝트에서는 pageTitle, 화면의 큰 틀 안에 담기는 table 형식의 목록, 
파일 업로드 부분 등 container안의 '요소'들이 들어있는 느낌
📌 component 사용 사례

파일 : sighty_project/admin/src/component/table/termsTable/index.jsx 
→ 약관 관련 페이지에서 목록을 띄워주는 table 스타일의 컴포넌트


const index = (props) => {
  const { dispatch, history, list } = props;
  const imageUrl = Const.getImageUrl();

  let renderList = (list || []).map((item, index) => {
    return (
      <div className={"termsTableRow"}>
        <div>{item.terms_id || '-'}</div>
        <div>{item.title || '-'}</div>
        <div className={"termsTableEdit"}>
          <span onClick={() => props.handleEdit(item.terms_id)}>수정</span>
        </div>
      </div>
    )
  });

  return (
    <div className={"termsTableContainer"}>
      <div className={"termsTableHeader"}>
        <div>번호</div>
        <div>약관명</div>
        <div>약관 수정</div>
      </div>
      <div className={"termsTableBody"}>
        {renderList}
      </div>
    </div>
  )
};

쉽게 말해서 container가 일은 다하고, 
component는 받아서 보여주기만 하는 역할을 한다고 생각하면 된다고 함 ..

리덕스를 사용할 때에는 특히 Container와 Component를 구분해주는 게 좋음.

why❓)
Container는 앱의 상태를 관리하기 때문에 앱의 상태가 자주 바뀔수록 
그에 따라 빈번하게 업데이트가 일어남. 그래서 필요없는 부분에 업데이트가 일어나지 않게 하려면
Container과 Component를 구분해주어야 함. *리액트는 컴포넌트 단위로 업데이트* 하기 때문.



참고 사이트

(React) Container와 Component
[React] Container(smart component) vs Component(dumb component)

profile
아자아자

0개의 댓글