카우치코딩의 6주 포트폴리오 프로그램에는 디자이너분이 whimsical에서 작성한 페이지 기획서를 figma에서 한 차례 디자인을 수정해주셨다.
때문에 페이지 CSS를 구성할때 저것을 참고하며 만들면 되었다.
3주차는 크게 공통 헤더 부분과 슬라이드 구현에 시간을 썻던것 같다.
요청한 페이지 컨셉
받아본 페이지 디자인
-@media를 사용하여 max-width에 따른 반응형을 기획하였다.
-styled-components를 사용하였다.
-antd의 아이콘을 사용하였다.
-flex를 사용한 레이아웃을 작성하였다.
-가능하면 rem과 vh,vw을 사용하여 작성하였다.
반응형의 경우 아래와 같은 ResponsiveBlock을 사용하여 컴포넌트의 Block 또는 개별 Component에서 사용하였다.
const ResponsiveBlock = styled.div`
padding-left: 1rem;
padding-right: 1rem;
width: 1440px;
margin: 0 auto; /**중앙정렬 */
@media (max-width: 1440px) {
width: 1080px;
}
@media (max-width: 768px) {
width: 100%;
}
`;
function Responsive({ children, ...rest }) {
return <ResponsiveBlock {...rest}>{children}</ResponsiveBlock>;
}
flex의 사용법은 익숙치 않다보니 처음에는 보이는대로 사용하였던것 같다.
하지만 프로젝트를 진행해가면서 많이 익숙해졌다.
flex 레이아웃 참고 사이트 : https://d2.naver.com/helloworld/8540176#ch2
리덕스의 경우는 값을 전역으로 가지고 있기 때문에, 여러곳에서 값을 사용하는 경우가 많으면 사용하는것이 좋다. 때문에 auth와 loading을 리덕스를 사용하였고, 리덕스의 라이브러리로는 saga를 사용하였다.
saga도 기본적인 리덕스 사용법과는 크게 다르지 않은데, 액션함수가 실행되면 takeLatest와 같은 함수에서 지정한 함수가 실행되는 식이다.
export const login = createAction(LOGIN, ({ token }) => ({
token,
}));
const loginSaga = createRequestSaga(LOGIN, authAPI.login);
export function* authSaga() {
yield takeLatest(LOGIN, loginSaga);
}
사가 함수의 사용. 외부 컨테이너에서 login이 실행시 LOGIN액션과 페이로드로 token값을 받으며, authSaga에서 지정된 함수 loginSaga가 실행된다.
export default function createRequestSaga(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return function* (action) {
yield put(startLoading(type)); //로딩시작
try {
const response = yield call(request, action.payload);
yield put({
type: SUCCESS,
payload: response.data,
});
} catch (e) {
yield put({
type: FAILURE,
payload: e,
error: true,
});
}
yield put(finishLoading(type)); //로딩 끝
};
}
사용된 createRequestSaga 함수. 내부에서 startLoading 을 호출하고, request(api요청)과 payload값을 통해서 요청을 처리하고, 실패시 FAILURE액션을 호출하고, 성공시 SUCCESS 액션을 호출한다.
모든 작업이 끝나면 finishLoading을 호출한다.
loading 모듈은 state값으로 호출된 타입을 [타입]:boolean값으로 저장한다.
createRequestSaga와 ResponsiveBlock은 책 "리액트를 다루는 기술"에서 사용된것을 사용하였다.
리덕스의 경우 새로고침시에는 값이 없어져버린다. 때문에 로그인시 localStorage에 값을 저장하였고, 로그아웃시 값을 삭제하였다.
index.js에는 localStorage에 저장된 값을 부팅시 받아와 auth모듈의 state에 넣어주고, token값을 헤더에 저장하는 함수를 작성하였다.(토큰값은 내부적으로 유효기간이 있기때문에 저장된 토큰은 시간제약이 존재한다)
//로그인 시
localStorage.setItem('user', JSON.stringify(user));
localStorage.setItem('token', `Bearer ${token}`);
// 로그아웃 시
localStorage.removeItem('token');
//index.js
function loadUser() {
try {
const user = localStorage.getItem('user');
const token = localStorage.getItem('token');
client.defaults.headers.Authorization = `${token}`;
if (!user) return;
store.dispatch(tempSetUser(JSON.parse(user)));
} catch (e) {
console.log('localStorage is not working');
}
}
슬라이드의 구현은 처음에는 자바스크립트와 유사하게 진행해보려다 쓴 경험을 하였다. 리액트는 기본적으로 state가 변경되면 리렌더링, 컴포넌트 함수가 재호출되기 때문에 리액트에 맞는 구현이 필요하다는걸 슬라이드를 구현하면서 깨닫게 되었다.
기획한 슬라이드는 화면 중앙에 메인 이미지가 존재하며, 양옆에 다음 또는 이전 슬라이드 이미지가 존재한다. 또한 메인 이미지의 양옆에는 고정된 버튼이 존재하여 클릭시 다음 이미지로 존재하며, 누르지 않을시 일정 시간후에는 다음 화면으로 이동한다. 이미지가 더 없을경우에는 처음에 있는 이미지가 마지막 이미지 다음의 이미지로 나타나게 된다.
->한차례 이동시
->한차례 더 이동시
이러한 구현에는 실제로 보여지는 이미지1/이미지2/이미지3 과 같은 렌더링 뿐만 아니라
와 같은 구현이 필요하다는걸 알 수 있고, 또한 이미지가 이동할경우, 그 이미지 또한 양옆에 이미지가 존재하기때문에 이미지가 하나 더 렌더링 되어야한다. 즉
와 같은 렌더링이 되어야한다.
이동은 이벤트에 맞는 transform의 이동을 할것이고
이미지가 아이템의 갯수만큼 이동하였을 경우에는 tansform을 사용하여 추가로 렌더링된 이미지의 위치가 아닌, 기본 렌더링된 이미지의 위치로 이동하게 될것이다.
img3에서 img4(img1과 같은 이미지)로 이동시에는 이동이 완료된 후에, transform으로 현재 div의 위치를 img1의 위치로 이동시킨다.
function rendering() {
const item = [];
const result = [];
let itemCount = 0;
let index = 0;
for (const key in mainInfoData.recommendInfo) {
itemCount += 1;
item.push(mainInfoData.recommendInfo[key]);
}
//무한 슬라이드를 위해서 아이템 앞뒤로 2개씩의 아이템이 더 필요 => itemCount +4 만큼 더 렌더링필요
//아이템이 총 4개일경우 렌더링해야되는것 : 34 1234 12
//아이템이 총 5개일경우 렌더링해야되는것 : 45 12345 12
// 현재는 23 123 12 모습. 총 7개 아이템 필요
// oreder는 SlideItem이 recommendInfo를 담은 item의 몇 번째 정보를 가지고 있어야 하는지 정하는데 사용된다.
while (index < itemCount + 4) {
let order = index < itemCount ? index : index % itemCount;
result.push(<SlideItem slideItem={item[order]} key={index} />);
index++;
}
return [itemCount, result];
}
실제 사용되는 이미지의 갯수(=itemCount)와 reactElemnet가 들어있는 result 함수를 반환하는 함수를 작성하였다.
mainInfoData 는 프로젝트에서 사용된, 프론트 단에서 사용되는 json파일이고, recommendInfo는 파일내에서 만들어진 객체이며, 내부에는 또다른 객체를 저장한 형태이다.
-- transform
const [index, setIndex] = useState(0);
const moveLength = 907;
const transitionTime = 500;
const transitionStyle = `transform ${transitionTime}ms ease-in-out`;
<SlideTemplateBlock
style={{
transition: slideTransition,
transform: `translateX(${-1 * (moveLength * index)}px)`,}}>
{result} // rendering함수의 result값
</SlideTemplateBlock>
moveLength의 경우 리액트 ref를 사용하여 사용된 슬라이드의 아이템 크기를 구해주어야 하는데, 이부분은 아쉽지만.. 시간은 없고, 할건 많아... 값이 고정된 크기이기도 하였기 때문에 하드코딩하였다.
index의 값을 변경되면 transform의 값이 바뀌며, 슬라이드가 되게된다.
index의 값을 변경시키는건 마우스의 클릭시 또는 일정시간이 지나면 자동으로 증가하게 구현하였다.
자동으로 증가하는 경우는 react의 경우 setInterval을 사용하는경우 약간의 문제가 있는데, 리액트의 상태(state)와 관련해서 사용될경우 제대로 작동이 되지 않을수있다. 때문에 useInterval이라는 Dan이라는 개발자분이 작성한 사용자 함수를 사용하였다.
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
useInterval(() => {
if (isOngoing && !isMouseOver) {
if (index >= itemCount - 2) replaceSlide(index);
setIndex(index + 1);
setTransition(transitionStyle);
} else {
setIsOngoing(true);
}
}, 2000);
(useInterval 함수와 사용)
index의 경우 슬라이드의 이미지의 막전(예시의 img4)에 도달할경우 -1*index를 해주었다. (img1 또는 img3의 위치로 이동을 위해), transitionStyle을 제거하여 슬라이드 효과를 제거 하여 이동 한 후, 다시 설정 하였다.
function replaceSlide(index) {
setTimeout(() => {
setTransition('');
setIndex(-1 * index);
}, transitionTime);
}
(이미지의 위치가 슬라이드의 실제 아이템을 벗어날경우 실행되는 함수)
위의 함수를 사용하여 버튼의 click이벤트에 인덱스의 가감을 설정하였고,
또한 마우스 over시 또는 버튼을 클릭하고 있을시에는 자동슬라이드가 작동하지 않도록 하였다.
버튼의 위치는 현재 슬라이드 템플릿의 width는 1440px, 1080px, 100%의 3가지로 나뉘는데, 1440px와 1080px에서 동일한 위치에 있을수 있도록 아래와 같이 css의 calc 함수를 사용하였다.
left: calc((100% - 940px) / 2);
(작동 화면)
3주차에는 css를 어떻게 짜야하는지, 그리고 슬라이드의 구현에서 많이 헤맸던것 같다.
사실 내가 직접 css를 짜는건 별로 없겠지 싶었다. 디자이너분이 css까지 완성해주지 않을까 라는 생각을 하였기 때문인데, 제공받은 figma의 파일을 그대로 복붙해서 쓰면 되는지 알았다.
figma의 css는 내 생각보다 난잡하였고, 반응형적인 페이지가 아니였기 때문에, 한차례 고생을 한 후 직접 css를 짜는걸로 결정하게 되었다..
그래도 이전에 공부했던 책에서 기본적인 반응형 틀과 상태관리에 대한 좋은 예시가 있었기 때문에 맨땅에 헤딩하는정도는 아니였던것 같다.
슬라이드의 경우는 이전에 자바스크립트에서 setTimeout과 requestanimationframe을 사용하여 구현한 경험이 있었기때문에, 비슷하게 구현하면 되겠지 했는데, react의 기본환경을 간과해서 하루정도 삽질을 했던것 같다.
그래도 슬라이드를 구현하면서 리액트의 작동방식에 대해 다시금 이해하게 되었고, 리액트에 맞는 기능구현이 어떠한 것인지 감을 잡을 수 있었던것 같다.
음, 그리고 코드의 작성은 아쉬움을 느꼇다. 구현을 할때에는 스스로는 왜 이렇게 작동을 하는지. 이게 함수에서 어떠한 의미인지 알고 있었지만, 다른 사람이 보거나, 또한 내가 이후에 다시 보게 된다면 왜 이렇게 작성한건지, 이 값이 왜 들어가야되는지에 대해서 한눈에 파악하기 어려울것이라 느꼇다.
함수명이나 변수명도 조금 더 신경써서 작성해주면 좋을것 같다 생각하지만,, 네이밍은 생각보다 중요한 문제지만 어려운 문제다.
어쨌건 어렵진 않더라도, 약간 복잡해보이는 로직이 들어가있는 함수, 사용되는 변수의 경우에는 조금 귀찮더라도 주석을 달아주는게 좋을것 같다.
마지막으로 슬라이드의 버튼을 고정시키는것도 꽤나 헤맸는데.. calc함수를 사용하면 정해진 규격의 반응형 환경에서 알맞은 위치에 위치시킬수있다는것을 알게 되었다.