이렇게 생긴 form에서 바닥 선 색 변환 ( 화살표 )


은근히 시간을 많이 잡아먹은...


공식 홈페이지에서는


/* label underline focus color */
   .input-field input[type=text]:focus {
     border-bottom: 1px solid #000;
     box-shadow: 0 1px 0 0 #000;
   }


이렇게 되어있는데,

 !important 붙여줘야 제대로 작동...


또한 만일 패스워드 부분일경우 type=password 이므로 

.input-field input[type=password]:focus 를 따로 지정해줘야함.



블로그 이미지

Jaro

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

,

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/

,

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


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

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

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


시작하겠습니다.


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/

,