<JSXElement></JSXElement>
<JSXElement/>
<>JSXChildren</>
<$></$>
<foo:bar></foo:bar>
<foo.bar foo:bar="baz"></foo.bar>
@babel/plugin-transform-react-jsx 플러그인을 통해 변환된다.
변환 결과
// 변환 전
const ComponentA = <A required={true}>Hello World</A>
const ComponentB = <>Hello World</>
const ComponentC = (
<div>
<span>Hello World</span>
</div>
)
// 변환 후
var ComponentA = React.createElement(
A,
{
required: true,
},
'Hello World'
)
var ComponentB = React.createElement(React.Fragment, null, 'Hello World')
var ComponentC = React.createElement(
'div',
null,
React.createElement('span', null, 'Hello World')
)
결과를 활용해 JSXElement만 다르고 JSXAttributes, JSXChildren이 완전 동일할 때 중복 코드를 최소화할 수 있다.
// props 여부에 따라 children 요소가 달라지는 경우
// 1) 삼항연산자로 처리(bad)
import {createElement, propsWithChildren} from 'react'
function TextOrHeading({
isHeading,
children
}){
return isHeading ? (
<h1 classname="text">{children}</h1>
) : (
<span classname="text">{children}</span>
)
}
// 2) 간결하게 처리(good)
import {createElement} from 'react'
function TextOrHeading({
isHeading,
children
}) : {
return createElement({isHeading}){
isHeading ? 'h1' : 'span',
{className: 'text'},
children
}
}
개념
역할
구성
특징
속성
stateNode : 파이버 자체에 대한 참조. 참조를 바탕으로 리액트는 파이버와 관련된 상태에 접근한다.
child, sibling, return
파이버 간의 관계 개념을 나타낸다.
리액트 컴포넌트 트리처럼 파이버도 트리 형식을 가짐 -> 트리 형식을 구성하는 데 필요한 정보가 이 속성 내부에 정의된다.
하나의 child만 존재한다. -> 여러 개면 첫 번째 자식의 참조로 구성된다.
// 구조
<ul>
<li>하나</li>
<li>둘</li>
<li>셋</li>
</ul>
// 파이버
const l3 = {
return: ul, // 부모
index: 2, // 형제 중 순서
}
const l2 = {
sibling: 13,
return : ul,
index: 1,
}
const l1 = {
sibling: 12,
return : ul,
index: 0
}
const ul = {
//...
child : l1
}
index : 여러 sibling 사이에서 자신의 위치가 몇 번째인지
pendingProps : 아직 작업을 미처 처리하지 못한 props
memoizeProps : pendingProps를 기준으로 렌더링 완료 이후 pendingProps를 memoizedProps로 저장해 관리
updateQueue : 상태 업데이트, 콜백 함수, DOM 업데이트 등 필요한 작업을 담아두는 큐
memoizedState : 함수 컴포넌트의 훅 목록 저장
alternate : 반대편 트리 파이버
이렇게 생성된 파이버는 state 변경, 생명주기 메서드 실행, DOM 변경 필요한 시점 등에 실행된다.
리액트의 핵심 원칙은 UI를 문자열, 숫자, 배열과 같은 값으로 관리한다는 것이다.
import React from 'react'
interface SampleProps {
required?: boolean
text: string
}
interface SampleState {
count : number
isLimited?: boolean
}
class SampleComponent extends React.Component<SampleProps, SampleState>{
private constructor(props: SampleProps){
super(props)
this.state = {
count : 0
isLimited: false,
}
}
private handleClick = () => {
const newValue = this.state.count + 1
this.setState({count : newValue, isLimited: newValue >= 10})
}
public render(){
const {
props : {required, text},
state : {count, isLimited}
} = this
return (
<h2>
Sample Component
<div>{required ? '필수' : '필수 아님'}</div>
<div>문자: {text}</div>
<div>count : {count}</div>
<button onClick={this.handleClick} disabled={isLimited}>증가</button>
</h2>
)
}
}
생명주기 메서드가 실행되는 시점은 크게 3가지로 나뉜다.
render()
componentDidMount()
componentDidUpdate()
componentWillUnmount()
shouldComponentUpdate()
shouldComponentUpdate(nextProps: Props, nextState: State){
// true인 경우, 즉 props의 title이 같지 않거나 state의 input이 같지 않은 경우에는
// 컴포넌트 업데이트. 이외의 경우에는 업데이트하지 않는다.
return this.props.title !== nextProps.title || this.state.input !== nextState.input
}
static getDerivedStateFromProps()
componentWillReceiveProps를 대체할 수 있는 메서드
render()를 호출하기 직전에 호출된다.
static으로 선언되어 this에 접근할 수 없고, 반환되는 객체는 내용이 모두 state로 들어간다.
모든 render() 실행 시 호출된다.
static getDerivedStaetFromProps(nextProps: Props, prevState: State){
// 다음에 올 props를 바탕으로 현재의 state를 변경하고 싶을 때 사용한다.
if(props.name !== state.name){
// state가 이렇게 변경된다.
return {
name: props.name
}
// state에 영향을 미치지 않는다
return null
}
}
getSnapShotBeforeUpdate()
componentWillUpdate를 대체할 수 있는 메서드
클래스 컴포넌트에서만 사용 가능하다.
DOM이 업데이트되기 직전에 호출된다.
반환되는 값은 componentDidUpdate로 전달된다.
DOM이 렌더링되기 전 윈도우 크기를 조절하거나 스크롤 위치를 조정하는 등 작업 처리에 유용하다.
getSnapShotBeforeUpdate(prevProps: Props, prevState: State){
// props로 넘겨받은 배열의 길이가 이전보다 길어지면 현재 스크롤 높이값을 반환
if(prevProps.list.length < this.props.list.length){
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps: Props, prevState: State, snapshot: Snapshot){
// getSnapshotBeforeUpdate로 넘겨받은 값은 snapshot으로 접근 가능
// 값이 있다면 스크롤 위치 재조정
if(snapshot !== null){
const list = this.listRef.current
list.scrollTop = list.scrollHeight - snapshot;
}
}
static getDerivedStateFromError()
componentDidCatch
리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다.
렌더 단계에서 계산 후 변경 사항이 없다면 커밋 단계는 생략될 수 있다.