React 문서리뷰 정리
React version v17.0.2 (https://ko.reactjs.org/docs/getting-started.html)
- 목차
- 주요개념
- JSX
- 엘리먼트 렌더링
- Components and Props
- 주요개념
주요개념
JSX
- 자바스크립트를 확장한 문법
- 기존 마크업처럼 UI표현을 할수 있으며 로직과UI를 자연스럽게 연결하기위한 문법
- Babel에서 해준다.
- JSX 중괄호 안에 유효한 모든 자바스크립트 표현식을 넣을 수 있다.
- JSX는 컴파일이 끝나면 자바스크립트 객체로 인식되고 호출된다.
- 따움표로 문자열 리터럴 표현, 중괄호로 javascript 표현식 삽입
const element = <div tabIndex="0"></div>; const element = <img src={user.avatarUrl}></img>; - 기본적으로 문자열을 escape하여 xss 방지
엘리먼트 렌더링
- 엘리먼트는 리액트앱의 가장 작은 단위이다.
리얼DOM에 엘리먼트 렌더링하기
- 하나의 어플리케이션은 하나의 root를 가지고 있고 reactDOM.render()를 통해 전달한다.
렌더링된 엘리먼트 업데이트하기
- 엘리먼트는 불변객체 이므로 엘리먼트를 다시 그리는 방법은 새로운 엘리먼트를 만들어서 render함수로 전달하는 것 뿐이다.
변경된 부분만 업데이트하기
- 엘리먼트 전체를 다시 render함수로 전달했다고해도 실제로 realdom의 반영은 리얼돔과 가상돔의 diff를 통해 변경된 부분에만 적용된다.
Components and Props
- UI를 개별적으 재사용 가능한 여러 조각으로 나누고 재사용 할 수 있다.
- props 라는 임의의에 값을 입력 받은 후 React엘리먼트를 반환한다.
함수 컴포넌트와 클래스 컴포넌트
- 컴포넌트는 함수로 선언하거나 클래스로 선언할 수 있다. 함수형 클래스형 각각 몇가지 추가기능이 있다.
컴포넌트 렌더링
- DOM태그로 매칭되는 엘리먼트 이외에도 사용자 정의 컴포넌트를 나타낼 수 있다.
- 사용자 정의 컴포넌트에는 어트리뷰트를 통해 값을 전달할수 있고 props객체를 통해 전달된다.
컴포넌트 합성
- 자신의 출력에 다른 컴포넌트를 참조하여 사용할 수 있다.
컴포넌트 추출
- 컴포넌트를 여러개의 작은 컴포넌트로 나누는걸 두려워 하지 말 것.
- 재활용과 가독성의 측면에서 추출하는 쪽이 도움이 됨.
읽기전용인 Props
- 리액트는 단방향 흐름의 컨셉으로 예측하기 쉬운 앱의 상태를 목표로 하기 때문에 Props는 직접 변경하면 안된다.
State와 생명주기
컴포넌트 생명주기 활용하기
- 컴포넌트가 생성되거나 삭제될때의 생명주기를 활용하여 구현할 수 있다.
- componentDidMount
- Component가 DOM에 렌더링 된후 실행된다.
- componentWillUnmount
- Component가 언마운트 될때 실행된다.
- componentDidMount
State를 올바르게 사용하기
- State를 직접 할당하여 수정하면 안된다(바뀐값으로 렌더링 하지 않는다).
- 대신 setState를 사용해야 한다.
- setState가 호출될때 성능상의 이유로 내부에서는 비동기로 실행될 수 있으므로 state값을 새로운 setState를 위해 활용해서는 안된다.
// Wrong this.setState({ counter: this.state.counter + this.props.increment, }); // Correct (함수를 인자로 사용하는 형태로 작성해야 함) this.setState((state, props) => ({ counter: state.counter + props.increment })); - state는 병합된다.
Data는 아래로 흐른다
- state는 자신을 정의한 컴포넌트 이외의 컴포넌트에서는 접근할 수 없다.
- 컴포넌트는 자신의 state를 자식으로 props를 통하여 전달할 수 있다.
<FormattedDate date={this.state.date} />
이벤트 처리하기
- React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용합니다.
- JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
- React의 이벤트는 합성 이벤트를 사용하기 때문에 브라우저 이벤트의 동작과 상이할 수 있다.
- JSX 콜백 안에서 this의 의미에 대해 주의해야 한다
- (bind 해주는 대신 클래스 필드 정의 방법을 사용하거나 화살표 함수로 대체할 수 있다).
class Toggle extends React.Component { constructor(props) { // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 한다. this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( <button ... } }
이벤트 핸들러에 인자 전달하기
- 아래 두가지 표현 다 옮음
<button => this.deleteRow(id, e)}>Delete Row</button> <button id)}>Delete Row</button>
조건부 렌더링 가능
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
엘리먼트 변수
- 엘리먼트를 변수의 형태로 저장하여 사용할 수 있다.
if (isLoggedIn) { button = <LogoutButton />; } else { button = <LoginButton />; }
JSX 안에서 논리연산하기
- jsx안에서 중괄호를 이용해 표현식을 포함할 수 있으므로
// && 논리연산을 이용해 조건부 랜더링 return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); // 3항 조건식을 이용한 조건부 렌더링 render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? <LogoutButton /> : <LoginButton /> } </div> ); }
렌더링 막기
// null을 리턴하면 렌더링을 막는다.
if (!props.warn) {
return null;
}
리스트와 Key
여러개의 컴포넌트 랜더링하기
- 아래처럼 반복되는 컴포넌트에는 키값을 지정해야 한다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
key
- key는 리액트가 어떤 항목을 추가, 수정,삭제할지 판단하는데 도움을 주며, 지정하지 않으면 리스트의 index를 키로 사용한다.
- 리스트의 순서가 바뀌어 업데이트 되는 등의 상황에서 react가 잘못 해석하여 엉뚱하게 출력하거나, 성능에 문제가 있을수 있다.
- Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다.
- 형제 사이에서만 고유한 값이면 된다.
- key는 리액트 내부에서 유용하게 사용되지만 자식 컴포넌트로 값을 전달하지는 않는다.
- 항목의 순서가 바뀔수 있는 리스트의 경우 index를 키로 지정하는것은 성능상 좋지 않다.
폼
- 보통 HTML에서 사용하는것 처럼 사용해도 좋지만, 리액트의 "제어 컴포넌트"라는 기술을 사용하는것이 좋다.
제어 컴포넌트 (Controlled Component)
- React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트된다.
constructor(props) { this.state = {value: ''}; } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('An essay was submitted: ' + this.state.value); event.preventDefault(); } render() { return( <form <textarea value={this.state.value} /> </form> ) }
제어되는 Input Null 값
- 제어 컴포넌트에 value prop을 지정하면 의도하지 않는 한 사용자가 변경할 수 없다.
- null을 입력하면 제어 가능해짐
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
대안
- 매번 똑같은 패턴을 작성하는게 지겹다면 https://formik.org/ 과 같은 슈퍼셋 컴포넌트를 사용할 수 있다.
State 끌어올리기
-
종종 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있습니다. 이럴 때는 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋다.
-
다른 컴포넌트 사이에 같은 값이 필요하게 되면 그 값을 그들의 가장 가까운 공통 조상으로 끌어올리면 된다. 다른 컴포넌트 간에 존재하는 state를 동기화시키려고 노력하는 대신 하향식 데이터 흐름에 기대면 "보일러 플레이트"는 더 증가할 가능성이 높지만 버그를 격리하여 처리하기 쉽다.
합성과 상속
- React는 강력한 합성 모델을 가지고 있으며, 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋다.
컴포넌트에서 다른 컴포넌트를 담기
- 자식으로 어떤 컴포넌트가 올지 알수없는 경우 children을 사용할 수 있다.
- 예를들면 사이드바, 다이얼로그와 같은 박스형 컴포넌트의 경우
- case 1
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); } function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> </FancyBorder> ); } - case 2
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); } - case 3 (Higher Order 패턴)
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); } function WelcomeDialog() { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); }
상속
- React에서 권장할만한 컴포넌트 상속 패턴은 없다고 함.
React로 사고하며 만들기
- 1단계: UI를 컴포넌트 계층 구조로 나누기
- 2단계: React로 정적인 버전 만들기
- 프로젝트가 작다면 하향식으로, 크다면 상향식으로 만들면 좋다고 함.
- 3단계: UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기
- 4단계: State가 어디에 있어야 할 지 찾기
- 5단계: 역방향 데이터 흐름 추가하기
- 리액트의 단방향 흐름 구조를 이해하고 사용하자 (코드가 많아지지만 이해하기 쉬어진다고 함).
- props을 통해 핸들러를 넘기는 방식을 이해하면 됨
고급 안내서
접근성
시맨틱 HTML
- React Fragment 를 사용하면 div를 사용하는대신 의미있는 태그를 사용하여 구현할 수 있다.
function Glossary(props) { return ( <dl> {props.items.map(item => ( // 항목을 매핑할 때 Fragment는 반드시 `key` 프로퍼티가 있어야 합니다. <Fragment key={item.id}> <dt>{item.term}</dt> <dd>{item.description}</dd> </Fragment> ))} </dl> ); }
접근성 있는 폼
- input, textarea와 같은 태그는 라벨을 위한 속성을 사용할 수 있다.
- 다만 for 애트리뷰트는 JSX에서 htmlFor로 사용해야 한다.
<label htmlFor="namedInput">Name:</label> <input id="namedInput" type="text" name="name"/>
프로그래밍적으로 포커스 관리하기
- 모든 웹 애플리케이션은 키보드만 사용하여 모든 동작을 할 수 있어야 한다.
- 키보드 포커스는 포커싱된 요소의 윤각선을 표시하며, 프로그래밍 적으로 포커스를 관리할 수 있어야 한다.
- 아래처럼 React에서 포커스를 지정하면, DOM 엘리먼트에 ref를 사용할 수 있다.
class CustomTextInput extends React.Component { constructor(props) { super(props); // DOM 엘리먼트를 저장할 textInput이라는 ref을 생성한다. this.textInput = React.createRef(); } focus() { // DOM API를 사용해 텍스트 input에 정확히 포커스를 맞춘다. // 주의: ‘현재’의 DOM 노드에 접근하고 있다. this.textInput.current.focus(); } render() { // `ref` 콜백으로 텍스트 input DOM을 저장한다. // 인스턴스 필드의 엘리먼트 (예를 들어, this.textInput) return ( <input type="text" ref={this.textInput} /> ); } } - 고차 컴포넌트에서 ref를 사용할 경우 예시
function CustomTextInput(props) { return ( <div> <input ref={props.inputRef} /> </div> ); } class Parent extends React.Component { constructor(props) { super(props); this.inputElement = React.createRef(); } render() { return ( <CustomTextInput inputRef={this.inputElement} /> ); } } // 이제 필요할 때마다 포커스를 잡을 수 있다. this.inputElement.current.focus();
마우스와 포인터 이벤트
- 마우스 포인터와 관련된 기능을 구현할때는 항상 키보드로도 동작하도록 구현해야 하며, onFocus, onBlue 이벤트를 활용할 수 있다.
render() { // React는 블러와 포커스 이벤트를 부모에 버블링해준다. return ( <div <button aria-haspopup="true" aria-expanded={this.state.isOpen}> Select an option </button> {this.state.isOpen && ( <ul> <li>Option 1</li> <li>Option 2</li> <li>Option 3</li> </ul> )} </div> ); }
더욱 복잡한 위젯
- 복잡한 사용자 경험을 갖는 위젯을 만들때도 접근성이 떨어져서는 안된다.
- ARIA 역할과 ARIA 상태 및 프로퍼티에 대한 지식이 있다면.JSX에서 모두 지원되는 HTML 어트리뷰트로 이를 통해 완전히 접근성 있고 기능이 우수한 React 컴포넌트를 구성할 수 있다.
기타 고려사항
- 언어설정
- 문서 제목 설정
- 색 대비
개발 및 테스트 도구
- ESLint 플러그인인
eslint-plugin-jsx-a11y는 JSX 내의 접근성 문제에 대해 즉각적인 AST 린팅 피드백을 제공함. 에디터에서도 통합하여 사용 가능.
코드 분할
- 여러 모듈들을 하나로 묶어서 하나의 파일로 만들어주는 번들링은 매우 편리하지만, 앱이 커질수록 로딩 시간이 오래 걸릴 수 있다.
- 번들 파일을 여러 파일로 나누는 전략이 도움이 될 수 있다(일반적인 번들러들은 이 기능을 지원하고 있다)
- 코드의 양을 줄이지 않고도 사용자가 당장 필요하지 않는 코드는 불러오지 않게하여 사용자 경험을 향상시킬 수 있다.
동적 import 사용
- 동적 import는 코드분할의 좋은 방법이다.
import("./math").then(math => { console.log(math.add(16, 26)); });
react.lazy
- react.lazy는 리액트에서 컴포넌트를 불러올때 동적 import를 사용할수 있는 방법이다.
- (처음 랜더링될때 컴포넌트를 로드함).
const OtherComponent = React.lazy(() => import('./OtherComponent')); - fallback prop은 컴포넌트가 로드될 때까지 기다리는 동안 렌더링하려는 React 엘리먼트를 사용자에게 보여준다.
import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); const AnotherComponent = React.lazy(() => import('./AnotherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </div> ); }
Context
- context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
- context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.
const ThemeContext = React.createContext('light'); class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { static contextType = ThemeContext; render() { // 값을 바로 사용 가능 return <Button theme={this.context} />; } }
컨텍스트 사용 전에 고려해야 할것
- 컴포넌트 합성이 더 효과적인 방법일 수 있다.
API
1. React.createContext
- context 객체를 만듬
const MyContext = React.createContext(defaultValue);
2. Context.Provider
- 구독하는 컴포넌트들에게 값의 변화를 알리는 역할을 한다.
- Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이 경우 하위 Provider의 값이 우선.
- shouldComponentUpdate 메서드에 의해 하위로 전파되는것을 막지 않으므로 주의해야 한다.
- (상위 컴포넌트가 업데이트를 건너뛰더라도 하위 컨슈머는 업데이트가 되므로)
<MyContext.Provider value={/* 어떤 값 */}>
3. Class.contextType
- 이렇게 생성하면 모든 생명주기 메서드에서 사용 가능함.
class MyClass extends React.Component { componentDidMount() { let value = this.context; /* MyContext의 값을 이용한 코드 */ } componentDidUpdate() { let value = this.context; /* ... */ } componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context; /* ... */ } } MyClass.contextType = MyContext;
4. Context.Consumer
- 함수 컴포넌트 안에서 context를 구독할 수 있다.
<MyContext.Consumer> {value => /* context 값을 이용한 렌더링 */} </MyContext.Consumer>
5. Context.displayName
- 개발자 도구에서 표시할 컨텍스트 네임을 지정할 수 있다.
Error boundaries
- 에러가 발생할 경우 에러를 숨기기 보다는 표시해주는게 더 좋을 수 있다.
- try catch도 훌륭하지만 명령형 코드에서만 동작한다.
- 에러경계는 이벤트 핸들러에서는 동작하지 않으므로이벤트 핸들러에서 에러 포착을 위해서는 try catch를 사용해야 한다.
- React에서는 컴포넌트 동적 모듈 로드가 실패할경우 에러 화면을 구성하여 보여줄 수 있다.
// 사용 <ErrorBoundary> <MyWidget /> </ErrorBoundary> // 구현 class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다. return { hasError: true }; } componentDidCatch(error, errorInfo) { // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다. logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // 폴백 UI를 커스텀하여 렌더링할 수 있습니다. return <h1>Something went wrong.</h1>; } return this.props.children; } }
Refs 전달하기
forwardRef를 사용하면 ref를 직접 사용하는 것 처럼 전달할 수 있다.
- key와 마찬가지로 ref역시 리액트 내부에서는 특별하게 처리하므로 props를 통해 전달되지 않는다는 것을 명심해야 한다.
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 이제 DOM 버튼으로 ref를 작접 받을 수 있습니다.
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
- DevTools에서 표시할 ref 이름을 명시할 수도 있다.
function logProps(Component) { class LogProps extends React.Component { // ... } function forwardRef(props, ref) { return <LogProps {...props} forwardedRef={ref} />; } // DevTools에서 이 컴포넌트에 조금 더 유용한 표시 이름을 지정하세요. // 예, "ForwardRef(logProps(MyComponent))" const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`; return React.forwardRef(forwardRef); }
Fragments
- 컴포넌트가 여러 엘리먼트를 반환할때 별도의 DOM노드를 추가하지 않고 그룹화 하는 기능
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
- 단축문법
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
- key가 있는 Fragments
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// React는 `key`가 없으면 key warning을 발생합니다.
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
고차컴포넌트 (Higher Order Component)
- 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수.
횡단관심사에 고차 컴포넌트 사용
- 예전에는 Mixin을 권장했으나, Mixin패턴이 많은 문제를 일으킨다는게 발견됨
- 구현체가 동일한 비슷한 패턴의 컴포넌트가 있을 경우 동일 기능을 묶는 고차 컴포넌트를 만들어서 조합하는 방법을 권장함.
- 원본 컴포넌트를 수정하지 않으므로 커플링이 완화됨
- 컨테이너 컴포넌트의 일종으로 봐도 무관함
- 고차 컴포넌트가 전달받은 원본 컴포넌트의 기능을 변경해서는 안된다.
- 고차 컴포넌트는 원본 컴포넌트의 본래 관심사와 분리된 격리된 props를 만들어 활용해야한다.
render() { // 이 HOC에만 해당되므로 추가된 props는 걸러내어 이 HOC에 전달되지 않도록 합니다. const { extraProp, ...passThroughProps } = this.props; // 이 Props는 일반적으로 Status값 또는 Instance method 입니다. const injectedProp = someStateOrInstanceMethod; // wrapped component에 props를 전달합니다. return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); } - 고차 컴포넌트는 쉽게 조합하여 사용할수 있다.
- 고차 컴포넌트도 다른 컴포넌트와 마찬가지로 개발툴에 표시되므로
displayName속성을 지정해 놓고 개발시 활용할 수 있다.
주의사항
-
ref는 prop이 아닌 특별히 취급되는 속성이므로 주의할것
-
render 메서드 안에서 고차 컴포넌트를 사용하지 말것.
render() { // render가 호출될 때마다 새로운 버전의 EnhancedComponent가 생성됩니다. // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 때문에 매번 전체 서브트리가 마운트 해제 후 다시 마운트 됩니다! return <EnhancedComponent />; } -
정적 메서드는 따로 복사할것 (기존 컴포넌트가 새 컴포넌트에 감싸지며, 새 컴포넌트는 기존 컴포넌트의 정적 메서드를 잃어버리게 됨)
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 복사 할 메서드를 정확히 알아야 합니다. Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }- hoist-non-react-statics 함수를 사용하면 모든 정적 메서드를 복사할 수 있다.
import hoistNonReactStatic from 'hoist-non-react-statics'; function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} hoistNonReactStatic(Enhance, WrappedComponent); return Enhance; }
다른 라이브러리와 통합하기
- React는 다른 DOM라이브러리가 임의로 DOM을 변경하는걸 알지 못하며 같이 사용한다면 혼란스러운 상태가 될 것이다. (다른 라이브러리와 통합을 추천하지 않음)
- 충돌을 피하는 방법은 React관여할 필요없는 빈 div를 렌더링하는 것이다(프로퍼티나 자식을 가지지 않으면 React가 업데이트하지 않는다).
class SomePlugin extends React.Component { componentDidMount() { this.$el = $(this.el); this.$el.somePlugin(); } componentWillUnmount() { this.$el.somePlugin('destroy'); } render() { return <div ref={el => this.el = el} />; } } class Chosen extends React.Component { render() { return ( <div> // children을 자식으로 갖으면 React가 관여하기 때문에 빈 div아래로 select가 들어가도록 처리 <select className="Chosen-select" ref={el => this.el = el}> {this.props.children} </select> </div> ); } }
JSX 이해하기
- 근본적으로, JSX는 React.createElement(component, props, …children) 함수에 대한 문법적 설탕을 제공할 뿐이다.
JSX 타입을 위한 점 표기법 사용
- 하나의 모듈에서 복수개의 컴포넌트들을 export할 수 있으며, 점 표기법을 이용해 사용할 수 있다.
import React from 'react'; const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />; }
실행 중에 타입 선택하기
- React element 타입에 일반적인 표현식은 사용할 수 없다. 만약 element 타입을 지정할 때 일반적인 표현식을 사용하고자 한다면 대문자로 시작하는 변수에 배정한 후 사용해야 한다.
import React from 'react'; import { PhotoStory, VideoStory } from './stories'; const components = { photo: PhotoStory, video: VideoStory }; function Story(props) { // 잘못된 사용법 // return <components[props.storyType] story={props.story} />; // 옮은 사용법 const SpecificStory = components[props.storyType]; return <SpecificStory story={props.story} />; }
JSX 안에서 표현식 사용
- 하지만 if와 for문은 표현식이 아니므로 jsx내에서 사용할수 없지만, 주변코드에서 사용할 수 있다.
function NumberDescriber(props) { let description; if (props.number % 2 == 0) { description = <strong>even</strong>; } else { description = <i>odd</i>; } return <div>{props.number} is an {description} number</div>; }
Props의 기본값은 “True”
- prop에 어떤값도 넘겨주지 않을 경우 기본값을 true이다.
<MyTextBox autocomplete /> <MyTextBox autocomplete={true} />
속성펼치기
- 전개 연산자는 유용하지만 불필요한 prop을 컴포넌트에 넘기거나 유효하지 않은 HTML 속성들을 DOM에 넘기기도 합니다. 꼭 필요할 때만 사용하는 것을 권장한다.
- 아래의 두 표현은 동일하다.
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; }
React 컴포넌트는 element로 이루어진 배열을 반환할 수 있다.
render() {
// 리스트 아이템들을 추가적인 엘리먼트로 둘러쌀 필요 없습니다!
return [
// key 지정을 잊지 마세요 :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
JavaScript 표현식을 자식으로 사용하기
-
동일한 표현
<MyComponent>foo</MyComponent> <MyComponent>{'foo'}</MyComponent> -
JSX내에서 표현식을 넣을 수 있으므로 아래와 같이 유용하게 사용 가능하다.
function Item(props) { return <li>{props.message}</li>; } function TodoList() { const todos = ['finish doc', 'submit pr', 'nag dan to review']; return ( <ul> {todos.map((message) => <Item key={message} message={message} />)} </ul> ); }
함수를 자식으로 사용하기
- props.children은 어떠한 표현식으로든지 넘겨줄 수 있으며 따라서 아래처럼 함수로 넘겨주는 것도 가능하다.
// 자식 콜백인 numTimes를 호출하여 반복되는 컴포넌트를 생성합니다. function Repeat(props) { let items = []; for (let i = 0; i < props.numTimes; i++) { items.push(props.children(i)); } return <div>{items}</div>; } function ListOfTenThings() { return ( <Repeat numTimes={10}> {(index) => <div key={index}>This is item {index} in the list</div>} </Repeat> ); }
boolean, null, undefined는 무시된다
-
따라서 아래의 표현들은 다 동일한 표인이다
<div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div> -
true, false와 같은 boolean 값이 출력되지 않기 때문에 아래와 같이도 유용하게 사용할수 있다.
<div> {showHeader && <Header />} <Content /> </div> -
주의할 점은 0과 같은 경우 booelan값이 아니므로 0이 출력된다는 점이다(아래와 같은 경우 주의해야 한다)
<div> {props.messages.length && <MessageList messages={props.messages} /> } </div>
성능 최적화
- React는 별다른 최적화 없이도 충분히 빠른 서비스를 제공 가능하다. 하지만 그럼에도 더 개선할 수 있는 몇가지 방법이 있다.
프러덕션 빌드를 사용해라
- 크롬에 react 개발툴 플러그인이 깔려있다면 개발툴 아이콘의 색으로 개발모드와 배포모드 구분이 가능하다.
- 개발모드는 빨간색 프로덕션 모드는 검은색
- 프러덕션 모드는 개발에 관련된 잡 코드나 경고가 포함되지 않으므로 더 빠를 수 있다.
- Create React App을 사용중이라면
npm run build를 통해 프러덕션용 파일을 만들 수 있다. - 빌드툴에 terserPlugin을 적용하면 코드를 축소해주므로 빨라질 수 있다.
DevTools Profiler로 컴포넌트 프로파일링
- react-dom 16.5 부터, react-native 0.57부터 개발툴의 프로파일링 도구를 이용해 성능을 개선할 수 있다.
긴 목록 가상화 컴포넌트를 사용하라
- react-window 또는 react-virtualized같은 라이브러리를 사용하면 성능을 개선하는데 유용할 수 있다.
리포지션을 피해라
- 이미 렌더링된 dom과 가상돔을 비교하는 작업이 오래걸릴 수 있으므로, shouldComponentUpdate 메서드로 리랜더링이 필요없는 상황을 이미 알고 있을때 비교 작업을 생략할 수 있다.
- 대부분의 경우 shouldComponentUpdate 메서드를 직접 구현하는 대신 React.PureComponent에서 상속받아 구현을 생략할 수 있다.
- React.PureComponent의 shouldComponentUpdate 구현은 얕은 참조 비교만 하므로 구조가 복잡한 prop이나 state의 경우 정상동작하지 않을 수 있으므로 주의해야 한다.
Portal
- Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공한다.
- DOM의 다른 계층구조에 children을 출력하고 싶을 경우
// React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링합니다. // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없습니다. return ReactDOM.createPortal( this.props.children, domNode ); }
- DOM의 다른 계층구조에 children을 출력하고 싶을 경우
Portal을 통한 이벤트 버블링
- portal이 DOM 트리의 어디에도 존재할 수 있다 하더라도 모든 다른 면에서 일반적인 React 자식처럼 동작한다.
- DOM 트리에서의 위치에 상관없이 portal은 여전히 React 트리에 존재하기 때문이다.
Profiler API
- Profile 컴포넌트로 특정 컴포는트 트리 구간을 감싸서 사용하며, 콜백을 통해서 해당 구간의 성능지표 값을 확인할 수 있다.
재조정 (Reconciliation)
리액트트리 비교 알고리즘
루트 엘리먼트의 타입이 같은경우
- 루트엘리먼트의 타입이 틀리면 하위 엘리먼트들은 완전 새로이 구축된다.
루트 엘리먼트의 타입이 같은경우
- 변경된 속성만 변경함
Ref와 DOM
- 왠만한 상황에서는 React의 라이프 사이클 과 Prop을 이용해서 구현해야 하며 특별한 경우에만 Ref를 사용하는 것이 좋다.
Ref를 사용해야 할 때
- 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때.
- 애니메이션을 직접적으로 실행시킬 때.
- 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.
Ref 생성하기
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Ref 접근하기
- render 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 어트리뷰트에 담기게 된다
- (함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 어트리뷰트를 사용할 수 없다.)
const node = this.myRef.current;
함수형 컴포넌트에 ref 어트리뷰트 사용하기
- 함수형 컴포넌트는 인스턴스가 없다는걸 인지하고 유의하여 사용하면 된다
- forwardRef 를 이용하여 함수 컴포넌트를 만들면 클래스 컴포넌트처럼 ref를 통과시켜 사용할 수 있다.
부모 컴포넌트에게 DOM ref를 공개하기
- 사용할 수 있고 특정 상황에서 유용할수 있지만, 자식컴포넌트가 함수컴포넌트일때는 동작하지 않으므로 주의해야 한다.
- (함수 컴포넌트는 인스턴스가 없기 때문에)
콜백 ref
- React는 ref가 설정되고 해제되는 상황을 세세하게 다룰 수 있는 “콜백 ref” 기능을제공
Render Props
- 컴포넌트간에 데이터를 공유하기위한 테크닉
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }}
{/*
<Mouse>가 무엇을 렌더링하는지에 대해 명확히 코드로 표기하는 대신,
`render` prop을 사용하여 무엇을 렌더링할지 동적으로 결정할 수 있습니다.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
정적타입검사
프로젝트에 타입스크립트 추가하기
- 설치
npm install --save-dev typescript
- 컨피그 생성
npx tsc --init
HOOK
Hook 소개
-
React 16.8부터 기능 추가
-
클래스 컴포넌트를 사용할 필요 없이 함수컴포넌트로 상태등 여러 기능을 사용할수 있게 됨
-
믹스인의 대용으로서의 기능도 함
-
기존 클래스 컴포넌트의 장황함을 해소
-
함수형 컴포넌트에서도 생명주기 기능을 사용할 수 있게 해준다.
-
훅은 클래스 컴포넌트에서는 동작하지 않는다
useState
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
- componentDidMount 나 componentDidUpdate, componentWillUnmount 의 기능을 대체
- 매번 랜더링 이후에 effect실행
- 최상위 컨텍스트에서만 실행되어야 함(반복문, 제어문 중첩된 함수 내에서 사용하면 안됨)
- 함수 본체에서componentDidMount 나 componentDidUpdate를 구현하고 리턴되는 메스드에서는 componentWillUnmount를 실행
- 관심사 분리를 위해 여러 여러 이팩트 메서드를 사용할수도 있다.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // componentDidMount 나 componentDidUpdate
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // componentWillUnmount
};
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.
useContect
- 메모이제이션을 같이 사용해야만 최적화 될수 있음
useContext(MyContext)- 클래스에서의 static contextType = MyContext 또는 <MyContext.Consumer>와 같다고 보면 된다.
useRef
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button the input</button>
</>
);
}
useImperativeHandle
- 부모컴포넌트에서 참조할 메서드를 구현할때 사용
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
useMemo
- render메서드가 실행될때마다 특정 값을 매번 재계산 해주는 걸 방지
- useMemo를 사용하지 않고도 정상적으로 구현되는 방법을 선택하고 useMemo는 최적화 하는 용도로만 사용하라.
useCallback
- useMemo와 동일하게 동작하며, 참조의 동일성에 의한 최적화된 콜백을 자식 컴포넌트에 전달할때 용이하다.
커스텀 훅 만들기
- 컴포넌트간 로직을 재활용하고 싶은 경우 고차컴포넌트, render 프롭 전략 외에 사용할수 있음