📌 Hooks
리액트 Hook은 클래스로 컴포넌트를 만들 때 발생하는 문제점을 해결하기 위해 등장하였다.
- 리액트 Hook을 사용하면 함수 컴포넌트 중심으로 개발이 가능하다.
- 함수 컴포넌트에서도 상태, 로직을 추출하여 다른 컴포넌트에서
재사용 할 수 있다.
리액트 Hook은 클래스로 컴포넌트를 만들 때 발생하는 문제점을 해결하기 위해 등장하였다.
1 | import styled from "styled-components"; |
Link 컴포넌트 안에다가 styled-component를 생성하고 as props를 전달해주었다.
위와 같이 할 경우, as props에 img 태그가 들어오게 된다면
이를 해결하기 위해 조건부 렌더링을 해주었다.
1 | import styled from "styled-components"; |
문제의 원인은 npm 명령어로 webpack server만 실행시켰는데, webpack config.js
파일에 static
속성값을 [build]
로 주었기 때문에 정적 이미지 파일을 불러오기 위해서는 build
폴더에서 assets
을 찾는다.
그러므로 npm run build
명령어로 build 폴더를 생성한 후 그 안에다가 img를 넣어주고 경로를 설정해주어 해결하였다.
1 | <Link |
aria-label
속성 있을 때, 조건부로 css 추가 이 속성을 삭제한 이유는 span 태그를 사용하여 Link 컴포넌트를 구현하였을 때, role="link"
로 해주었기 때문에 aria-label
속성을 굳이 해주지 않아도 되므로 삭제하였다.
리액트 컴포넌트에 전달되는 Prop(속성)의 Type(타입)을 검사하는 방법에 대해 알아보자.
1 | EmotionCard.propTypes = { |
위와 같이 직접 custom propTypes를 통해 prop를 검사할 수 있지만 리액트 팀에서 제공하는 propTypes 패키지를 사용하면 편리하고 안정적으로 prop 검사를 할 수 있다.
1 | npm i -D prop-types |
1 | import PropTypes from "prop-types"; |
1 | import { objectOf, number } from "prop-types"; |
propTypes.objectOf
는 객체의 속성 값이 모두 동일한 타입을 설명할 경우 사용한다.1 | import { shape, arrayOf, string } from "prop-types"; |
propTypes.shape
는 객체의 각 속성별 타입을 설명할 때 사용한다.arrayOf
를 사용하였다.객체의 속성이 정확히 동일하게 일치해야한다면
propTypes.exact() 를 사용한다.
propTypes는 null 타입 체크를 할 수 없어 oneOf를 사용한다.
1 | import { oneOf, oneOfType, shape, string } from "prop-types"; |
PropTypes.oneOf([’grow’, ‘learn’, ‘connect’])
이 중 하나만 파라미터로 들어올 수 있다.함수에 기본값을 주듯이 props에 기본값을 주는 것을
1 | import React from 'react'; |
애플리케이션의 비동기 처리는 빈번하게 발생하므로 비동기 요청의 응답을 기다리는 동안 사용자에게 로딩을 표시해줘야한다. 재사용이 가능한 컴포넌트로 로딩 컴포넌트를 만들어보자.
접근성을 고려하였을 때,
1 | // public/index.html |
aria-live="assertive"
속성을 주어 다른 것보다 우선적으로 스크린 리더가 읽도록 설정해준다.1 | // Spinner.js |
role="alert"
속성을 주어 스크린 리더가 읽고 있는 것을 중지하고 로딩중을 읽도록 설정하였다.포털을 사용하면 애플리케이션 영역을 벗어나 특정 위치에 컴포넌트를 렌더링할 수 있다. 즉, 지금껏 root 컨테이너에만 렌더링을 해왔다면 포털을 사용하여
포털을 통해 렌더링된 컴포넌트는 DOM 트리 위치와 상관없이 React 컴포넌트 트리에 포함되기 때문이다.
1 | ReactDOM.createPortal(child, container); |
1 | render() { |
시각적으로 자식을 튀어나오도록 보여야하는 다이얼로그, 호버카드, 툴팁에 사용된다. 이 때,
키보드 포커스 관리와 접근성을 고려해줘야한다.
aria-modal=true
이여야한다.aria-label
, aria-labelledby
로 설정한다.1 | export class DemoDialog extends React.Component { |
root 요소가 아닌 곳에 자식 컴포넌트를 그려주기 위해서 public 폴더의 index.html에 다음과 같이 div 요소를 추가해줘야한다.
1 | // public/index.html |
1 | // Dialog.jsx |
createPortal()
에 렌더링할 자식 컴포넌트와 자식 컴포넌트를 렌더링할 컨테이너를 전달하였다.Dialog.footer
자식 요소에게 onClose를 props로 전달하였다.1 | export class Dialog extends React.Component { |
e.preventDefault()
로 기본동작을 막을 수 있다.htmlElement.style.overflowY = "hidden"
로 모달 컴포넌트가 띄워져있을 때, 다른 요소는 스크롤이 비활성화시켜주었다.어떤 컴포넌트들은 어떤 자식 요소가 들어올지 예상할 수 없는 경우가 있다. 이럴 경우 children prop을 사용하여 자식 요소를 출력에 그대로 전달하는 것이 좋다.
합성(composition)을 사용하여
1 | Dialog.Header = function DialogHeader({ children }) { |
리액트 앱은 대부분 리액트에 의해 컨트롤되지만 예외인 상황이 있다. 리액트는 virtualDOM을 가지고 동작하기 때문에 RealDOM 요소에 접근하거나 조작해야할 경우 이는 리액트가 할 수 없는 일이다.
이러한 일을
사이드 이펙트를 처리하기 위해서는 다음과 같은 React API를 사용하여야한다.
컴포넌트 생성 시점에 이벤트를 구독한 경우, 컴포넌트 제거 시점에 구독한 이벤트를 취소해야한다.
1 | export class TiltCard extends React.Component { |
라이프 사이클은 함수 컴포넌트와 달리 클래스 컴포넌트에서만 라이프 사이클이 제공된다. 컴포넌트 생성, 마운트, 업데이트, 언마운트 등 특정 시점에서 실행되는 콜백함수를 말한다.
라이프 사이클은 React의 성능을 최적화하고 React가 컨트롤할 수 없는
대표적인 사이드 이펙트는 다음과 같다.
constructor()
render()
componentDidMount()
componentDidUpdate()
componentWillunmount()
commit 단계는 RealDOM이므로 명령형이 가능하다.
setState()
메서드는 컴포넌트의 state 객체에 대한 업데이트를 실행합니다. state가 변경되면, 컴포넌트는 리렌더링됩니다.
1 | setState(updater[, callback]) |
updater에 컴포넌트의 state 변경사항을 인자로 넘겨주고 React에게 해당 컴포넌트를 재렌더링해야한다고 요청을 보내는 메서드이다.
setState()
호출은 비동기적으로 이뤄진다.setState()
호출하자마자 this.state
에 접근하는 것은 문제가 될 수 있다.componentDidUpdate()
또는 setState(updater, callback)
처럼 2번째 인자로 콜백함수를 전달하는 방법을 사용한다.1 | this.state = { |
this.state.message
의 값이 갱신되지 않았다.setState()
첫번째 인수로 콜백함수 올 때1 | export class BinaryCalcurator extends Component { |
과거의 리액트는 상태를 가지는 컴포넌트는 클래스형으로, 상태를 가지지 않는 컴포넌트는 함수형으로 작성하였다. 왜 그랬을까?
Container 컴포넌트는 상태를 가지는데, 함수형 컴포넌트로 생성하게 되면
render()
메서드가 꼭 있어야 한다.stateless 컴포넌트는 말 그대로 상태를 가지지 않는 컴포넌트이다. 이러한 컴포넌트를 stateless 컴포넌트 또는 presentational(프레젠테이셔널) 컴포넌트라고 한다.
표현을 목적으로 하는 컴포넌트는 복잡한 비즈니스 로직이 필요없고 이를 클래스로 작성할 경우 Babel 컴파일러가 클래스를 컴파일 해줄 때 함수형 컴포넌트일 때 보다 훨씬 더 많은 코드로 컴파일 해주기 때문에 stateless 컴포넌트를 클래스형으로 작성하는 것은 낭비이다.
상태를 가지는 컴포넌트를 stateful 컴포넌트 또는 Container(컨테이너) 컴포넌트라고 한다.
하지만, React Hook의 등장으로 함수형 컴포넌트로도 Container 컴포넌트의 역할이 가능해졌다.
클래스형 컴포넌트와 함수형 컴포넌트에 차이에 대해 알게되었다.
프로젝트를 시작하게 될 때, 컴포넌트를 생성할텐데 이 때, 이 컴포넌트가 상태를 가지는 컴포넌트인지 상태를 갖지 않는 컴포넌트인지 구분을 짓고 컴포넌트를 생성해야겠다.
오늘날 리액트에서 React Hook이 등장하게 되면서 클래스형으로만 작성되던 stateful Component가 어떻게 함수형 컴포넌트로 바뀌게 되었는지를 배우게 되면 리액트에 대한 이해가 한층 더 깊어질 것 같다.
테스트를 위한 함수로, 원하는 함수의 로직을 테스트 하려면 작성해야할 코드가 많지만 테스트 함수를 사용하여 원하는 테스트를 시험할 코드만 간략하게 작성하여 테스트해볼 수 있어 편리하다.
1 | test("`onToggle` 속성(prop)에 연결된 함수가 정상적으로 실행됩니다.", () => { |
테스트 목적은 버튼 클릭이 제대로 인식되는지, 선택된 요소가 예상된 값으로 할당이 되는지를 확인한다.
이렇게 테스트 코드를 작성하는 것은 번거로운 일이다.
그러므로 간략하게 테스트 함수를 생성하여 이벤트가 발생할 때, 모의 함수가 호출되면 앞서 말한 로직을 구현해줄 수 있으므로 버튼이 클릭되었을 때, 모의 함수 호출이 제대로 되는지만 확인하면 된다.
1 | // mock Fn |
리액트에서는 VitualDOM에 렌더링을 하여 RealDOM과 비교하기 때문에 Test시에도 RealDOM이 아닌 VirtualDOM에 렌더링된 것으로 테스트를 해줘야한다.
1 | test("컴포넌트는 기본적으로 <span> 요소로 렌더링 됩니다.", () => { |
querySelector()
로 요소를 가져오는 것처럼 VirtualDOM에서는 screen
으로 요소를 가져올 수 있다.data-testId
값을 사용하였다.리액트 컴포넌트에서는 props에 default 값을 주기 위해서 defaultProps를 사용한다.
직접 파라미터에
App({ as: ComponentName = 'div' })
이런식으로 주는 것을 안티패턴으로 본다.
1 | import styles from "./A11yHidden.module.css"; |
component는 PascalCase 명명 규칙을 따르기 때문에 이를 따르지 않으면 에러를 발생시킨다.
기본적으로 div, span
같은 태그는 Role(역할)이 없다. 테스트 케이스에서 이러한 요소를 가져오기 위해서는 Role이 없으니 다른 방법을 찾아야한다.
그에 대한 대안으로 테스트용 data-testId="tester"
값을 주고 getByTestId()
를 사용하여 요소를 가져올 수 있다.
1 | // div 요소 가져올 경우 |
figure, img
등 우리가 직접 Role을 선정해주지 않아도 브라우저에서 해당 태그들을 해석할 때 Role을 할당해주기 때문에 이러한 요소들은getByRole()
를 이용하여 가져올 수 있다.
마침 우테코에서도 Jest를 사용하고 있었는데, 수업시간 때 리액트 DOM 테스트를 해볼 수 있는 기회가 있어서 유익했다. 프론트 엔드에서는 사용자의 행동에 의해 UI가 변경되는 요소들이 많기 때문에 이러한 사용자 액션에 의한 test를 하기 어렵다.
이럴 경우 사용자가 행동하는 것처럼 테스트를 진행하는 storyBook이라는 testing Tool도 있으니 Jest가 익숙해지면 storyBook도 사용해봐야겠다.
author.bio
author.job