TS스터디 팀원들과 함께 매주 주차별 과제를 진행합니다
https://github.com/bradtraversy/vanillawebprojects
const toggleButton = document.getElementById('toggle') as HTMLButtonElement;
const closeButton = document.getElementById('close') as HTMLButtonElement;
const openButton = document.getElementById('open') as HTMLButtonElement;
const modal = document.getElementById('modal') as HTMLDivElement;
const navButton = document.getElementById('navbar') as HTMLDivElement;
type toggleState = {
stateName: 'toggle';
isToggle: boolean;
};
type modalState = {
stateName: 'modal';
isShow: boolean;
};
type stateType = toggleState | modalState;
const useState = <T extends stateType>(status: T): [() => T, (state: T) => void] => {
let initialState = status;
const state = () => initialState as T;
const setState = (newState: T) => {
initialState = newState;
newState.stateName === 'toggle' ? toggleRender() : modalRender();
};
return [state, setState];
};
const [navToggle, setNavToggle] = useState({
isToggle: false,
stateName: 'toggle',
} as toggleState); // isToggle: false가 아닌 isToggle: boolean로 하기 위한 타입 지정
const [showModal, setShowModal] = useState({
isShow: false,
stateName: 'modal',
} as modalState);
const toggleAction = (e: Event) => {
let current = navToggle();
// 토글이 되어 있는 상황일 때(=navbar가 열려 있는 상황일 때)
if (current.isToggle) {
// navbar 외부 클릭 시 (navbar를 닫아야 함)
if (
e.target !== toggleButton &&
!toggleButton.contains(e.target as HTMLElement) &&
e.target !== navButton &&
!navButton.contains(e.target as HTMLElement)
) {
setNavToggle({ isToggle: false, stateName: 'toggle' });
}
// 토글 버튼 클릭 시 (navbar를 닫아야 함)
if (toggleButton.contains(e.target as HTMLElement)) {
setNavToggle({ isToggle: false, stateName: 'toggle' });
}
return;
}
// 토글이 안 되어 있는 상황일 때(=navbar가 닫혀 있는 상황일 때)
if (!current.isToggle) {
if (toggleButton.contains(e.target as HTMLElement)) {
setNavToggle({ isToggle: true, stateName: 'toggle' });
}
return;
}
};
const modalAction = () => {
let current = showModal();
// if (current.isShow) {
// setShowModal({ isShow: false, stateName: 'modal' });
// return;
// }
// if (!current.isShow) {
// setShowModal({ isShow: true, stateName: 'modal' });
// return;
// }
setShowModal({ isShow: !(current.isShow), stateName: 'modal' });
};
// 리렌더링 로직들
// 상태가 true인 값으로 리렌더링이 호출 되면 navbar or modal을 엽니다
// 상태가 false인 값으로 리렌더링이 호출 되면 navbar or modal을 닫습니다
const toggleRender = () => {
if (navToggle().isToggle) {
document.body.classList.add('show-nav');
}
if (!navToggle().isToggle) {
document.body.classList.remove('show-nav');
}
};
const modalRender = () => {
console.log(showModal());
if (showModal().isShow) {
modal.classList.add('show-modal');
}
if (!showModal().isShow) {
modal.classList.remove('show-modal');
}
};
// 이벤트 할당
openButton.addEventListener('click', modalAction);
closeButton.addEventListener('click', modalAction);
document.body.addEventListener('click', toggleAction);
이번 주 과제는 생각보다 난해했던 것 같습니다
아무래도 단순한 토글 기능이 아니라 왼쪽 navbar의 경우, css를 body에 추가해 놓았었기 때문에
body에도 클릭이벤트를 등록 해 놓아야 했고 , 이렇게 되면 화면 전체를 클릭할 때마다 이벤트가 발생하므로 클릭하는 위치에 따라 동작을 다르게 해야 했기 때문입니다
기존에 작성되어 있던 JS로직을 미리 봤었을 땐 코드는 짧지만 로직 자체가 복잡했었기 때문에 어떻게 다르게 작성을 해야 할지 고민을 많이 했었습니다
지난 주차의 과제를 구현하면서 제가 가장 중요하게 생각한 것은 단순한 DOM을 다루면서도
아래의 2가지의 추가 구현에 집중했었습니다
상태값
일방향적인 업데이트 로직 ( 오로지 상태 변경을 통해 화면이 리렌더링 )
사실 몇몇 과제는 굳이 위의 구현들을 추가해야 할 필요성이 없어 보이는 것도 있었습니다
그렇지만 이번주차는 위의 구현을 통해 코드 길이는 더 길어졌지만 ,
상대적으로 흐름에 따른 코드의 가독성을 보다 높일 수 있었던 같습니다
주요 기능 구현 사항은 아래와 같습니다
이 2개를 union type으로 사용해서 최종 상태값의 타입을 지정했고,
추후에 타입 기반으로 리렌더링을 진행 해야 하므로 union type을 구분하기 위한 Discriminated Union으로써 , stateName 키값을 사용합니다
type toggleState = {
stateName: 'toggle';
isToggle: boolean;
};
type modalState = {
stateName: 'modal';
isShow: boolean;
};
type stateType = toggleState | modalState;
리액트의 useState()를 구현해보는 과정에서 사용한 로직입니다
상태를 변수가 아닌 함수로 사용하고 있으며, 그 외에도 리액트와
완전히 똑같이 작동하지는 않지만 아래의 기능들을 구현했습니다
자세한 설명은 바닐라로 useState 구현해보기
const useState = <T extends stateType>(status: T): [() => T, (state: T) => void] => {
let initialState = status;
const state = () => initialState as T;
const setState = (newState: T) => {
initialState = newState;
// Discriminated Union을 이용해서 상태 종류에 따라 서로 다른 리렌더링 로직 수행
newState.stateName === 'toggle' ? toggleRender() : modalRender();
};
return [state, setState];
};
const [navToggle, setNavToggle] = useState({
isToggle: false,
stateName: 'toggle',
} as toggleState);
const [showModal, setShowModal] = useState({
isShow: false,
stateName: 'modal',
} as modalState);
여기서 중요한 것은 처음에 상태를 선언할 때
아래와 같이 초깃값을 주게 된다면
isToggle , isShow 의 타입이 boolean이 아니라 false 리터럴 값으로 고정이 되어 버립니다
{
isToggle: false,
stateName: 'toggle',
}
그러므로 반드시 type assertion을 사용해야 합다
{
isToggle: false, // boolean 타입
stateName: 'toggle',
} as toggleState
const toggleAction = (e: Event) : void => {
const current: toggleState = navToggle();
// 토글이 되어 있는 상황일 때(=navbar가 열려 있는 상황일 때)
if (current.isToggle) {
// navbar 외부 클릭 시 (navbar를 닫아야 함)
if (
e.target !== toggleButton &&
!toggleButton.contains(e.target as HTMLElement) &&
e.target !== navButton &&
!navButton.contains(e.target as HTMLElement)
) {
setNavToggle({ isToggle: false, stateName: 'toggle' });
}
// 토글 버튼 클릭 시 (navbar를 닫아야 함)
if (toggleButton.contains(e.target as HTMLElement)) {
setNavToggle({ isToggle: false, stateName: 'toggle' });
}
return;
}
// 토글이 안 되어 있는 상황일 때(=navbar가 닫혀 있는 상황일 때)
if (!current.isToggle) {
if (toggleButton.contains(e.target as HTMLElement)) {
setNavToggle({ isToggle: true, stateName: 'toggle' });
}
return;
}
};
const modalAction = () : void => {
const current: modalState = showModal();
setShowModal({ isShow: !(current.isShow), stateName: 'modal' });
};
// 토글 리렌더링
const toggleRender = () : void=> {
const current: toggleState =navToggle()
if (current.isToggle) {
document.body.classList.add('show-nav');
}
if (!current.isToggle) {
document.body.classList.remove('show-nav');
}
};
// 모달 리렌더링
const modalRender = () : void => {
const current: modalState = showModal()
if (current.isShow) {
modal.classList.add('show-modal');
}
if (!current.isShow) {
modal.classList.remove('show-modal');
}
};
상태 업데이트가 발생하게 되면 , 해당 상태에 따른 리렌더링을 진행하는 로직입니다
openButton.addEventListener('click', modalAction);
closeButton.addEventListener('click', modalAction);
document.body.addEventListener('click', toggleAction);