-
Notifications
You must be signed in to change notification settings - Fork 0
DOCS. React Component
초기 프로젝트 구조를 설정할 때부터 우리는 Container와 Component를 분리해서 사용하기로 했다. Container는 ViewModel, Component는 View를 담당하는 느낌으로다가 말이다.
그럼 좀 더 자세히 들어가보자.
Container 컴포넌트란 Redux의 상태 가져오기, dispatch 등을 수행하고 결과 데이터를 Component에 props로 넘겨주는 역할을 한다. 이를 통해서 데이터를 처리하는 부분(Container)과 오직 뷰를 렌더링하는 부분(Component)를 나눌 수 있게 된다.
다음 표를 보면 좀 더 이해하기 쉬울 것이다. Presentational 컴포넌트가 Component를 뜻한다. (referenced from Container 컴포넌트와 Presentational 컴포넌트)
위 개념을 가지고 구현을 진행하였는데, 회원가입을 예로 들자면 다음과 같다.
Container에서 Redux 상태 가져오기와 dispatch을 수행하고,
// import ...
const SignupContainer: FC = () => {
// state 생략 ...
const { loading, error, userId, userLoading } = useSelector(
({ auth, loading }: RootState) => ({
loading: loading['auth/getSignup'],
error: auth.signup.error,
userId: auth.user.userId,
userLoading: loading['auth/getUser'],
}),
shallowEqual,
);
const dispatch = useDispatch();
useEffect(() => {
return () => {
dispatch({ type: getSignupReset.type });
};
}, [dispatch]);
// 생략 ...
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const validation: string | boolean = authValidation({ id, password, check });
if (validation !== true) setAuthError(validation as string);
else dispatch({ type: getSignup.type, payload: { id, password } });
};
return (
<>
<AuthForm
id={id}
password={password}
onChange={onChange}
onSubmit={onSubmit}
error={authError}
loading={loading}
isSignup
onCheckChange={onCheckChange}
/>
<AuthSuccessModal userId={userId} visible={modalVisible} setVisible={setModalVisible} />
</>
);
};
export default SignupContainer;
Component에서는 오직 "뷰"에만 집중하여 구현하였다. 여기서 스타일 컴포넌트를 통해 스타일을 정의하였다. (아래는 Wrapper
, Form
등..)
// import ...
interface AuthFormProps {
id: string;
password: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: (e: React.FormEvent) => void;
onGuestLogin?: () => void;
error: string | null;
loading: boolean;
isSignup?: boolean;
onCheckChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const AuthForm: FC<AuthFormProps> = ({
id,
password,
onChange,
onSubmit,
onGuestLogin,
error,
loading,
isSignup,
onCheckChange,
}) => {
return (
<WrapForCenter>
<Wrapper>
<Form onSubmit={onSubmit}>
<LineInput
type="text"
placeholder="아이디"
className="auth-input"
value={id}
name="id"
onChange={onChange}
maxLength={30}
/>
<LineInput
type="password"
placeholder="비밀번호"
className="auth-input"
value={password}
name="password"
onChange={onChange}
maxLength={20}
/>
<div className="auth-check">
<CheckBox id="signup-agree" text="배민문방구 전체 동의" onChange={onCheckChange} />
</div>
<Error>{error}</Error>
<BoxButton type="submit">
{loading ? (
<CircleLoader size="25px" color="brown" />
) : (
<>
<Image src={baedal} alt="form-icon" />
회원가입
</>
)}
</BoxButton>
</Form>
<LinkWrapper>
<Link to={SIGNIN_URL}>계정이 있다면? 로그인하러 가기</Link>
</LinkWrapper>
</Wrapper>
</WrapForCenter>
);
};
export default AuthForm;
이렇게 Container와 Component를 나눠서 구현했을 때 데이터 처리와 뷰 렌더링의 역할을 분명하게 구분할 수 있어서 좋은 점도 있었지만, Container라는 하나의 계층이 더 생기는 것이기 때문에 좀 더 복잡하게 느꼈을 수도 있을 것 같다. 프로젝트를 진행하면서 그림의 남자들은 각자 어떻게 느꼈을까? 🤔 🤔 🤔
- 박기덕 : 클래스형도 아니고 함수형 컴포넌트인데? Hook도 사용할건데 컨테이너로 나누는게 맞을까? 싶었다. 컨테이너의 구조가 복잡한데, 어차피 컨테이너가 아니라도 어디서든 복잡한 곳이 있어야 했다. 오히려 데이터를 필요한 곳에서 바로바로 불러올 수 있다면 렌더링 측면에서도 더 효율적으로 될 수 있지 않았을까 싶다.
- 서그림 : 이전에 React와 Redux로 프로젝트를 진행하면서 Container 컴포넌트라는 개념이 생소해 적용하지 못했었는데 이번 프로젝트에서는 잘 이해하고 사용한 것 같아서 좋다. 데이터 처리 부분이 따로 나뉘어진 것이 이후 수정할 때 찾기도 쉽고 구조적으로 훨씬 좋았던 것 같다.
- 손원우 : 초반에 기능이 없어 코드량이 작을 때는 컴포넌트가 한층 더 쌓여있어서 불편한 감이 있었지만 코드량이 늘고 기능이 복잡해지니 훨씬 구조를 이해하기 쉬워 좋았다.
- 윤민상 : 사실 처음에는 독립적인 state도 컨테이너에서 사용을 하려고 했다. 그런데 자료를 찾아보고 팀원들과 얘기를 나누고보니 내부의 독립적인 상태는 view랑만 관련되어 있기 때문에 컴포넌트 내부에서 해도 괜찮다고 생각했다. 예전에 리액트 책을 볼때 전역으로 사용하지 않을, state로 관리해도 전혀 문제 없는 것들도 redux에서 관리하는 것을 보고 이해가 안되서 따라하지 않았는데 이 프로젝트를 겪고나니 복잡해지고 커질수록 리덕스에서 모든걸 관리해서 컴포넌트는 더 깔끔한 구조를 만드는 그런 방법도 괜찮을 것 같다는 생각이 들었다.