이번 포스팅은 상위컴포넌트에서 하위 컴포넌트를, 하위컴포넌트에서 상위컴포넌트를 변경하는것에 대해 포스팅 해보겠습니다.


이전 포스팅에서 잠깐 언급하였지만, 이번 포스팅은 목적은 리액트가 가진 데이터의 흐름, 

즉 단일 방향으로만 이루어지는 데이터의 흐름으로 인해 생기는 복잡성을 직접 알아보기 위함입니다. 

따라서 이번 포스팅에 나오는 내용들에 대해서는 공부를 하되, 실무에서는 쓰지 않는것을 추천드립니다.


시작하겠습니다.


1. 상위 컴포넌트에서 하위 컴포넌트의 내용 변경하기


이번에 실습해 볼 컴포넌트들의 관계 입니다.


- App

   - Parent

      - Me

         - Child


과정은, Child는 props로 App의 state.toChild 값을 받아 화면에 표시할것입니다.

App에는 버튼이 있어서, 버튼을 누를때마다 state의 값이 변화하고, 그 변화한 값은

Child로 전달되어 렌더링 되게 됩니다.

그러나 App과 Child 사이에는 Parent와 Me가 있으므로 App의 toChild 값은

먼저 Patrent에게 props로 전달되고, 그 값은 또다시 Me에게 props로 전달되고

비로서 마지막에 Child로 전달될것입니다. ( 복잡하네요 )


바로 시작해보겠습니다.


새롭게 CRA를 이용하게 프로젝트를 생성해주세요.

프로젝트 이름은 자유롭게 해주세요.

( 이전 프로젝트를 그대로 쓰셔도 되지만 이번 포스팅 부터는 폴더구조도 신경쓰면서 할것이라 새롭게 프로젝트 생성하는것이 더 편하실거 같습니다. )


그리고 src 폴더에 Components 폴더와 Containers 폴더를 생성하고

각각 index.tsx 파일을 생성해주세요. 

그리고 App.tsx 파일을 Containers 폴더로 이동시켜주세요.

Components 폴더에는 

 Parent.tsx , Me.tsx, Child.tsx

 파일을 생성해주세요.

역시 아직까지는 App.test.tsx 파일은 지우고 하겠습니다.




이번 포스팅 부터는 폴더를 나누어 실습하는것을 시작하려 합니다.

사용되는 의미에 따라 나누어 추후에 좀더 관리를 용이하게 하기 위함입니다.


여기서 사용되는 폴더 구조는 https://github.com/erikras/react-redux-universal-hot-example

에서 사용되는 컨벤션을 사용하고 있으며, Components 에는 각 컴포넌트들이,

그리고 Containers 에는 추후에 포스팅할 리액트 라우터로 보여지는 페이지가 위치하게 됩니다.

App.tsx 는 각 페이지에 대한 틀로써 사용되기 때문에 Containers 에 위치시켰습니다.


참고한사이트 : https://velopert.com/1954 에서 7.프로젝트 디렉토리 구조 이해하기 부분


* 만약 선호하시는, 혹은 더 좋은 구조를 알고 계신분은 알려주시기 바랍니다.


우선 Containers의 App.tsx 부터 시작하겠습니다.


import * as React from 'react';

interface AppProps {
}

interface AppState {
}

class App extends React.Component<AppProps, AppState> {

constructor(props: AppProps) {
console.log('App constructor');
super(props);
}

componentWillMount() {
console.log('App componentWillMount');
}

componentDidMount() {
console.log('App componentDidMount');
}

componentWillReceiveProps(nextProps: AppProps) {
console.log(`Parent componentWillReceiveProps : ${JSON.stringify(nextProps)}`);
}

shouldComponentUpdate(nextProps: AppProps, nextState: AppState): boolean {
console.log(`App shouldComponentUpdate : ${JSON.stringify(nextProps)}, ${JSON.stringify(nextState)}`);
return true;
}

componentWillUpdate(nextProps: AppProps, nextState: AppState) {
console.log(`App componentWillUpdate : ${JSON.stringify(nextProps)}, ${JSON.stringify(nextState)}`);
}

componentDidUpdate(prevProps: AppProps, prevState: AppState) {
console.log(`App componentDidUpdate : ${JSON.stringify(prevProps)}, ${JSON.stringify(prevState)}`);
}

render() {

console.log('App render');

return (
<div className="App">
<h1>App</h1>
</div>
);
}

}

export default App;


저는 앞으로 Class형 컴포넌트를 작성할때 위와 같은 방식으로 틀을 짜놓고 작업을 할 계획입니다.

각종 콘솔이나, lifecycle을 계속 쓰는 이유는 직접 확인하면서 공부를 하기 위함입니다.


이번에는 src/Containers/index.tsx 파일을 살펴보겠습니다.


import App from './App';

export { App };


지금은 Containers에 App밖에 없어서 이 코드는 사실 의미가 없습니다.

하지만 Container가 늘어날수록 추후에 다른곳에서 Container를 import할때 좀더 가독성 있게 할수 있습니다.


이제 src/index에 App을 import 시키고 실행해보겠습니다.

기존의 import App 을 삭제해주시고, 

import { App } from './Containers';

변경해주세요.


index.css 의 body에

text-align: center;

추가하는건 옵션입니다.


그리고 실행해보겠습니다.



그리고 App.tsx에는 toChild state를 만들어 놓겠습니다.


interface AppState {
toChild: string;
}


constructor(props: AppProps) {
console.log('App constructor');
super(props);
this.state = {
toChild: 'App에서 Child로 보내주는 값'
};
}


자 이제 각 Component들을 만들 시간입니다.


위에서 App Component를 만든 방식대로 Components 폴더에 있는 각각의 Component를 작성해주세요. ( state 제외 )


예 ) Parent.tsx


import * as React from 'react';
import { Me } from '.';

interface ParentProps {
}

interface ParentState {
}

class Parent extends React.Component<ParentProps, ParentState> {

constructor(props: ParentProps) {
console.log('Parent constructor');
super(props);
}

componentWillMount() {
console.log('Parent componentWillMount');
}

componentDidMount() {
console.log('Parent componentDidMount');
}

componentWillReceiveProps(nextProps: ParentProps) {
console.log(`Parent componentWillReceiveProps : ${JSON.stringify(nextProps)}`);
}

shouldComponentUpdate(nextProps: ParentProps, nextState: ParentState): boolean {
console.log(`Parent shouldComponentUpdate : ${JSON.stringify(nextProps)}, ${JSON.stringify(nextState)}`);
return true;
}

componentWillUpdate(nextProps: ParentProps, nextState: ParentState) {
console.log(`Parent componentWillUpdate : ${JSON.stringify(nextProps)}, ${JSON.stringify(nextState)}`);
}

componentDidUpdate(prevProps: ParentProps, prevState: ParentState) {
console.log(`Parent componentDidUpdate : ${JSON.stringify(prevProps)}, ${JSON.stringify(prevState)}`);
}

render() {

console.log('Parent render');

return (
<div className="Parent">
<h1>Parent</h1>
<Me />
</div>
);
}

}

export default Parent;



그리고 Components/index.tsx 파일을 작성하겠습니다.


import Parent from './Parent';
import Me from './Me';
import Child from './Child';

export { Parent, Me, Child };

다시한번 말씀드리지만, 사실 여기서 이렇게까지 사용할 필요는 없습니다.

하지만 저도 습관을 만들기 위함이라 계속 이렇게 사용해 보도록 하겠습니다.

아마 좀더 공부하면서 바뀔수도.....


이제 각 Component들을 적용해보도록 하겠습니다.


우선 App.tsx 에 Parent를 import 시켜주세요.


import { Parent } from '../Components';


그리고 render에 Parent Component를 적용합니다.


render() {

console.log('App render');

return (
<div className="App">
<h1>App</h1>
<Parent {...this.state}/>
</div>
);
}


여기서 

{...this.state}

이 연산은 Spread 연산자입니다.


Spread연산자 참고 사이트

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_operator


여기서는 그냥

<Parent toChild={this.state.toChild}/>

이렇게 쓰셔도 무방합니다.


이제 Parent부터 Child까지 이 값을 props로 넘겨주세요.


예) Parent.tsx

interface ParentProps {
toChild: string;
}


render() {

console.log('Parent render');

return (
<div className="Parent">
<h1>Parent</h1>
<Me {...this.props}/>
</div>
);
}



그리고 Child에서는 받은 값을 적용시키겠습니다.


render() {

console.log('Child render');

return (
<div className="Child">
<h1>Child</h1>
<h2>{this.props.toChild}</h2>
</div>
);
}


실행시켜 보도록 하겠습니다.



잘 넘어갔네요.


이제 버튼만 만들어서 이 값을 변경시키는걸 App에 구현해보겠습니다.


함수부터 만들어주시고


private _changeToChild(): void {
this.setState({
toChild: 'App에서 Child로 보내주는 값 변경 변경 변경 변경!!!!!!'
});
}


constructor에 바인딩 해주세요.

this._changeToChild = this._changeToChild.bind(this);


마지막으로 버튼 추가해주세요.

<button onClick={this._changeToChild}>App에서 Child값 변경</button>


저장 후 확인하겠습니다.

이 상태에서 버튼을 누르면


변경이 됩니다.


2. 하위 컴포넌트에서 상위 컴포넌트의 내용 변경하기


이번에는 하위에서 상위 component 값을 변경하는 방법입니다.

과정은, App에 있는 state값을 변경하는 함수를 만들고,

그 함수를 prop로 하위로 전달하여 하위 component에 생성된 버튼을 클릭하면 그 함수를 실행하게 하여 상위 컴포넌트의 state를 변경하는 과정입니다.


일단 해보겠습니다.


App.tsx 를 수정해주세요.


interface AppState {
toChild: string;
fromChild: string;
}


constructor(props: AppProps) {
console.log('App constructor');
super(props);
this.state = {
toChild: '이건 App에서 Child로 보내주는 값',
fromChild: '이건 App의 state에 있는 값'
};
this._changeToChild = this._changeToChild.bind(this);
this._changeFromChild = this._changeFromChild.bind(this);
}


전달할 함수 추가해주세요.

private _changeFromChild(): void {
this.setState({
fromChild: 'Child에서 App값 변경 변경 변경 변경!!!!!!'
});
}


그리고 changeFromChild 라는 props로 함수를 전달합니다.

<Parent {...this.state} changeFromChild={this._changeFromChild}/>


이전과 과정이 거의 비슷하지만, 이번에는 Parent로 App의 state를 넘기는게 아니라,

ChangeFromChild라는 이름의 props로 _changeFromChild 함수를 넘겨주고 있습니다.


이제 Parent와 Me, Child의 props interface에 함수를 추가해주세요.


예) 

interface ParentProps {
toChild: string;
changeFromChild(): void;
}


마지막으로 Child에서는 버튼을 추가해주세요. 

onClick 이벤트에는 props로 받은 함수를 넣습니다.

<button onClick={this.props.changeFromChild}>Child에서 App의 값 변경</button>


실행하겠습니다



이렇게 되어있는데요,


각각 버튼을 누르면 작동하는것을 확인 가능합니다.


이번 포스팅은 상위 component에서 하위 component를 변경하거나 하위 component에서 상위component를 변경하는것에 대해 살펴보았습니다.


이 과정을 살펴본 이유는 이러한 과정의 복잡성을 보고, 나아가 프로젝트가 커지면 도저히 감당할수 없을거 같다라는걸 느끼기 위함이었습니다.

따라서 이러한 데이터의 교류를 사용하지 않고 다른 방법을 공부하기 전에 다뤄본 포스팅입니다.

대표적으로 Redux라는것 입니다. 최근에는 mobx라는것도 좋은 평가를 받고 있습니다.

일단 다음 포스팅에서 바로 Redux를 하지 않고, 좀더 몇가지 내용들을 살펴 본 후

빠른 시일 내에 Redux와 mobx에 대해 자세하게 포스팅할 계획입니다.

( Redux와 mobx에 대해 좀더 공부할 시간이 필요합니다 ^^; )


감사합니다.


소스코드

https://github.com/JaroInside/tistory-react-typescript-study/tree/9.changecomponent


터미널에서

git clone https://github.com/JaroInside/tistory-react-typescript-study.git


cd tistory-react-typescript-study


npm install ( yarn install )


git checkout 9.changecomponent



참고 슬라이드 - http://slides.com/woongjae/react-with-typescript-1#/
참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0



'React > React&typeScript' 카테고리의 다른 글

11. 서버사이드 렌더링 , 클라이언트 사이드 렌더링  (4) 2017.07.18
10. Composition , Refs , PureComponent  (0) 2017.07.17
8. Default Props  (0) 2017.07.13
7. LifeCycle  (0) 2017.07.12
6. stateless Component  (0) 2017.07.12
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,