Composition


Composition(합성) Vs Inheritance(상속)


두 방법 모두 객체 지향에서 기능을 재사용 하기 위해 사용되는 방법입니다.


Inheritance - 클래스를 하나 만들고 이것을 부모 클래스라고 부르겠습니다.

자식 클래스는 이 부모클래스를 상속 받아 구현을 정의합니다.

상속은 재사용 보다는 기능 확장의 관점에 중점을 가지고 있습니다.


Composition - 서로 다른 객체를 여러개 붙여 새로운 기능이나 객체를 정의합니다



"Facebook 은 수천개의 컴포넌트에서 React 를 사용하며, 컴포넌트 상속 계층을 사용하는 것이 권장되는 use case 를 찾지 못했습니다."


"컴포넌트에서 UI 이외의 기능을 재사용 하고 싶으면,

상속을 이용하지 말고 자바스크립트 모듈로 분리해서 사용하는것이 좋다"


-> React를 사용할때는 Composition을 사용하는것이 좋다.



기본은 props를 이용한 재사용입니다.

코드를 통해 이해해 보도록 하겠습니다.


새로운 CRA 프로젝트를 생성해주세요.

create-react-app composition-test --scripts-version=react-scripts-ts


그리고 src 폴더에 components 폴더와 containers 폴더를 생성하고


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


App.tsx.파일을 contauners 폴더로 이동시켜주시고


App.test.tsx , css 파일은 삭제, logo 파일도 삭제해주세요.


components 폴더에는


HelloForm.tsx , HelloContent.tsx, Text.tsx 파일을 생성해놓으시기 바랍니다.


코드 작성하겠습니다.


src/containers/index.tsx


import App from './App';

export { App };


src/containers/App.tsx

import * as React from 'react';
import { HelloForm, HelloContent, Text } from '../components';

interface AppProps {
}

interface AppState {
name: string;
}

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

constructor(props: AppProps) {
console.log('App constructor');
super(props);
this.state = {
name: 'Jaro'
};
this.handleChange = this.handleChange.bind(this);
}
public handleChange(event: React.FormEvent<HTMLSelectElement>): void {
this.setState({ name: event.currentTarget.value });
}

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>Composition Test</h1>
<HelloForm
name = {this.state.name}
handleChange = {this.handleChange}
/>
<HelloContent
Test = {<Text name={this.state.name}/>}
/>
</div>
);
}
}

export default App;


src/component/index.tsx

import HelloForm from './HelloForm';
import HelloContent from './HelloContent';

import Text from './Text';

export { HelloForm, HelloContent, Text };


src/component/HelloForm.tsx

import * as React from 'react';

interface HelloFormProps {
name: string;
handleChange(e: {}): void;
}

export default class HelloForm extends React.Component<HelloFormProps, {}> {
constructor(props: HelloFormProps) {
super(props);
}

public render() {
return (
<div>
<input
value={this.props.name}
onChange={e => this.props.handleChange(e)}
/>
</div>
);
}
}


src/component/HelloContent.tsx

import * as React from 'react';

interface HelloContentProps {
Test: JSX.Element;
}

export default class HelloContent extends React.Component<HelloContentProps, {}> {
constructor(props: HelloContentProps) {
super(props);
}

public render() {
return (
<div>
{this.props.Test}
</div>
);
}
}


src/containers/Text.tsx

import * as React from 'react';

interface TextProps {
name: string;
}

export default class Text extends React.Component<TextProps, {}> {
constructor(props: TextProps) {
super(props);
console.log(this.props);
}

public render() {
return (
<div>
<h1>Hello {this.props.name}</h1>
</div>
);
}
}



위 코드는 모듈 구성 요소로 UI를 구성하여 작성한 기본 예제 입니다.

https://charleslbryant.gitbooks.io/hello-react-and-typescript/content/Samples/ComponentPropsAndState.html

을 참조하였으며, 제 나름대로 좀더 변형해 보았습니다.


참조 사이트에서는 type를 any로만 하여 제가 추구하고자 하는 any쓰지 않는것에 위배되므로

코드를 수정하였습니다.


각각의 prop를 interface로 빼내어 정의 하였고, any로 명시되어있는 부분들은 모두 type을 지정하였습니다.

EventHandle에서 event의 type을 React.FormEvent<HTMLSelectElment> 로 지정하였고,

App.tsx의 render에서 HelloContent에 Props 넘겨주는 Text component의 경우에는

JSX.Element type이라고 지정해주었습니다.


실행화면


Input 칸에 값을 변경하면 아래 Text 의 값도 변경하는것을 확인할수 있습니다.


Refs


props 를 다루지 않고 자식의 어떤 요소를 건드리고 싶을때..

ref를 이용하여 새롭게 랜더링을 하지 않고 하위 요소를 다룰수 있습니다.


ref를 남용하는것은 추천드리지 않으며, 가능하면 props와 state를 사용하여 문제를 해결하는것이 좋습니다.


만일 그 두가지로 해결이 되지 않을때 ref를 사용해주세요.

ref가 유용하게 사용되는 경우는 아래와 같습니다.

1. component에 의해 렌더된 Dom에 직접 어떠한 처리를 해야할 경우에

2. 다른 웹 프레임 워크와 혼용하여 사용할때 


PureComponent


이전 포스팅에서 잠시 언급한 PureComponent에 대해 공부해보겠습니다.


PureComponent는 ShouldComponentUpdate를 제외하면 React.Component와 동일합니다.


기본적으로 PureComponent는 Renderer에서 LifeCycle 로직시 Shallow Compare를 수행합니다.


Shallow Compare는 값의 레퍼런스를 비교하여 같은 값인지 다른값인지 판별하는것으로


번역하자면, 얕은 비교 라고 할수 있습니다.


코드를 통해 알아보겠습니다.


위에서 작성한 코드에서 App.tsx를 수정하겠습니다.


import * as React from 'react';
import { HelloForm, HelloContent, Text } from '../components';

interface AppProps {
}

interface AppState {
name: string;
array: number[];
}

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

constructor(props: AppProps) {
console.log('App constructor');
super(props);
this.state = {
name: 'Jaro',
array: [1]
};
this.handleChange = this.handleChange.bind(this);
this._Click = this._Click.bind(this);
}
public handleChange(event: React.FormEvent<HTMLSelectElement>): void {
this.setState({ name: event.currentTarget.value });
}

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>Composition Test</h1>
<h1>{this.state.array.join(', ')}</h1>
<button onClick={this._Click}>추가</button>
<HelloForm
name = {this.state.name}
handleChange = {this.handleChange}
/>
<HelloContent
Test = {<Text name={this.state.name}/>}
/>
</div>
);
}

private _Click() {
const next: number[] = this.state.array;
next.push(this.state.array[this.state.array.length - 1] + 1);
this.setState({
array: next
});
}
}

export default App;


state에 array를 추가하고, 버튼을 누르면 array 값이 추가되는 함수를 작성하였습니다.


또한 ShouldComponentUpdate를 주석처리 하였습니다.


실행해보겠습니다.



추가 버튼을 누르면 정상적으로 변한값이 렌더링 되는것을 확인할수 있습니다.


이번에는


class App extends React.Component<AppProps, AppState>


부분을 

class App extends React.PureComponent<AppProps, AppState>

로 바꿔주세요. PureComponent입니다.


실행하면 화면은 똑같은 화면이 나오지만 추가를 눌러도 화면에 변화는 없습니다.


하지만 여기서 input 박스 안의 내용을 바꾼다면 순식간에 렌더링이 진행됩니다.


즉 array의 값은 바뀌었지만 Shallow Compare로 인해 렌더링이 진행되지 않았던 것입니다.


따라서 이 문제를 해결하기 위해서는 copy를 사용하거나 Immutable.js 를 사용하는 방법이 있습니다. -> 레퍼런스 값을 변화시켜줍니다.


* 모든 상황에서 PureComponent 가 가장 성능이 좋은것은 아닙니다.

* 주의할점은 PureComponent class가 ShouldComponentUpdate를 정의하고 있다는 뜻은 아니며, 실제로 정의하고 있지도 않다 라고 합니다.

http://meetup.toast.com/posts/110


소스코드

https://github.com/JaroInside/tistory-react-typescript-study/tree/10.composition-purecomponent


드디어 스터디의 1주차 공부 및 정리가 어느정도 된거같습니다.


5주차까지 다 하려면 아직 먼 길이 남은거 같지만 확실하게 끝내 보도록 하겠습니다.


다음 포스팅은 2주차 내용인 react Router 입니다.


감사합니다.



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


블로그 이미지

Jaro

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

,