본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024-2 React.js 스터디] 윤아영 #2주차

반응형

1. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기

1) deps가 비어있을 때

( * deps는 useEffect의 두 번째 인자를 의미한다. )

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log('컴포넌트가 화면에 나타남');
    return () => {
      console.log('컴포넌트가 화면에서 사라짐');
    };
  }, []);

 

위 코드에서 return() => { 여기에 쓰여진 명령어들은 컴포넌트가 사라질 때 실행된다. }

return 앞에 쓰여진 명령어들은 컴포넌트가 나타날 때 실행된다.(지금은 useEffect의 두 번째 인자가 비어있기 때문이다.)

 

2) deps에 특정값을 넣을 때

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log('user 값이 설정됨');
    console.log(user);
    return () => {
      console.log('user 가 바뀌기 전..');
      console.log(user);
    };
  }, [user]);

 

1. 처음 렌더링될 때 'user 값이 설정됨' 과 user이 출력된다.

2-1. user값이 바뀌면 return 함수 안의 명령어가 실행된다. 이 때 return 부분의 함수를 cleanup 함수라고 한다. 이는 컴포넌트가 사라지기 전에 어떤 리소스나 상태를 정리하는 데 사용된다.

2-2. cleanup 함수 실행 후 'user 값이 설정됨' 과 새로운 user값이 출력된다.

 

3) deps 파라미터를 생략할 때

컴포넌트가 리렌더링 될 때마다 실행된다.


2. useMemo

const count = countActiveUsers(users);
const count = useMemo(() => countActiveUsers(users), [users]);

 

첫번째 코드에서는 컴포넌트가 재렌더링될 때마다 countActiveUsers 함수가 호출된다는 문제가 있다.

두번째 코드에서는 useMemo를 사용하여 users값이 바뀔 때에만 countActiveUsers 함수를 호출한다.

 

→ useMemo를 사용하여 불필요한 계산을 피하고 성능을 높일 수 있다.


3. useCallback

 const onChange = useCallback(
    e => {
      const { name, value } = e.target;
      setInputs({
        ...inputs,
        [name]: value
      });
    },
    [inputs]
  );
const onToggle = useMemo(
  () => () => {
    /* ... */
  },
  [users]
);

 

useCallback 함수는 위와 같이 사용된다.

deps 값이 바뀔 때에만 함수가 새로 생성된다.

 

useCallback은 주로 성능을 최적화하고 자식 컴포넌트의 불필요한 재렌더링을 방지한다.

useCallback을 사용하지 않으면, 컴포넌트가 다시 렌더링될 때마다 새로운 함수가 생성되기 때문에 useCallback으로 함수를 효율적으로 사용할 수 있다.


4. React.memo

const CreateUser = ({ username, email, onChange, onCreate }) => {
  return (
    <div>
      ...
    </div>
  );
};

export default React.memo(CreateUser);

 

React.memo는 특정 컴포넌트의 props가 변경되지 않는 한 다시 렌더링하지 않도록 최적화해준다.

컴포넌트가 전달받은 props 중 하나라도 변경되면 렌더링된다.


5. useReducer

useReducer는 상태 변경 로직이 복잡하거나 여러 가지 조건에 따라 상태가 변경되는 경우, 또는 액션 기반 상태 관리가 필요한 경우에 적합하다.

//기본 문법

const [state, dispatch] = useReducer(reducer, initialState);
  • initialState : 초기 상태
  • reducer 함수 : 현재 상태와 액션(action) 객체를 받아서 새로운 상태를 반환하는 함수
  • dispatch 함수 : reducer 함수에 액션을 보내서 상태를 변경해주는 함수
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

 

reducer 함수는 action.type을 확인하여 상태를 변경한다.

 

const onIncrease = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const onDecrease = () => {
    dispatch({ type: 'DECREMENT' });
  };

 

위 함수들에서 dispatch 함수를 호출하여 각각 'INCREMENT', 'DECREMENT' 액션을 보냄으로써 reducer 함수가 현재 상태를 업데이트하도록 한다.

 

< 액션의 예시 >

// 카운터에 1을 더하는 액션
{
  type: 'INCREMENT'
}
// 카운터에 1을 빼는 액션
{
  type: 'DECREMENT'
}
// input 값을 바꾸는 액션
{
  type: 'CHANGE_INPUT',
  key: 'email',
  value: 'tester@react.com'
}
// 새 할 일을 등록하는 액션
{
  type: 'ADD_TODO',
  todo: {
    id: 1,
    text: 'useReducer 배우기',
    done: false,
  }
}

6. 커스텀 Hooks

 

자주 사용되는 로직을 커스텀 Hooks로 만들어 쉽게 재사용할 수 있다.

커스텀 Hooks 를 만들 때에는 보통 use 라는 키워드로 시작하는 파일을 만들고 그 안에 함수를 작성한다.

 

<예시>

// 커스텀 Hooks 만들기
import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;
// 만든 커스텀 Hooks 사용하기
function App() {
  const [{ username, email }, onChange, reset] = useInputs({
    username: '',
    email: ''
  });
}

7. Context API를 사용한 전역 값 관리

props로 데이터를 컴포넌트에 전달할 때, 깊이 중첩된 컴포넌트 구조에서는 불편할 수 있다.

이를 해결하기 위해 Context API를 사용한다.

리액트의 Context API 를 사용하면, 프로젝트 안에서 전역적으로 사용 할 수 있는 값을 관리 할 수 있다.

const UserDispatch = React.createContext(null);

 

createContext 의 파라미터에는 Context 의 기본값을 설정할 수 있다.

<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>

 

위 코드처럼 Provider을 사용하여 value 값을 설정하면, 하위 컴포넌트에서 props로 전달하지 않아도 이 값을 사용할 수 있다.

const dispatch = useContext(UserDispatch);

 

위 방식으로 하위 컴포넌트에서 전달받은 value 값을 사용할 수 있다.


8. Immer를 사용한 더 쉬운 불변성 관리

리액트에서 배열이나 객체를 업데이트 할 때에는 불변성을 지켜야 한다.

기존 데이터를 변경하지 않고 새로운 데이터를 만들어야 하는데, 이는 상태 관리에서 불변성을 유지하기 위한 원칙이다.

Immer는 이러한 불변성을 유지하면서도 데이터를 쉽게 수정할 수 있도록 한다.

 

1) Immer을 사용하지 않는 업데이트 예시 - ..., concat, filter, map을 사용해야 한다.

const object = {
  a: 1,
  b: 2
};

const nextObject = {
  ...object,
  b: 3
};
const filtered = todos.filter(todo => todo.id !== 2);

 

2) immer을 사용하여 posts 배열 안의 id 가 1 인 post 객체를 찾아 comments 에 새로운 댓글 객체를 추가 구현

const state = {
  posts: [
    {
      id: 1,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 1,
          text: '와 정말 잘 읽었습니다.'
        }
      ]
    },
    {
      id: 2,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 2,
          text: '또 다른 댓글 어쩌고 저쩌고'
        }
      ]
    }
  ],
  selectedId: 1
};
const nextState = produce(state, draft => {
  const post = draft.posts.find(post => post.id === 1);
  post.comments.push({
    id: 3,
    text: '와 정말 쉽다!'
  });
});

 

3) immer 사용법

$ yarn add immer
import produce from 'immer';
const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

 

draft는 상태를 쉽게 수정할 수 있게 해주는 임시 객체로, 원본 상태는 보호하면서도 수정할 수 있는 편리한 도구이다.

 

 배열이 객체의 깊은곳에 위치하지 않기 때문에 새 항목을 추가하거나 제거 할 때는 Immer 를 사용하는 것 보다 concat  filter 를 사용하는것이 더 코드가 짧고 편하다.


9. 클래스형 컴포넌트

import React, { Component } from 'react';

class Hello extends Component {
  render() {
    const { color, name, isSpecial } = this.props;
    return (
      <div style={{ color }}>
        {isSpecial && <b>*</b>}
        안녕하세요 {name}
      </div>
    );
  }
}

Hello.defaultProps = {
  name: '이름없음'
};

export default Hello;

클래스형 컴포넌트는 위와 같은 형식으로 사용하면 된다. 

 

클래스형 컴포넌트에서는 render() 메서드가 꼭 있어야 한다.

클래스형 컴포넌트에서 상태를 관리할 때에는 state를 것을 사용한다. 클래스형 컴포넌트의 state 는 무조건 객체형태여야 한다.

constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

 

- 커스텀 메서드

const onIncrease = () => {
  dispatch({ type: 'INCREMENT' });
};

 

위와 같은 커스텀 메서드를 클래스형 컴포넌트로 바꾼 것은 다음과 같다.

class Counter extends Component {
  handleIncrease() {
    console.log('increase');
  }

  handleDecrease() {
    console.log('decrease');
  }

  render() {
    return (
      <div>
        <h1>0</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

10. LifeCycle Method

LifeCycle Method 는 한국어로 "생명주기 메서드" 라고 부른다.

 

1) 마운트

  • constructor : 컴포넌트의 생성자 메서드로, 컴포넌트가 만들어지면 가장 먼저 실행된다.
  • getDerivedStateFromProps : props 로 받아온 것을 state 에 넣어주고 싶을 때 사용한다. 다른 생명주기 메서드와는 달리 앞에 static 을 필요로 한다. 
  • render : 컴포넌트를 렌더링하는 메서드
  • componentDidMount : 컴포넌트의 첫번째 렌더링이 마치고 나면 호출되는 메서드

2) 업데이트

  • getDerivedStateFromProps : 컴포넌트의 props 나 state 가 바뀔 때 호출된다. 
  • shouldComponentUpdate : 컴포넌트를 리렌더링 할지 말지를 결정한다. ( React.memo와 비슷한 역할을 수행한다. )
  • render 
  • getSnapshotBeforeUpdate : 컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면 그 다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용을 할 수 있다.
  • componentDidUpdate : 리렌더링을 마치고, 화면에 우리가 원하는 변화가 모두 반영되고 난 뒤 호출되는 메서드

3) 언마운트

  • componentWillUnmount : 컴포넌트가 화면에서 사라지기 직전에 호출된다.

출처 : http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/


11. componentDidCatch 로 에러 잡아내기 / Sentry 연동

componentDidCatch는 생명주기 메서드 중 하나이다. 이를 이용하여 리액트 애플리케이션에서 발생하는 에러를 처리할 수 있다.

사전에 예외처리를 하지 않은 에러가 발생 했을 때 사용자에게 에러가 발생했다고 알려주는 화면을 보여주자.

import React, { Component } from 'react';

class ErrorBoundary extends Component {
  state = {
    error: false
  };

  componentDidCatch(error, info) {
    console.log('에러가 발생했습니다.');
    console.log({
      error,
      info
    });
    this.setState({
      error: true
    });
  }

  render() {
    if (this.state.error) {
      return <h1>에러 발생!</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

 

componentDidCatch 메서드에는 두개의 파라미터를 사용하게 되는데 첫번째 파라미터는 에러의 내용, 두번째 파라미터에서는 에러가 발생한 위치를 알려준다.

 

하지만, componentDidCatch가 실제로 호출되는 일은 서비스에서 없어야 한다.

발견하지 못하여 예외 처리를 하지 못한 에러들은 componentDidCatch 에서 error  info 값을 네트워크를 통하여 다른 곳으로 전달을 해주면 되는데, 그 과정이 번거롭다.

괜찮은 솔루션으로, Sentry 라는 상용서비스가 있다.

Sentry에 회원가입하여 사용할 수 있고, Sentry를 프로젝트에 적용하는 방법이 제공되어 있다.


12. 리액트 개발할 때 사용하면 편리한 도구들

1) Prettier : 자동으로 코드의 스타일을 관리해준다.

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}

 

프로젝트의 루트 디렉터리에 위 파일(.prettierrc)을 만든다.

  • trailingComma : 객체나 배열이 여러줄로 구성되어 있을 때 맨 마지막 줄에 쉼표를 붙인다. ("none"이면 쉼표를 붙이지 않고, "es5"이면 객체, 배열에서 쉼표를 붙이고, "all"이면 함수를 사용할 때 인자를 전달할 때에도 쉼표를 붙인다. )
  • tabWidth : 들여쓰기 크기
  • semi : 세미콜론 사용 여부
  • singleQuote : 문자열 입력시 " 를 쓸지, '를 쓸지 결정

2) ESLint : 자바스크립트의 문법을 확인해준다.

3) Snippet : 자주 사용되는 코드에 대하여 단축어를 만들어서 코드를 빠르게 생성해낸다.


 

 

참고 : https://react.vlpt.us/

 

반응형