[2021.08.05] 성능최적화

이다은·2021년 8월 7일
0
post-thumbnail

사실 React에서 성능 최적화는 중요하지 않습니다.
코드 1000줄 정도 리팩토링하면 0.001초 정도 개선될 수 있을까요..?
하지만 중요하지 않은 것과 내가 성능에 대해 알지 못하는 것은 다르죠!
내용을 충분히 익히고 성능 최적화가 필요한 그 순간!
오늘의 지식이 빛을 발할거라고 생각합니다 ✨

💡 좋은 코드란? ➡ "가독성"과 "생산성"을 고려한 코드

  • 흐름을 파악할 수 있는 코드
  • 유지보수가 쉬운 코드
  • 쉽게 사용할 수 있는 코드

📌 기업과제 Review

1. 함수는 input에 따른 output을 기대할 수 있어야 한다.

  • fetchData가 무슨 데이터를 가져오는건지 알 수 없다.
  • 항상 user 데이터만 가져오는게 아닌 원하는 데이터를 fetch 하는 기능을 가진 함수로 만들어야 한다!
    useEffect(() => {
      fetchData().then((res) => userListStorage.save(res))
    }, [])

2. handle 함수

  • handleClick 함수 만들어서, 그 함수에서 두 기능의 함수 호출하자!
  • 분리, 모듈화, 가독성..
    <InputList> 
      <Button onClick={() => { resetNumber(); openModal(); // ❌ }}>카드 입력</Button>
    </InputList>

3. convention 몇 가지

  • jsx가 길어지면 가독성이 떨어지고, 어디서부터인지 놓치게 된다.
    if 문 웬만하면 { 중괄호 } 하자
    삼항연산자 추천

4. 대소문자 조심

  • git은 대소문자를 구분해서 인지를 못해서, 그냥 editor에서 대소문자 변경하면 안된다!

    git이 파일명의 대소문자 변경을 구분하지 못한다면 다음과 같은 설정해줘야 한다
    git config core.ignorecase false


📌 React 성능향상을 위한 몇 가지 방법

  • 성능을 최적화하기 위한 코드는 가독성이 좋지 않고, 유지보수를 더 어렵게 할 때가 있다.
  • 성능 이슈가 크게 문제될만한 곳에만 최적화 하도록 하자.
  • DOM 조작이 CPU리소스를 가장 많이 사용하거나 브라우저에 부하를 주기 때문에, rerender 안 되게 하는 팁이 대부분이다!

💡 React.PureComponent, React.memo

Props 값이 변하지 않으면 다시 렌더하지 않음 (feat. memoizing, memoization)
➡ 부모가 rerender 하면, 자식 컴포넌트도 모두 rerender 된다!

  • 아래 예시에서 Modal 컴포넌트의 title 값이 바뀌면?
    const Modal = ({ title }) => {
    	const [btnColor, setBtnColor] = useState('yellow');  

    	return (
        <div>
    	  <p>{title}</>
    	  <button onClick={() => { setBtnColor('black') }}>Change Color!</button>
    	  <CounterButton color={btnColor} />
    	</div>
      );
    }
  • Modal 컴포넌트는 리렌더링 된다
  • 그럼, 자식인 CounterButtonbtnColor props의 변경사항이 없는데도 리렌더링 된다.

➡ class 컴포넌트는 React.PureComponent,
   functional 컴포넌트는 React.memo 를 사용해서
   props 값이 변하지 않으면 다시 렌더링 하지 않도록 CounterButton를 고친다!

(멘토님의 조언)

  • 항상 PureComponent와 memo를 쓸 필요는 없습니다.
  • 같은 props인데 rerender 될 경우가 많은 컴포넌트인가? 할 때 쓰시면 좋을 것 같습니다 😉

💡 Avoid Object/Array Mutation

➡ state가 Object나 Array일 때 state 값 자체를 변경하지 말자

  • (변경 전)
    handleClick() {
      this.setState(state => ({
        words: state.words.concat(['marklar']) // ❌
      }));
    }
  
    function updateColorMap(colormap) {
      colormap.right = 'blue'; // ❌
    }
  • (변경 후)
  • ES6의 spread syntax
    handleClick() {
      this.setState(state => ({
        words: [...state.words, 'marklar'], // ✅
      }));
    };
  • ES6의 Object.assign
    function updateColorMap(colormap) {
      return Object.assign({}, colormap, {right: 'blue'}); // ✅
    }
  • ES8의 object spread properties
    function updateColorMap(colormap) {
      return {...colormap, right: 'blue'}; // ✅
    }

💡 참조형 데이터를 주의(1)

  • (변경 전)
    () => { alert('clicked!!'); }는 함수
    함수는 참조형! 따라서 다시 렌더링 될때마다 새로 생성된다.
    참조형의 메모이제이션 useCallback으로 해결해보자!
function Modal() {
  return (
      <CounderButton handleClick={() => { alert('clicked!!'); }} /> // ❌
  );
}
  • (변경 후)
    useCallback으로 onHandleClick 함수를 새로 만들어서 props로 준다!
function Modal() {
    const onHandleClick = useCallback(() => { // ✅
        alert('clicked!!');
    });
    return (
        <CounderButton handleClick={onHandleClick} />  // ✅
    );
}

💡 참조형 데이터를 주의(2)

  • (변경 전)
    { name: 'About', link: '/about' } 객체도 참조형이다
    따라서 다시 렌더링 될때마다 새로 생성된다.
function TopBanner() {
  return (
      <MenuList menu={{ name: 'About', link: '/about' }} />
  );
}
  • (변경 후)
    MENU_ITEM으로 상수로 따로 빼두면 매번 다시 리렌더링 되지 않는다.
function TopBanner() {
  return (
      <MenuList menu={MENU_ITEM} /> // ✅
  );
}
const MENU_ITEM = { // ✅
  name: 'About', 
  link: '/about'
}

💡 useEffect 다시 보기(1)

  • (변경 전)
    아래 코드는 BigButton이 다시 렌더링 될 때마다, changeColor 함수를 생성한다는 문제점이 있다.
    async 함수useEffect에 넣어서 사용하면 되지만, useCallback을 사용한다면..?
function BigButton({ message }) {
  const [color, setColor] = useState();

  async function changeColor(isPrimary) {
    const data = await fetchColor(message.type, isPrimary);
    setColor(data.color);
  }

  useEffect(() => {
    changeColor(true);
  }, [changeColor])
}

(변경 후) changeColor 함수가 정말로 필요할 때만 호출되도록 해야한다

function BigButton({ message }) {
	const [color, setColor] = useState();
	const changeColor = useCallback(// ✅ `useCallback` 사용해서 `message 가 변경되었을 때만` changeColor가 갱신되도록 함
		async isPrimary => {
			const data = await fetchColor(message.type, isPrimary);
			setColor(data.color);
		},
		[message]
	);
	useEffect(() => {
		changeColor(true);
	}, [changeColor])
}

💡 useEffect 다시 보기(2)

➡ useEffect는 의존성 배열(두 번째 매개변수)을 관리하는 것이 관건이다.

  • (변경 전) 의존성 배열에 count가 있다.
    function Counter() {
      const [count, setCount] = useState(0);

      useEffect(() => {
        const id = setInterval(() => {
          setCount(count + 1);
        }, 1000);
        return () => clearInterval(id);
      }, [count]);

      return <h1>{count}</h1>;
    }

(변경 후) 의존성 배열을 최대한 줄이자!

    function Counter() {
      const [count, setCount] = useState(0);
      useEffect(() => {
        const id = setInterval(() => {
          setCount(c => c + 1); // ✅ This doesn't depend on `count` variable outside
        }, 1000);
        return () => clearInterval(id);
      }, []); // ✅
      return <h1>{count}</h1>; 
    }

📌 Advanced React

컴포넌트의 공통 로직을 관리하는 HOC(Higher Order Component)

💡 컴포넌트를 매개변수으로 받아서 새로운 컴포넌트를 반환하는 함수다.

  • 컴포넌트의 공통로직을 만들어서 재사용하고 싶을 때 사용한다.
  • 클래스형 컴포넌트에서 사용한다. (함수형은 custom hook 으로!)

예제

  • react-router의 withRouter도 HOC
  • redux의 connect도 HOC

📌 React 디버깅 방법

1. React devTools

2. Debugger for Chrome

    ➡ .vscode/launch.json을 만들고 F5로 실행

// launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Chrome",
            "type": "chrome",
            "request": "launch",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceRoot}/src"
        }
    ]
}

3. Redux devTools

profile
단단_프로트엔드개발자!

0개의 댓글