JSX
는 항상 모든 태그를 하나로 감싸는 컨테이너 태그가 하나만 있어야 합니다. 이는 리액트가 하나의 컴포넌트만을 리턴해야 때문입니다. 여기서 문제점이 나타나는데, 불 필요한 컨테이너 태그가 생길 수 있다는 것 입니다. JSX
를 규칙을 따르다가 아래와 같은 코드가 만들어질 수도 있지 않을까요???
<div>
<div>
<div>
<h1>Hello world!</h1>
</div>
</div>
</div>
fragment
사용은 매우 간단합니다. 기존의 컨테이너 태그 대신<React.Fragment>
태그를 만들어주면 됩니다. 단축 문법으로 <>
로 쓰는 것도 가능합니다.
import { Fragment } from "react";
const App = () => {
return (
<Fragment>
<h1>Hello world</h1>
</Fragment>
// <>
// <h1>Hello world</h1>
// </>
);
}
const AddUser = (props) => {
...
...
return (
<div>
{error && <ErrorModal title={error.title} message={error.message} onConfirm={errorHandler} />}
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={enteredUserName}
onChange={userNameChangeHandler}
/>
<label htmlFor="age">Age (Years)</label>
<input id="age" type="number" value={enteredAge} onChange={ageChangeHandler} />
<Button type="submit">Add User</Button>
</form>
</Card>
</div>
);
};
위의 코드는 <form>
이벤트가 적용된 컴포넌트입니다. 이름과 나이가 들어가는 경우 유효성 검사를 통해 에러 모달을 던져줍니다. 저 상태에서도 프로그램을 쓰는데는 큰 문제는 없겠지만, 어찌되었든 좋은 코드라고 보기 어려운 현상이 나타납니다.
우리가 자주 쓰는 모달 페이지는 보통 <body>
태그 전체를 오버레이하여 모달을 정중앙 혹은 위쪽에 배치하여 나타내곤 합니다. 그렇기 때문에 보통 배치를 <header>
혹은 <body>
태그 위에 배치하곤 하죠. 하지만 JSX
모든 태그는 root
컨테이너 태그의 아래에 놓이기 때문에 기존의 배치 방식을 적용할 수 없는 현상이 발생합니다.
portal
은 부모 컴포넌트의 DOM
계층 구조 바깥에 있는 DOM
노드로 자식을 렌더링하게 만들어줍니다.
처음 리액트 프로젝트를 생성하는 경우, index.js
에는 기본적으로 React.DOM
을 통해 root
컴포넌트를 렌더링하고 있을 겁니다. portal
역시 이와 비슷한 방법으로 적용할 수 있습니다. 먼저 배치 할 새로운 루트 태그를 public
폴더의 html
파일에 넣어줍시다. 중요성을 강조하기 위해 id 값도 함께 설정해줍시다.
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="backdrop-root"></div>
<div id="overlay-root"></div>
<div id="root"></div>
...
...
</body>
그 이후 새로운 컴포넌트를 생성해주고 ReactDOM
을 import
하여 ReactDOM.createPortal()
함수를 호출해주면 됩니다.
ReactDOM.createPortal()
함수에는 2개의 인자를 받습니다. 이는 index.js
파일에 기본적으로 존재하는, ReactDOM.render()
함수와 동일합니다.
첫번째 인자 : 컴포넌트
두번째 인자 :index.html
의 루트DOM
const ErrorModal = (props) => {
return (
<>
{ReactDOM.createPortal(
<Backdrop onConfirm={props.onConfirm} />,
document.getElementById("backdrop-root")
)}
{ReactDOM.createPortal(
<ModalOverLay title={props.title} message={props.message} onConfirm={props.onConfirm} />,
document.getElementById("overlay-root")
)}
</>
);
};
리액트에서 훅을 통해 컴포넌트의 데이터를 변경하거나 읽어오기 위해 state
를 사용합니다. state
의 단점은 뭐가 있을까요??? 우선 단순히 읽어오기만 하면 되는 태그의 value
조차도 일일이 읽기와 변경을 모두 설정해주어야 한다는 겁니다. 또한, state
는 해당 태그의 값이 변경될 때 마다 state
의 데이터가 변경, 저장, 변경, 저장을 반복합니다. 이러한 문제들을 개선하는 것이 바로 리액트 내에서 DOM API
에 직접 접근 하는 refs
입니다.
state
마찬가지로 ref
에는 useRef()
라는 함수가 존재합니다. DOM
을 통해 직접적으로 값을 가져올 태그에 ref
속성을 정의해준 후, useRef()
와 연결해주면 됩니다.
const AddUser = (props) => {
const nameInputRef = useRef();
const ageInputRef = useRef();
...
...
const enteredName = nameInputRef.current.value;
const enteredUserAge = ageInputRef.current.value;
console.log(enteredName, enteredUserAge);
return (
<>
{error && <ErrorModal title={error.title} message={error.message} onConfirm={errorHandler} />}
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
// value={enteredUserName}
// onChange={userNameChangeHandler}
ref={nameInputRef}
/>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
// value={enteredAge}
// onChange={ageChangeHandler}
ref={ageInputRef}
/>
<Button type="submit">Add User</Button>
</form>
</Card>
</>
);
}
라이프 사이클의 내에서 빈번한 변경을 알 필요가 없고, 마지막으로 주어진 값만 잘 읽어올 수 있다면 ref
가 좋은 대안일 것 입니다. 리액트와 상태 관리를 통해 데이터가 어떻게 변경되는 과정 모두를 알 필요가 있고, 데이터의 불변성을 침해하지 않는 선에서 데이터를 관리하기를 원한다면 state
가 나을 거라고 생각합니다. ref
도 데이터를 변경하는 것이 사실 가능하지만 직접적인 접근을 통한 데이터의 변경은 코드를 어지럽히기 마련이니까요.
추가적으로 리액트의 상태관리에 의해 데이터가 제어되는 컴포넌트를 controlled-component
라 하며, 리액트의 라이프 사이클 권한 밖에서 개발자가 직접 DOM
을 통해 데이터가 관리되는 컴포넌트를 uncontrolled-component
라고 합니다.