일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- SCSS extend
- SCSS use
- 프로그래머스 K_Digital Training
- KDT 프로그래머스 데브코스 프론트엔드
- 리스트 렌더링
- 폼 입력 바인딩
- 고양이 사진 검색기
- SCSS import
- 리액트
- 다른컴퓨터에서 git사용
- 프로그래머스 데브코스
- Vue
- flex
- vue mixin
- 프로그래머스 프론트엔드 데브코스
- postcss
- 프로그래머스 데브코스 프론트엔드
- vuex map
- intersection opserver
- 쌓임맥락
- Spacer
- netlify redirect
- react next
- git 같은계정 다른 컴퓨터
- KDT 프로그래머스
- nextjs사용법
- 이벤트 수식어
- vue 지역 컴포넌트
- vue 이벤트 수신
- SCSS forward
- Today
- Total
혼자 적어보는 노트
프로그래머스 데브코스 TIL - Day 55 본문
✅ 오늘의 학습
📌 React (8)
- 사용자 정의 Hook 연습하기
useAsync / useHotKey
- 컴포넌트 연습하기
Modal / Toast
useAsync
이전의 setTimeout과 마찬가지로 함수버전과 즉시실행 버전을 만들었다.
로직을 구성하는 것은 어렵진 않으나 비동기 실행 시 중복 요청에 대한
결과 값을 마지막 요청의 것으로만 적용이 되게 방어 코드를 작성해야 한다.
const useAsyncFn = (fn, deps) => {
const lastCallId = useRef(0);
// 비동기 실행시 중복 요청에 대한 결과값을 마지막 요청의 것으로만 적용하기 위함.
const [state, setState] = useState({
isLoading: false,
});
const callback = useCallback((...args) => {
const callId = ++lastCallId.current;
if (!state.isLoading) {
setState({ ...state, isLoading: true });
}
return fn(...args).then(
(value) => {
console.log(callId, lastCallId);
callId === lastCallId.current && setState({ value, isLoading: false });
return value;
},
(error) => {
callId === lastCallId.current && setState({ error, isLoading: false });
return error;
},
);
// eslint-disable-next-line
}, deps);
return [state, callback];
};
lastCallId를 사용하여 마지막 요청의 id값을 저장하고
callback함수 내부에서 callId를 사용하여 들어온 요청에 따라 숫자를 증가시킨다.
연달아 요청을하면 lastCallId의 값이 계속 증가하게되어 누적된 값을 가지게 되고
callId의 경우 함수를 실행시켰을 시점의 값을 그대로 가지고 있기 때문에 lastCallId값과 일치하지 않아
앞부분 call을 무시할 수 있다.
useHotKey
키보드 단축키를 사용하기 위한 hook
전역에서 사용할 단축키와 특정 영역에서 사용할 단축키를 구분하여 사용할 수 있도록 구분해서 작성해 주었다.
keyboard이벤트를 통해 동시에 입력된 키가 어떤 키인지 알 수 있었고
지정한 bitmasks값을 사용하여 어떤 버튼이 동시에 눌렸는지 구분을 할 수 있다는 것이 신기했다.
const ModifierBitMasks = {
alt: 1,
ctrl: 2,
meta: 4,
shift: 8,
};
그리고 리액트에서 이벤트를 내려줄 때는 native event가 아니라
래핑된 이벤트를 전달을 해준다는 것을 이번에 알게 되었다.
공식문서를 살펴보니 리액트에서 이벤트의 인터페이스는 브라우저의 고유 이벤트와 같고
모든 브라우저에서 동일하게 동작되지만 래핑이 되어있다.
브라우저의 고유 이벤트가 필요하다면 nativeEvent 속성을 사용하여 활용할 수 있다.
Modal 컴포넌트
createPortal을 사용하여 body에 createElement로 생성한 el에 모달을 넣어줄 수 있다.
const Modal = ({
children,
width,
height,
visible = false,
onClose,
...props
}) => {
const containerStyle = useMemo(
() => ({
width,
height,
}),
[width, height],
);
// 부모에서 onClose를 받아서 처리
const ref = useClickAway(() => {
onClose && onClose();
});
const el = useMemo(() => document.createElement('div'), []);
// body에 모달 생성
useEffect(() => {
document.body.appendChild(el);
return () => {
document.body.removeChild(el);
};
});
return ReactDOM.createPortal(
<BackgroundDim style={{ display: visible ? 'block' : 'none' }}>
<ModalContainer
{...props}
ref={ref}
style={{ ...props.style, ...containerStyle }}
>
{children}
</ModalContainer>
</BackgroundDim>,
el,
);
};
export default Modal;
Potals
강의에서 처음 접해보는 부분이라 portal에 대해 알아보았다.
ReactDOM.createPortal(child, container)
첫 번째 인자는 엘리먼트나 렌더링할 수 있는 React의 자식요소를 넣을 수 있고
두 번째 인자는 컨테이너를 지정할 수 있다.
==> 두 번째 인자의 자식으로 첫 번째 인자가 들어가게 된다.
Component Tree 위치
createPortal을 사용하여 body 내부에 특정 컴포넌트를 위치시켰을 경우
dom의 위치는 body내부에 있지만 React의 컴포넌트 tree에서는 호출 위치의 하위에 위치하게 된다.
* Portal 엘리먼트는 modal의 자식이 마운트 된 이후에 DOM 트리에 삽입된다.
이벤트 버블링
React의 컴포넌트 tree에서는 호출 위치의 하위에 위치하고 있기 때문에
부모이벤트에 이벤트 버블링 현상이 생기게된다.
Lifecycle
createPortal로 연결된 경우에도 컴포넌트의 생명주기와 합성 이벤트가 적용이 된다.
컴포넌트로 래핑하여 컴포넌트 내에서 lifecycle을 통해 state및 props의 변화를 전달시킬 수 있다.
그리고 lifeCycle에 의해 자동으로 unMount된다.
Toast 컴포넌트
작은 팝업형태로 알림을 띄워주는 컴포넌트
<button onClick={() => Toast.show('안녕하세요!', 3000)}>Show Toast</button>
버튼의 onClick이벤트를 통해 Toast의 알림 메세지와 메세지의 지속 시간을 전달하여 화면에 띄우는 방식이다.
Toast
ㄴindex.js
ㄴToastItem.js
ㄴToastManager.js
Toast의 경우 modal과 마찬가지로 body태그 내부에서 생성해야하는데
body태그 내부에서 Toast를 생성을 담당하는 index.js
bind를 통해 생성된 Toast내부에 ui를 넣고 빼는 것을 담당하는 ToastManager.js
Toast내부의 ui와 animation을 담당하는 ToastItem.js로 구성하여 만들었다.
[Toast/index.js]
import ReactDOM from 'react-dom';
import ToastManager from './ToastManager';
class Toast {
portal = null;
constructor() {
const portalId = 'toast-portal';
const portalElement = document.getElementById(portalId);
if (portalElement) {
this.portal = portalElement;
return;
} else {
this.portal = document.createElement('div');
this.portal.id = portalId;
document.body.appendChild(this.portal);
}
ReactDOM.render(
<ToastManager
bind={(createToast) => {
this.createToast = createToast;
// show함수를 실행했을 때 토스트매니저 안의 createToast가 실행된다.
}}
/>,
this.portal,
);
}
show(message, duration = 2000) {
this.createToast(message, duration);
}
}
export default new Toast();
클래스를 통해 Toast를 만들고 내부에서 ReactDom.render()를 통해 바로 렌더를 해줌으로써
컴포넌트 자체를 최상위에 작성하지 않고도 body태그 안에 위치시킬 수 있다.
createPortal와 비슷하지만 Portal과는 다르게 render를 사용하여 body내부에 위치시켰을 경우,
Dom 위치는 물론 React의 컴포넌트 트리에서도 최상위에 생성된다.
* 그리고 render는 unMount를 직접 해주어야한다.
💡 근데 여기서 잠깐!!
render를 사용하게되면 상단에 에러가 발생하게된다.
ReactDOM.render는 React 18에서 더 이상 지원되지 않아서 createRoot를 사용을 하라는 뜻!
React 공식문서 : Updates to Client Rendering APIs
이전에 아래와 같은 형태로 작성했다면
// before
import ReactDom from 'react-dom'; before
const container = document.createElement('div');
document.body.appendChild(container);
ReactDom.render(content, container); // before
이렇게 작성하면 된다.
// after
import { createRoot } from 'react-dom/client';
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
root.render(content);
✍ 느낀 점
리액트 컴포넌트 만들기와 hook 만들기 파트가 끝났다!
자주 사용될만한 컴포넌트와 hook들을 만들어본 경험이 프로젝트를 진행할 때 많은 도움을 줄 듯 했다.
그리고 컴포넌트들을 조합해서 또 다른 컴포넌트를 만들어 보았는데
이래서 재사용성이 좋은 컴포넌트들을 만들어 놓으면 블록처럼 조합해서 쓰기가 좋다는 것을
몸소 깨닫게 되었다..ㅎ
'스터디' 카테고리의 다른 글
프로그래머스 데브코스 TIL - Day 69 (0) | 2022.06.23 |
---|---|
프로그래머스 데브코스 TIL - Day 56 (0) | 2022.06.05 |
프로그래머스 데브코스 TIL - Day 54 (0) | 2022.06.02 |
프로그래머스 데브코스 TIL - Day 53 (0) | 2022.06.02 |
프로그래머스 데브코스 TIL - Day 51 (0) | 2022.05.30 |