목차
목차1. 리액트 2화1. 컴포넌트 스타일링 - 13m1-1. 스타일 시트를 이용하는 방법1-2. inline을 통한 Css 적용1-3. CSS in JS2. useMemo + React.memo- 7m + 3m2-1 useMemo2-2. React.memo(공식문서 link)3. useCallback- (공식문서) 6m4. customHook - 13m5. storybook- 13m6. 사용사례 (로그인, 회원가입) - 30m2.1.1-1.2.2-1.
1. 리액트 2화
1. 컴포넌트 스타일링 - 13m
1-1. 스타일 시트를 이용하는 방법
- 일반적인 css 파일을 import 해서 적용하는 방법
- 생략
1-2. inline을 통한 Css 적용
prop을 통해 동적으로 스타일이 변경될 경우 사용
1-3. CSS in JS
- StyledComponent, Emotion 등 사용가능
- 기본적으로 Emotion 코드를 적용하기 위해서 Babel을 사용하는데, CRA환경에서는 babelrc를 통한 설정이 불가하다.
- 따라서 플래그마 (
/** @jsxImportSource @emotion/react */
)를 코드 상단에 작성하여, 파서에게 알려주는 방법으로 해결할 수 있다. - 단 매번 모든 파일에 작성하는 것은 번거롭기 때문에 이를 자동화하여 사용
- 1) eject를 통해 설정 파일을 불러내어 babelrc를 사용하기
- 2) craco 라이브러리를 통해 설정을 overide하여 적용하기
- 설치 (
npm i -D @craco/craco
) - babel 설정을 craco.config.js 안에서 작성
- package.json에서 실행 스크립트를
craco start
로 변경 - Emotion의 css 모듈을 통한 스타일링
- Emotion의 styled 모듈을 통한 styledcompoent 방식의 스타일링
Emotion을 통한 실습 (Emotion)
코드
module.exports = { babel: { presets: ["@emotion/babel-preset-css-prop"], }, };
EmotionBox 예제
import React from "react"; import { css } from "@emotion/react"; const style = css` color: blue; background-color: red; width: 150px; height: 150px; `; const EmotionBox = () => { return ( <div css={style}> <AnotherComponent /> </div> ); }; const anotherStyle = css({ textDecoration: "underline", }); const AnotherComponent = () => ( <div css={anotherStyle}>Some text with an underline.</div> ); export default EmotionBox;
EmotionStyledBox 예제
import styled from "@emotion/styled"; const EmotionStyledBox = styled.div` color: white; background-color: royalblue; width: 150px; height: 150px; `; export default EmotionStyledBox;
2. useMemo + React.memo- 7m + 3m
값비싼 계산의 함수의 경우는 useMemo를 통해 memoization 하여, 관련된 상태의 변경 시에만 새로 계산하여, 컴포넌트를 리렌더링하도록하고,
부모컴포넌트의 변경과 관련 없는 자식 컴포넌트의 리렌더링 방지를 위해 React.memo를 사용할 수 있다.
함수 컴포넌트의 리렌더링 상황
- 자신의 상태가 변경될 때
- 부모 컴포넌트로 부터 받는 props가 변경될 때
- 부모 컴포넌트의 상태가 변경될 때 ⇒
React.memo
2-1 useMemo
useMemo는 memoization을 통해 함수컴포넌트의 렌더링 최적화를 위해 사용
첫번째 인자로 값비싼 비용의 함수를 콜백형태로 받고, 두번째 인자로 의존성 배열을 넣어 사용useMemo( () ⇒ func , [] )
!! 연산속도가 느린 컴포넌트가 있다면, 리렌더링 상황에 취약하다.
- 값 비싼 연산을 가진 컴포넌트 예제
코드
xport default function UseMemoCompoent({ label, n }) { const expensiveComputeSum = (n) => { let result = 0; console.log("게산 시작"); for (let i = 0; i < n; i++) { result = result + n; } console.log("게산 끝"); return result; }; const result = useMemo(() => expensiveComputeSum(n), [n]);
주의
useMemo로 전달된 함수는 렌더링 중에 실행된다. 사이드 이펙트의 경우 useEffect에서 처리할 것!주의
useMemo는 필수가 아니라 값비싼 계산과 같은 필요한 부분에 적용하는 것 (메모리 비용들기 때문에)2-2. React.memo(공식문서 link)
React.memo는 고차컴포넌트(HOC)로, 동일한 props를 통해 같은 결과를 렌더링한다면, 이전에 Memoization 해두었던 컴포넌트를 그대로 재사용한다.
주의
React.memo로 감싸더라도, 내부 컴포넌트에서 useState, useReducer, useContext등을 통해 state를 변경한다면 리렌더링 된다.
주의
부모로 부터 받는 props가 변할 경우에 당연히 리렌더링 한다.
심화
props는 기본적으로 얕은 비교를 수행한다.- 깊은 비교로 리렌더링 여부를 결정하기 위해서는 두번째 인자로 custom비교함수를 넣어주어 사용할 수 있다.
React.memo(ChildComponent, areDeepEquaul)
3. useCallback- (공식문서) 6m
컴포넌트에서 props을 통해 함수를 받게 될 때, 해당 함수는 항상 재정의되기 때문에 React.memo를 적용하여도 리렌더링 된다.
이를 방지하기 위해, 함수 자체를 memoization하여, props으로 넘겨주어도 재정의되어 리렌더링 되는 것을 막을 수 있다.
사용상황
- 하나의 부모요소의 여러개의 자식요소가 있을 때, 특정 자식요소의 변경이 모든 자식요소의 리렌더링 되는 것을 방지할 때 사용
- 즉 여러 자식요소 중 변경이 있는 자식요소만을 리렌더링하고 싶을 때에는
- React.memo를 통해 자식 컴포넌트를 memoization하고,
- props로 넘어오는 함수들은 부모컴포넌트의 변화로 리렌더링시 재정의되지 않도록 useCallback을 통해 memoization 해주어야 한다.
4. customHook - 13m
반복되는 hooks의 사용을 custom 모듈화 하여 가독성있게 표현할 수 있다.
- useToggle 사례
- toggle상태와 toggle변경함수를 리턴하는 hooks
코드비교
// Before const [appleOn, setAppleOn] = useState(false); const [bananaOn, setBananaOn] = useState(false); const [caramelOn, setCaramelOn] = useState(false); const onAppleChange = useCallback((e) => { setAppleOn(e.target.checked); }, []); const onBananaChange = useCallback((e) => { setBananaOn(e.target.checked); }, []); const onCaramelChange = useCallback((e) => { setCaramelOn(e.target.checked); }, []); // After const [appleOn, setAppleOn] = useToggle(false); const [bananaOn, setBananaOn] = useToggle(false); const [caramelOn, setCaramelOn] = useToggle(false);
- useHover 사례 (이벤트 제어)
- 특정 element를 지칭하는 ref와 해당 element의 hover 여부를 반환하는 Hooks
- hooks에서 hover Event 처리
코드
const useHover = () => { const [state, setState] = useState(false); const ref = useRef(null); const handleMouseOver = useCallback(() => setState(true), []); const handleMouseOut = useCallback(() => setState(false), []); useEffect(() => { const element = ref.current; if (element) { element.addEventListener("mouseover", handleMouseOver); element.addEventListener("mouseout", handleMouseOut); } return () => { element.removeEventListener("mouseover", handleMouseOver); element.removeEventListener("mouseout", handleMouseOut); }; }, [ref, handleMouseOver, handleMouseOut]); return [ref, state]; }; export default useHover;
- useKeyPress 사례 (이벤트 제어)
- 특정 key가 눌렸을 때만 true를 리턴하는 hooks
- hooks에서 keypress Event처리
코드
import { useState, useCallback, useEffect } from "react"; const useKeyPress = (targetKey) => { const [state, setState] = useState(false); const handleKeyDown = useCallback( ({ key }) => { if (key === targetKey) { setState(true); } }, [targetKey] ); const handleKeyUp = useCallback( ({ key }) => { if (key === targetKey) { setState(false); } }, [targetKey] ); useEffect(() => { window.addEventListener("keydown", handleKeyDown); window.addEventListener("keyup", handleKeyUp); return () => { window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keyup", handleKeyUp); }; }, [handleKeyDown, handleKeyUp]); return [targetKey, state]; }; export default useKeyPress;
5. storybook- 13m
- 설치
- CRA시
$ npx -p @storybook/cli sb init