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

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

이 값은 함수일 수도, 상태일 수도, 어떤 외부 라이브러리 인스턴스일수도, DOM 일 수도 있다.

새로운 context만드는 법

// App.jsx (데이터를 넣는 곳)
export const UserDispatch = React.createContext(null); // 초기값을 넣어 새로운 context를 생성

Context값을 정의하고 사용 범위 지정하기

  • 만들어진 context안에는 Provider라는 컴포넌트가 있다
  • value속성을 통해 context의 값(전달할 값)을 정의할 수 있다
  • Provider 에 의하여 감싸진 컴포넌트 중 어디서든지 우리가 Context의 값을 다른 곳에서 바로 조회해서 사용 할 수 있다.
// App.jsx (데이터를 넣는 곳)
<UserDispatch.Provider value={dispatch}> // value에 전달할 값을 넣어준다
	<CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
  <UserList users={users} />
  <div>활성사용자 수 : {count}</div>
</UserDispatch.Provider>

Context의 값 조회해서 사용하기

  • useContext Hook과 사용할 Context를 불러온다
  • useContext의 인수로 Context를 넣어 실행하면 value로 넘긴 값이 반환된다
  • 원하는 곳에 값을 사용한다
// UserList.jsx에서 User컴포넌트(데이터를 받는 곳)

import React, { useContext } from 'react'; // 훅과
import { UserDispatch } from './App'; // 만든 Context를 import

// 컴포넌트 안에서 정의
const dispatch = useContext(UserDispatch); // value로 전달한 값이 할당된다

onClick={() => {
          dispatch({ type: 'TOGGLE_USER', id: user.id });
        }}

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

리액트에서 배열이나 객체를 업데이트 해야 할 때에는 직접 수정 하면 안되고
불변성을 지켜주면서 업데이트를 해주어야 한다

// 객체 - 전개 연산자 사용
const object = {
  a: 1,
  b: 2
};

const nextObject = {
  ...object,
  b: 3
};

// 배열 - concat, filter, map사용
const todos = [
  {
    id: 1,
    text: '할 일 #1',
    done: true
  },
  {
    id: 2
    text: '할 일 #2',
    done: false
  }
];

const inserted = todos.concat({
  id: 3,
  text: '할 일 #3',
  done: false
});

const filtered = todos.filter(todo => todo.id !== 2);

const toggled = todos.map(
  todo => todo.id === 2
    ? {
      ...todo,
      done: !todo.done,
    }
    : todo
);

불변성을 지키는 코드는 코드의 구조가 좀 복잡해지면 코드를 봤을 때 한 눈에 들어오질 않게된다.

Immer 를 사용하면 우리가 상태를 업데이트 할 때, 불변성을 신경쓰지 않으면서 업데이트를 해주면 Immer 가 불변성 관리를 대신 해준다.

사용법

$ yarn add immer  // immer설치

import produce from 'immer'; //produce(함수)란 이름으로 불러온다

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

// 첫번째 파라미터 - 수정하고 싶은 상태
// 두번째 파라미터 - 어떻게 업데이트하고 싶을지 정의하는 함수
const nextState = produce(state, draft => {
  draft.number += 1; // 불변성에 대해서 신경쓰지 않고 그냥 업데이트 해주면된다
});

console.log(nextState);
// { number: 2, dontChangeMe: 2 }
// immer를 사용한다면 push splice등의 함수를 사용해도 불변성을 지킬 수 있다

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

*수정할 상태가 객체에 깊은 곳에 있지않고 간결하다면 immer보다 concat 과 filter 를 사용하는것이 더 코드가 짧고 편하다. = 상황에 따라 잘 선택하여 사용해야 함

Immer 와 함수형 업데이트

produce 함수에 두개의 파라미터를 넣게 된다면, 첫번째 파라미터에 넣은 상태의 불변성을 유지한 새로운 상태를 반환한다. 하지만

첫번째 파라미터를 생략하고 바로 업데이트 함수를 넣어주게 된다면, 반환 값은 새로운 상태가 아닌
상태를 업데이트 해주는 함수가 된다.

// 함수로 따로 빼고 후에 인자를 넣어서 실행하는 방식으로 사용 가능
const updater = produce(draft => {
  draft.done = !draft.done;
});

const nextTodo = updater(todo);

결국 produce 가 반환하는 것이 업데이트 함수가 되기 때문에 useState 의 업데이트 함수 빙식을 사용 할 떄 다음과 같이 구현할 수 있게 된다

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

const onClick = useCallback(() => {
  setTodo(
    produce(draft => {
      draft.done = !draft.done;
    })
  );
}, []);
  • 사용안한 코드가 성능이 조금 더 빠르다
  • 데이터의 구조가 복잡해져서 불변성을 유지하면서 업데이트하려면 코드가 복잡해지는 상황이 온다면, 이를 사용하는 것을 권장
  • 가능하면 데이터의 구조가 복잡해지게 되는 것을 방지하고 어쩔 수 없을 때 Immer 를 사용하는것이 좋다.
  • 필요한곳에만 쓰고, 간단히 처리 될 수 있는 곳에서는 그냥 일반 JavaScript 로 구현하는 것을 권장

24. 클래스형 컴포넌트

JSX렌더링

  • react객체에서 Component를 불러와야 한다
  • render() 메서드가 꼭 있어야 하고 이 안에서 JSX를 반환한다
  • 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;

기능 구현

기능을 구현할 땐 커스텀 메서드를 선언한다(클래스 내부에 종속된 함수를 "메서드" 라고한다)

화살표 함수로 구현해야 this binding이슈를 피할 수 있다

import React, { Component } from 'react';

class Counter extends Component {

  handleIncrease = () => {
    console.log('increase');
    console.log(this);
  };

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

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

export default Counter;

상태 선언, 사용

상태를 선언할 때에는 state 라는 것을 사용한다

  • constructor 내부에서 this.state 를 설정해준다
  • 클래스형 컴포넌트의 state 는 무조건 객체 형태여야 한다
  • render 메서드에서 state 를 조회하려면 this.state 를 조회하면 된다
  • 상태 업데이트 시 this.setState 함수 사용 - 상태 값이 참조형일 경우 불변성을 유지하며 업데이트해야함
import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

  // class-properties 문법이 적용되어 있는 경우(CRA)
  state = {
    counter: 0
  };   

  handleIncrease = () => {
    this.setState({
      counter: this.state.counter + 1
    });
  };

  handleDecrease = () => {
    this.setState({
      counter: this.state.counter - 1
    });
  };

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

export default Counter;

*함수형 업데이트를 하면 상태 변화가 바로바로 적용된다

보통 한 함수에서 setState 를 여러번에 걸쳐서 해야 되는 경우에 사용하면 유용하다

// 연속 사용 시 업데이트 바로 안됨
handleIncrease = () => {
  this.setState({
    counter: this.state.counter + 1
  });
  this.setState({
    counter: this.state.counter + 1
  });
}; 

// 업데이트 바로바로 됨
handleIncrease = () => {
    this.setState(state => ({
      counter: state.counter + 1
    }));
    this.setState(state => ({
      counter: state.counter + 1
    }));
  };

25. LifeCycle Method

  • 생명주기 메서드는 컴포넌트가 브라우저상에 나타나고, 업데이트되고, 사라지게 될 때 호출되는 메서드들
  • 클래스형 컴포넌트에서만 사용 할 수 있다.

마운트

  • constructor
  • 컴포넌트가 만들어지면 가장 먼저 실행되는 메서드
  • getDerivedStateFromProps
  • props 로 받아온 것을 state 에 넣어주고 싶을 때 사용
  • 컴포넌트가 처음 렌더링 되기 전에도 호출 되고, 그 이후 리렌더링 되기 전에도 매번 실행
  • render
  • 컴포넌트를 렌더링하는 메서드
  • componentDidMount
  • 컴포넌트의 첫번째 렌더링이 마치고 나면 호출되는 메서드, 컴포넌트가 화면에 나타난 상태

업데이트

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • 컴포넌트가 리렌더링 할지 말지를 결정하는 메서드
  • render
  • getSnapshotBeforeUpdate
  • DOM업데이트가 일어나기 직전에 발생
  • componentDidUpdate
  • 리렌더링이 마치고, 화면에 우리가 원하는 변화가 모두 반영되고 난 뒤 호출되는 메서드

언마운트

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

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

생명주기 메서드 중 하나로 리액트 애플리케이션에서 발생하는 에러를 처리하는 메서드

  • props이 없을 때의 해결법
    
    // early return 해주기
    // 데이터가 없으면 null 을 보여주거나,
    // <div>로딩중</div>과 같은 결과물을 렌더링하면 된다
    
    if (!user) {
        return null;
      }
    // 디폴트 값을 설정해주기
    
    Users.defaultProps = {
      onToggle: () => {
        console.warn('onToggle is missing!');
      }
    };

componentDidCatch 로 에러 잡아내기

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

ErrorBoundary 컴포넌트 만들기

첫번째 파라미터는 에러의 내용, 두번째 파라미터에서는 에러가 발생한 위치를 알려준다

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 // 현재 컴포넌트 상태 error 를 true로 설정
    });
  }

  render() {
    if (this.state.error) {
      return <h1>에러 발생!</h1>; // error 값이 true 라면 에러가 발생했다는 문구를 렌더링
    }
    return this.props.children; // false라면 children을 렌더링하도록 처리
  }
}

export default ErrorBoundary;

렌더링하려면 컴포넌트에 감싸서 사용

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;

2-3. styled-components

  • 미리 style을 입혀놓은 컴포넌트를 만들어 쓰는 것
  • css사용 시 컴포넌트가 많고 복잡해질 경우 클래스명 중복 등의 문제가 발생하기 때문에
  • 컴포넌에 종속되게 사용할 수 있게 만든 것
  • 클래스로 작동하긴하는데 우리가 클래스명을 작명할 필요가 없다

${}안에서 화살표 함수를 사용해 props를 인자로 받고 원하는 값을 return 해줄 수 있다

import React from 'react';
import styled from 'styled-components';

const Circle = styled.div`
  width: 5rem;
  height: 5rem;
  background: ${props => props.color || 'black'};
  border-radius: 50%;
`;

function App() {
  return <Circle color="blue" />;
}

export default App;

속성 값이 아닌, 여러줄의 코드를 조건부로 반환하고 싶다면 css를 불러와 사용할 수 있다

import React from 'react';
import styled, { css } from 'styled-components';

const Circle = styled.div`
  width: 5rem;
  height: 5rem;
  background: ${props => props.color || 'black'};
  border-radius: 50%;
  ${props =>
    props.huge &&
    css`
      width: 10rem;
      height: 10rem;
    `}
`;

function App() {
  return <Circle color="red" huge />;
}

export default App;

Sass유틸 함수 사용하기

$ yarn add polished

import { darken, lighten } from 'polished';

&:hover {
    background: ${lighten(0.1, '#228be6')};
  }

ThemeProvider사용하기

ThemeProvider 라는 기능을 사용하여 styled-components 로 만드는 모든 컴포넌트에서 조회하여
사용 할 수 있는 전역적인 값을 설정할 수 있다

App.js - 값 넘겨주기

import React from 'react';
import styled, { ThemeProvider } from 'styled-components'; // ThemeProvider 받아오기
import Button from './components/Button';

const AppBlock = styled.div`
  width: 512px;
  margin: 0 auto;
  margin-top: 4rem;
  border: 1px solid black;
  padding: 1rem;
`;

function App() {
  return (
    <ThemeProvider // 컴포넌트 가장 바깥에 감싸준다
      theme={{     //theme속성에 원하는 값을 객체로 넘겨준다
        palette: {
          blue: '#228be6',
          gray: '#495057',
          pink: '#f06595'
        }
      }}
    >
      <AppBlock>
        <Button>BUTTON</Button>
      </AppBlock>
    </ThemeProvider>
  );
}

export default App;

Buttin.js - 값 props으로 받아서 사용하기

/* 색상 */
  ${props => {
    const selected = props.theme.palette.blue;
    return css`
      background: ${selected};
      &:hover {
        background: ${lighten(0.1, selected)};
      }
      &:active {
        background: ${darken(0.1, selected)};
      }
    `;
  }}

Button 컴포넌트가 color props 를 를 통하여 받아오게 될 색상을 사용하도록 할 시

/* 색상 */
  `${({ theme, color }) => { // props객체를 구조분해할당
    const selected = theme.palette[color];
    return css`
      background: ${selected};
      &:hover {
        background: ${lighten(0.1, selected)};
      }
      &:active {
        background: ${darken(0.1, selected)};
      }
    `;
  }}`

function Button({ children, color, ...rest }) {
  return <StyledButton color={color} {...rest}>{children}</StyledButton>;
}

Button.defaultProps = {
  color: 'blue'
};

코드 분리하여 사용하기

유지보수를 할 때 더 편해질 수 있다

import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';

const colorStyles = css`
  ${({ theme, color }) => {
    const selected = theme.palette[color];
    return css`
      background: ${selected};
      &:hover {
        background: ${lighten(0.1, selected)};
      }
      &:active {
        background: ${darken(0.1, selected)};
      }
    `;
  }}
`;

const sizeStyles = css`
  ${props =>
    props.size === 'large' &&
    css`
      height: 3rem;
      font-size: 1.25rem;
    `}

  ${props =>
    props.size === 'medium' &&
    css`
      height: 2.25rem;
      font-size: 1rem;
    `}

    ${props =>
      props.size === 'small' &&
      css`
        height: 1.75rem;
        font-size: 0.875rem;
      `}
`;

const StyledButton = styled.button`
  /* 공통 스타일 */
  display: inline-flex;
  outline: none;
  border: none;
  border-radius: 4px;
  color: white;
  font-weight: bold;
  cursor: pointer;
  padding-left: 1rem;
  padding-right: 1rem;

  /* 크기 */
  ${sizeStyles}

  /* 색상 */
  ${colorStyles}

  /* 기타 */
  & + & {
    margin-left: 1rem;
  }
`;

function Button({ children, color, size,  ...rest }) {
  return (
    <StyledButton color={color} size={size} {...rest}>
      {children}
    </StyledButton>
  );
}

Button.defaultProps = {
  color: 'blue'
};

export default Button;

기존 styledCompontent에 style덮어씌우기

const ShortMarginButton = styled(Button)`
  & + & {
    margin-left: 0.5rem;
  }
`;
  • 댓글 & + & selector를 styled-components에서 사용할 경우, 이유는 모르겠지만 브라우저를 열어보시면 모든 버튼에 동일하게 부여된 classname에 margin값이 적용됩니다. 그래서 최초 의도한 & + &의 의미인 바로 옆의 버튼에만 마진값을 적용하라는 의미는 퇴색되고 모든 버튼에 동일한 마진값이 적용되는 것이죠. 저는 & + & 대신 &:not(:first-child) selector를 사용함으로써 해결했습니다. 참고하세요!
    출처 https://react.vlpt.us/
profile
#프론트엔드

0개의 댓글

Powered by GraphCDN, the GraphQL CDN