Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
props
와 state
= 부모-자식 컴포넌트가 연결된 상태에서 공유하는 데이터context
를 통해 데이터 공유 가능컴포넌트는 트리 구조 형태를 띄고 있음.
상위 -> 하위 컴포넌트로 전달시 props 사용.
최상위 컴포넌트에서 최하위 컴포넌트로 props
를 전달하려면 그 중간 컴포넌트들을 모두 거쳐야 한다.
-> 만약 중간 컴포넌트에서 그 props를 사용하지 않거나 전달과정에서 누락이 될 시, 오류 발생.
공급자 | 관찰자 (=소비자) |
---|---|
데이터 보관, 변경, 소비자에게 데이터 공급 | 공급자를 구독하여 데이터를 소비 |
1. 소비자는 공급자보다 낮은 계층에 있어야 한다.
-> 소비자가 공급자보다 상위에 있으면, 공급자를 구독할 수 없다.
2. 소비자는 공급자가 제공하는 콜백 함수로 데이터를 변경할 수 있다.
-> 소비자는 공급자의 하위계층이지만, 공급자의 데이터를 변경할 수 있다.
-> 직접 변경은 X. 공급자로부터 데이터 변경을 위한 콜백을 받아 데이터 변경 요청을 함.
- 공급자는 소비자에게 콜백함수를 전달함.
- 소비자는 콜백함수에 인자로 자신이 속한 컴포넌트의 데이터를 전달함.(변경 요청)
-> 공급자의 데이터가 변경되었으므로 소비자와 동일하게 맞춤.
-> Data Down, Action Up (DDAU) 라고 부름.
getChildContext
정의/src/component/ctx/HomePageComponent.jsx
// 공급자 구현 - 공급자 역할을 할 공급자의 자료형(childContextTypes)과, 데이터 제공함수(getChildContext) 정의
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Button from '../Button';
import ButtonWithContext from './ButtonWithContext'; // 소비자.
function RowBComponent() {
return <Button>버튼</Button>;
}
function RowCComponent() {
return <ButtonWithContext>버튼</ButtonWithContext>; // 소비자인 ButtonWithContext를 출력
}
function TableComponent() {
return (
<table>
<RowBComponent />
<RowCComponent />
</table>
);
}
class HomePageComponent extends PureComponent {
constructor(props) {
super(props);
this.state = { loading: false };
this.setLoading = this.setLoading.bind(this); // setLoading을 소비자에 전달하여 데이터를 변경할 예정.
// this를 바인딩항 소비자에서 공급자의 setState()에 접근할 수 있도록 함.
this.toggleLoading = this.toggleLoading.bind(this);
}
getChildContext() { // 소비자는 getChildContext로 loading과 setLoading을 전달받을 것임.
return {
loading: this.state.loading,
setLoading: this.setLoading,
};
}
setLoading(loading) {
this.setState({ loading });
}
toggleLoading() {
this.setState(({ loading }) => ({ loading: !loading }));
}
render() {
return (
<div>
<TableComponent />
<Button onPress={this.toggleLoading}>상태 변경</Button> { // toggleLoading을 실행하는 버튼 }
</div>
);
}
}
HomePageComponent.childContextTypes = { // 컨텍스트의 자료형 정의
loading: PropTypes.bool,
setLoading: PropTypes.func,
};
export default HomePageComponent;
참고 -
onClick
과onPress
onPress = { () => this._onPress(key) }
- onPress는 HTML 어트리뷰트 onClick과 같지만, 콜백함수를 값으로 받는다.
- 즉, 함수의 리턴값이 아닌 함수 자체를 값으로 전달해줘야 한다.
참고 2 - PropTypes
- propTypes - props의 타입을 정의
- defaultProps - props의 기본값 정의
- childContextTypes - context의 타입 정의
contextTypes
속성을 추가하여 공급자에서 구독할 항목 정의./src/component/ctx/WithLoadingContext.jsx
import React from 'react';
import PropTypes from 'prop-types';
export default (WrappedComponent) => { // 하이어오더 컴포넌트임. (WithLoadingContext를 리턴함)
const { displayName, name: componentName } = WrappedComponent;
const wrappedComponentName = displayName || componentName;
function WithLoadingContext(props, context) {
const { loading, setLoading } = context;
return (
<WrappedComponent {...props} loading={loading} setLoading={setLoading} /> { // ✅ context로 받은 객체를 직접 프로퍼티로 전달함.}
);
}
WithLoadingContext.displayName = `withLoadingContext(${wrappedComponentName})`;
WithLoadingContext.contextTypes = {
loading: PropTypes.bool,
setLoading: PropTypes.func,
};
return WithLoadingContext;
};
/src/component/ctx/ButtonWithLoadingContext.jsx
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../Button';
import WithLoadingContext from './WithLoadingContext';
function ButtonWithLoadingContext({ label, loading, setLoading }) { // 공급자의 데이터를 props로 전달받음
return (
<Button onPress={() => setLoading(!loading)}>
{loading ? '로딩 중' : label}
</Button>
);
}
ButtonWithLoadingContext.propTypes = {
label: PropTypes.string,
loading: PropTypes.bool,
setLoading: PropTypes.func,
};
export default WithLoadingContext(ButtonWithLoadingContext);
/src/component/ctx/LoadingProvider.jsx
import React from 'react';
import PropTypes from 'prop-types';
class LoadingProvider extends React.Component {
constructor(props) {
super(props);
this.state = { loading: false };
this.setLoading = this.setLoading.bind(this);
}
getChildContext() { // HomePageComponent에 사용된 공급자 데이터 항목을 옮겨놓음
return {
loading: this.state.loading,
setLoading: this.state.setLoading,
};
}
setLoading(loading) {
this.setState({ loading });
}
render() {
return this.props.children; /// 자식 props 노드를 출력함.
}
}
LoadingProvider.childContextTypes = {
loading: PropTypes.bool,
setLoading: PropTypes.func,
};
export default LoadingProvider;
/src/component/ctx/HomePageWithProvider
import React, { PureComponent } from 'react';
import Button from '../Button';
import ButtonWithLoadingContext from './ButtonWithLoadingContext'; // 소비자 컴포넌트
import LoadingProvider from './LoadingProvider'; // 공급자 컴포넌트
function RowBComponent() {
return <Button>버튼</Button>;
}
function RowCComponent() {
return <ButtonWithLoadingContext>버튼</ButtonWithLoadingContext>; // 소비자 출력
}
function TableComponent() {
return (
<table>
<RowBComponent />
<RowCComponent />
</table>
);
}
class HomePageComponent extends PureComponent {
render() {
return (
<LoadingProvider> // 공급자
<TableComponent />
<ButtonWithLoadingContext>상태 변경</ButtonWithLoadingContext> // 소비자 출력
</LoadingProvider>
);
}
}
export default HomePageComponent;
/src/stories/ContextStory.jsx
import React from 'react';
import { storiesOf } from '@storybook/react';
import HomePageComponent from '../component/ctx/HomePageComponent';
import HomePageWithProvider from '../component/ctx/HomePageWithProvider';
storiesOf('HomePageComponent', module)
.add('컨텍스트', () => <HomePageComponent />)
.add('Provider', () => <HomePageWithProvider />);