[Day 43] React Basic(Layout Component) & React Advance(Context, Portals)
React Basic
합성 (composition) vs 상속 (inheritance)
Layout 컴포넌트
컴포넌트에 어떤 자식이 들어올 지 미리 알 수 없는 경우에, 비어있는 ‘박스’ 역할을 하는 컴포넌트에서 사용하는 패턴.
children
이라는 특별한 prop을 통해 자식 요소를 출력에 그대로 전달할 수 있다.
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>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
만약 컴포넌트에 여러개의 ‘빈 공간(empty space)’ 이 필요하다면, children
대신에 자신만의 관례(props)를 만들어 사용할 수 있다.
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 />
} />
);
}
(장점!) 공통되는 부분이 많지만 아주 약간씩 다른 부분들을 처리해주기에 적합하다. (Layout 컴포넌트에 props를 넘겨서 다른 부분들을 처리해줄 수 있다.)
React Advance
Context
일일이 props를 연결해서 내려보내주지 않아도 데이터를 컴포넌트 트리 아래 쪽으로 한번에 전달할 수 있게 해주는 API.
(언제 사용?) 앱의 여러 부분에서 공유되어야 하는 정보(ex. 현재 유저 정보, 언어 설정, UI 테마, …)를 상위 컴포넌트에 두고 하위의 여러 컴포넌트에 뿌리기 위해 Context API를 사용한다.
(과거에는) Context API가 등장하기 이전에는 데이터를 공유하기 위해 Redux
를 사용했다.
(사용법)
const ThemeContext = React.createElement('defaultValue')
// defaultValue 인수는 오직 상위에 같은 context로부터 생성된 Provider가 없고, Consumer만 있을 경우에 사용된다.
const {Provider, Consumer} = React.createContext(defaultValue);
-------------------------------------------------------------------
// Provider 컴포넌트를 이용해 'dark'라는 정보를 내려준다.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
-------------------------------------------------------------------
// Consumer 컴포넌트를 이용해서 정보를 받아준다.
// Provider에서 넘겨준 값을 매개변수로 받는 함수에서 React 요소를 반환한다.
// 컴포넌트의 자식으로 함수를 사용하는 이러한 패턴을 'function as a child' 라고 부른다.
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
// Consumer는 가장 가까운 Provider 컴포넌트를 찾아서 정보를 받아온다.
// 중간에 같은 Context로부터 생성된 Provider가 하나 더 존재한다면 context 값이 덮어써지면서 중간 Provider의 context 값을 받아온다.
// 다른 Context로부터 생성된 값은 다른 Context에서 받아올 수 없다.
(Very 중요!!) Provider의 자손인 모든 Consumer는 Provider의 value
prop이 바뀔 때마다 다시 렌더링 된다. (shouldComponentUpdate
의 영향을 받지 않으므로, 조상 컴포넌트의 업데이트가 무시된 경우라도 Provider의 value
값이 바뀌었다면 Consumer는 업데이트 되면서 리렌더링 된다.)
Context 값 변경하기
버튼 클릭으로 Context값의 상태를 바꿔주면서 리렌더링하는 예제
(중요!) Context 값으로 객체를 내려보내면서 필요한 값과, 값을 바꾸는 함수를 같이 내려보낼 수 있다. —> 객체를 어떤 형태로 내려보내느냐에 따라 효율이 달라진다.
- render() 메소드 안에서 객체를 만들어서 내려보내면 매번 객체가 달라지고, 객체가 달라지면 그 객체 값을 받는 Consumer에서도 값이 바뀐 것으로 인식해서 매번 새로 그린다.
- Provider의 context 값으로
this.state
를 넘긴 뒤, Consumer에서 객체의 property를 분해대입으로 받아서 사용하면 state가 변할 때만 Consumer가 새로 그려진다.
게시판 실습
(중요!) 컴포넌트간의 의존관계가 존재할 때, 의존성을 가진 컴포넌트는 상위 컴포넌트의 하위에 존재해야만 한다.
라이프사이클 메소드에서 context에 접근하기
라이프사이클 메소드에서 context값을 사용해야 하는 경우, 값을 prop으로 넘겨준 뒤, 일반적인 prop을 다루듯이 다루면 된다.
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme ? 'dark' : 'light'}>
{children}
</button>
);
}
}
// props를 받아서 함수형 컴포넌트(Unknown)를 만들어주고 그 안에서 위에 정의한 Button 컴포넌트를 불러온다.
// 쓸 때는 Button 컴포넌트와 똑같은 방법으로 사용할 수 있다.
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
이러한 기법은 동일한 Context를 사용하는 모든 컴포넌트에서 똑같이 적용할 수 있는데, 수많은 컴포넌트에 위와 같은 코드를 전부 작성해주는 것이 불편하기 때문에 위의 Unknown 함수형 컴포넌트를 함수로 둘러싸서 재활용하기 편하게 만드는 것이 바로 고차 컴포넌트
이다.
Portals
포탈은 부모 컴포넌트의 DOM 계층 외부에 존재하는 DOM 노드로 자식에 렌더링하는 방법을 제공한다.
(언제 사용?) Modal, Popup, ToolTip 등 z-index를 높여서 보여줘야 하는 경우에, z-index만으로는 해결이 안되는 stacking context(싸인 맥락) 같은 알고리즘에 의해 보호되는 DOM 요소의 순서를 거슬러서 해당 컴포넌트의 외부 DOM 노드에 렌더링 할 수 있게 해줄 수 있다.
/* (사용 예시) */
render() {
// React mounts a new div and renders the children into it
return (
<div>
{this.props.children}
</div>
);
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
Portals를 통한 이벤트 버블링
포탈은 DOM 트리의 어디에나 존재할 수 있지만, 일반 React 자식처럼 동작한다. 따라서 포탈이 DOM 트리 상의 어느 위치에 존재하더라도 여전히 React Tree 내부에 존재하는 것이기 때문에 이벤트 버블링도 React 트리 내부의 순서에 따라 이루어진다.
—> 즉, 포탈 내부에서 시작된 이벤트는 DOM 트리에서 조상이 아니더라도 React 트리에 있는 조상에 전달된다.
댓글남기기