React : Context API, Immer, 클래스형 컴포넌트, 에러

김가영·2020년 12월 21일
0

Web

목록 보기
10/11
post-thumbnail

참고 - react.vlpt.us

Context API

프로젝트 안에서 전역적으로 사용 할 수 있는 값을 관리할 수 있다.

const UserDispatch = React.createContext();

Context 를 만들 땐 다음과 같이 React.createContext() 를 사용한다. 파라미터로는 Context 의 기본 값.
Context 가 생성되면 Context 내부에 Provider 라는 컴포넌트가 들어있는데, 이 컴포넌트를 통하여 Context 의 값을 정할 수 있다. 이 컴포넌트를 사용할 때, value 라는 값을 설정해주면 됨.

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

이렇게 설정해주면 Provider 에 의해 감싸진 컴포넌트 중 어디에서든지 Context 의 값을 바로 조회하여 사용 가능하다.

앞서 UserList 컴포넌트는 onToggleonRemove 를 전달하기 위한 중간다리 역할만 함. UserList 에서 해당 함수를 직접 사용하지도 않으므로 컴포넌트를 여러개 전달하는 과정이 번거로울 때 이러한 Context API 를 이용.

import './App.css';
import React, {useRef, useState, useMemo, useCallback, useReducer }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';
import use_Inputs from './hooks/useNewInputs';

function counterActiveUsers(users) {
  console.log('counting active users...');
  return users.filter(user => user.active).length;
}

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user)
      }
    case 'REMOVE_USER':
      return {
        ...state,
        users : state.users.filter(user => user.id != action.userId)
      }
    case 'TOGGLE_USER':
      return {
        ...state,
        users : state.users.map(user => user.id === action.userId? {...user, active : ! user.active } : user)
      }
    default:
      return state;
  }
}

const initialState = {
  inputs : {
    username: '',
    email: ''
  },
  users : [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active : true,      
  },
  {
      id: 2,
      username: 'tester',
      email: 'tester@gmail.com',
      active : false,
  },
  {
      id: 3,
      username: 'liza',
      email: 'liz@gmail.com',
      active : false,
  },
  ]
}

export const UserDispatch = React.createContext(null);

function App() {

  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } =state;
  const [{username, email}, onChange, reset] = use_Inputs({
    username : '',
    email: ''
  });

 ...
  
  return (
    <UserDispatch.Provider value ={dispatch}>
      <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
      < UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
  <div>number of Active users : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

이렇게 되면,

export const UserDispatch = React.createContext(null);

로 내보내준것을

import { UserDispatch } from './App';

로 불러올 수 있다.

  • UserList.js
import React, {useContext} from 'react'
import { UserDispatch } from './App';

const User = React.memo(function User({ user }) {
    const dispatch = useContext(UserDispatch);

    return (
        <div>
            <b
                style={{
                    cursor: 'pointer',
                    color: user.active? 'green' : 'black'
                }}
                onClick={()=> { dispatch({ type : 'TOGGLE_USER', userId: user.id})}}
            >
                {user.username}
            </b> 
            &nbsp;
            <span>({user.email})</span>
            <button onClick={() => { dispatch({ type : 'REMOVE_USER', userId : user.id})}}>삭제</button>
        </div>
    )
});

숙제

CreateUser 컴포넌트를 props을 전달하지 않고 구현하기

import React, { useContext, useRef } from 'react'
import { UserDispatch } from './App';
import useInputs from './hooks/useNewInputs'

function CreateUser() {
    const dispatch = useContext(UserDispatch);
    const [{username, email}, onChange, reset] = useInputs({ username : '', email : ''})

    const nextId = useRef(4)

    return (
        <div>
            <input
                name="username"
                placeholder='계정명'
                onChange={onChange}
                value={username}
            />
            <input
                name="email"
                placeholder='이메일'
                onChange={onChange}
                value={email}
            />
            <button onClick={() => { dispatch({
                type : 'CREATE_USER',
                user : {
                    id : nextId.current ++,
                    username,
                    email
                }
                })
                reset();
                }}>등록</button>
            
        </div>
    );
}

export default React.memo(CreateUser);

Immer

리액트에서는 배열이나 객체를 업데이트 할 때 직접 수정하는 대신 새로운 객체를 만들어준다.

시작하기

npm install immer
import produce from 'immer

produce 함수를 사용할 때에는 첫번째 파라미터에는 수정하고 싶은 상태 , 두번째 파라미터에는 어떻게 업데이트 할 지를 정의하는 함수를 넣는다.

produce 가 반환하는 것은 함수라는 것을 기억하자

import produce from 'immer';

const state = {
    number: 1,
    dontChangeMe: 2
};

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

이전의 reducer 를

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      })
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      })
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    default:
      return state;
  }
}

와 같이 바꿀 수도 있다.
객체의 깊은 곳에 원하는 값이 위치할 때 효과적

Immer 와 함수형 업데이트

const [todo, setTodo] = useState({
  text: 'Hello',
  done: false
});

const onClick = useCallback(() => {
  setTodo(todo => ({
    ...todo,
    done: !todo.done
  }));
}, []);

위처럼 함수형 업데이트를 할 때, Immer 를 사용하면 함수가 더 간결해 질 수도 있다.

import produce from 'immer';
import { useCallback } from 'react';

const [todo, setTodo] = useState({
    text: 'Hello',
    done: false,
});

const onClick = useCallback(() => {
    setTodo(
        produce(draft => {
            draft.done = !draft.done;
        })
    )
}, [])

단, 일반 js 코드보다 조금 더 느리고, 구형 브라우저 및 react-native 환경에서는 지원되지 않는다.

클래스형 컴포넌트

함수형

import React from 'react';

function Hello({ color, name, isSpecial }) {
  return (
    <div style={{ color }}>
      {isSpecial && <b>*</b>}
      안녕하세요 {name}
    </div>
  );
}

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

export default Hello;

  • 클래스형
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() 메서드가 꼭 있어야 한다. props 을 조회할 때에는 this.props 를 이용하면 된다.
defaultProps 는 함수형과 똑같이 해도되고, 클래스 내부에서 static 키워드와 함께 선언할 수도 있음


import React, { Component } from 'react'

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

export default Hello

귀찮음 나중에 할래

리엑트 앱에서 에러가 발생할 때

가장 일반적인 경우는 props 를 건네주지 않아서!

function User({ user, onToggle }) {
  if (!user) {
    return null;
  }
  ...
}

Users.defaultProps = {
  onToggle: () => {
    console.warn('onToggle is missing!');
  }
};

이런식으로 props 가 없을 때의 default 값을 설정해주자

componentDigCatch

생명주기를 이용하여 메서드를 이용하여 우리가 예외처리하지 못한 에러가 발생했을 때 사용자에게 알려주기

  • ErrorBoundary.js
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;
  • App.js
import React from 'react';
import User from './User';
import ErrorBoundary from './ErrorBoundary';

function App() {
  const user = {
    id: 1,
    username: 'velopert'
  };
  return (
    <ErrorBoundary>
      <User />
    </ErrorBoundary>
  );
}

export default App;
profile
개발블로그

1개의 댓글

comment-user-thumbnail
2021년 6월 12일

정보를 흡수해가겠습니다

답글 달기