과제의 대략적인 요구사항은 아래와 같았습니다.
++ API 서버는 별도로 주어지지 않으며, json 파일만을 가진 repo를 json 서버로 구동하여 로컬환경에서 작업할것
초기 셋팅은 이전 과제물들과 동일하게 진행하였습니다.
불필요한 파일이 제거된 CRA를 생성하고,
ESLint,Prettier,husky가 설정이 된 repo를 main으로 올린 뒤, 팀원들이 모두 클론하여 각자의 브런치를 만들고 진행하였습니다.
추가적으로, 저는 이번 프로젝트에서 TS를 사용하였기 때문에, 우선 CRA를 클론한 후, typescript CRA를 따로 생성하여, 기존 파일에 덮어 쓰기하여 파일을 변경하여 git을 관리하였습니다.
debounce와 캐시 스토리지를 사용하였습니다.
debounce는 seTimeout을 통해서 일정횟수에 한번씩만 동작하게 하는 함수입니다.
캐시 스토리지는 로컬스토리지와 비슷하게 작동하지만, URL을 파라미터로 받아, 만약 같은 URL로의 요청일시 저장된 캐싱값을 사용하는 브라우저의 내장 기능-함수입니다.
useDebounce 참고 사이트
캐시 스토리지 참고사이트
두 사이트를 통해 얻게된 지식으로 기능구현은 크게 문제 없었습니다.
const debouncedSearchTerm = useDebounce(inputValue, 300);
useEffect(() => {
const getSick = async () => {
if (isBlankVal(debouncedSearchTerm)) {
return;
}
const SearchURL = `URL...`;
const cacheData = await getSingleCacheData(debouncedSearchTerm.trim(), SearchURL);
if (cacheData.length > 0) {
setSickData(cacheData);
} else {
const response = await SearchService.getSick(debouncedSearchTerm.trim());
setSickData(response.data);
addDataIntoCache(debouncedSearchTerm.trim(), SearchURL, response.data);
console.info('calling api');
}
};
if (debouncedSearchTerm) {
getSick();
} else {
setSickData([]);
}
}, [SearchService, debouncedSearchTerm]);
replaceALl을 사용하여 검색값과 같은 부분은 모두 strong 태그로 감싼 텍스트를 생성하였고, 이후 이 값을 react JSX의 dangerouslySetInnerHTML 기능을 사용하여 텍스트가 태그가 있으면 그대로 반영되도록 하였습니다.
하지만 dangerouslySetInnerHTML의 사용은 지양하는것이 좋기 때문에, regex를 사용하여 배열을 만들고, 그 배열을 map을 돌려 처리하는것이 더 옳은 방법이였다고 생각합니다.
export function parseTextBold(originText: string, targetText: string) {
const text = originText.replaceAll(targetText, '<strong>' + targetText + '</strong>');
return text;
}
<div dangerouslySetInnerHTML={{ __html: parseTextBold(sickName, inputValue) }}></div>
=> 구현과 사용
Input이 Focus 되어있는 상황에서, 키보드 이벤트를 통해 렌더링된 List에 UI상의 변화를 주는 요구사항이였습니다.
기능구현은 커스텀훅을 사용하여, Form을 감싸는 container에서 키보드 이벤트를 주었습니다.
대략적인 기능 구상은 아래와 같습니다.
추가적으로, 해당 리스트 값을 클릭시 마찬가지로 Num값에 대한 state를 변경해주도록 하였습니다.
이러한 경우 Div가 클릭되면 input창으로부터 focus가 벗어나게되어 keyboard 이벤트가 발생되지 않는데, forward Ref를 통해 ref를 넘겨주어도 괜찮은 방법이지만, List를 감싸주는 컴포넌트에 hidden input을 만들어 그곳에 focus를 주는 방법을 택하였습니다.
const onKeyEventHanler = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.nativeEvent.isComposing) {
return;
}
const typing = e.code;
upDownValHandle(typing, sickData);
};
export function useSelectVal() {
const [selected, setSelected] = useState('');
const [selectNum, setSelectNum] = useState(0);
const handleSelected = (value: string, idx?: number) => {
setSelected(value);
if (idx !== undefined) {
setSelectNum(idx + 1);
}
};
const upDownValHandle = (typing: string, sickData: sick[]) => {
switch (typing) {
case 'ArrowDown':
if (sickData.length <= selectNum) {
setSelectNum(1);
const selectData = sickData[0];
handleSelected(selectData?.sickNm);
} else {
setSelectNum(prev => prev+1);
const selectData = sickData[selectNum];
handleSelected(selectData?.sickNm);
}
break;
case 'ArrowUp':
if (selectNum <= 1) {
setSelectNum(sickData.length);
const selectData = sickData[sickData.length - 1];
handleSelected(selectData?.sickNm);
} else {
setSelectNum(prev => prev-1);
const selectData = sickData[selectNum - 2];
handleSelected(selectData?.sickNm);
}
break;
default:
break;
}
};
==> selected는 선택된 리스트이 텍스트값입니다.
==> const selectData = sickData[selectNum - 2];
와 같은 조건의 경우, 최초에 ArrowDown이 발동하고, up을 할 경우는 예외 조건에 걸리기때문에 up에서의 else문이 발동되지 않습니다. 때문에 최소 2번이상의 ArrowDown 함수가 발동 이후에 up함수의 else부분이 발동되기때문에 -2를 통해 이전 값을 바라보게 됩니다.
이러한 방식으로 구현을 하게되었는데,
구현을 해가며, 한가지 예상치 못한 버그를 발견하였습니다.
인풋값이 영어일때는 로직이 예상대로 실행이 되었지만, 한글이 들어온경우에는 연속하여 2번 실행되는 버그였습니다.
찾아낸 문제에 대한 설명 및 해결법은 아래와 같습니다.
input에서 한글과 같은 조합식 문자를 사용하는경우, OS에서는 composition 이라는 과정을 거치게 됩니다. OS에서는 영어가 아닌 글자가 들어오면, 이것을 해당 언어 글자로 바꿔주는 이벤트를 처리하게됩니다. 이러한 과정에서 키보드 이벤트가 발생하면 OS와 브라우저 동시에 이벤트가 실행되기 때문에, 영어로 입력시에는 정상적으로 작동하지만, 한글의 경우에는 중복실행이 되는것이였습니다.
이 문제는 자바스크립트의 nativeEvent인 isComposing 옵션을 통해, 만약 컴포징중일시에는(OS와 중복 실행될 가능성이 있는 동안에는) 함수가 실행되지 않도록 처리하였습니다.
const onKeyEventHanler = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.nativeEvent.isComposing) {
return;
}
const typing = e.code;
upDownValHandle(typing, sickData);
};
==> 키보드 이벤트에서 isCopsing 중이면 함수가 실행되지 않도록 하였습니다.
(작업 기간 약 2일)
https://pre-onboarding-7th-3-1-9-june.vercel.app/
https://github.com/jun-05/pre-onboarding-7th-3-1-9
캐싱 기능을 깔끔히 구현하셨고, 기타 요구사항이외의 것들을 모두 작성하신 분의 과제물이 BestPractice 로 선정되었습니다.