본문 바로가기

React.js

Contact * 주소록 심화 *

이번엔 주소록 관련 심화 버전을 진행해보려고 합니다.

코드 먼저 보여드리고 하나하나씩 정리하겠습니다.






import React from 'react';
import ContactInfo from './ContactInfo';
import ContactDetails from './ContactDetails';

export default class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {
keyword: "", // input name ="keyword" 여기서 입력 하는 내용을 (인풋에 쓰는 값 >> state에 값 을 넣기 위함 )
// react-loader >> component가 수정되어서 reloading 될 때 state를 파괴하지 않고 유지 시켜줌
// 부작용 : react한 loader는 component가 수정 되어서 reloading 될 때
// constructor 실행 하지 않음 .
selectedKey : "",
contactData: [{
name: 'aspect',
phone: '000-0000-0001'
},
{
name: 'browny',
phone: '000-0000-0002'
},
{
name: 'cerkein',
phone: '000-0000-0003'
},
{
name: 'dramatic',
phone: '000-0000-0004'
}]
};
console.log("aa");
console.log("aa");
// 콘솔 찍은 거 이거 constructor는 실행되지 않기 때문에
// 수정되어도 reloading 작업할 때에 찍히지 않는다.
// refresh 해줘야함!!

this.handleChange = this.handleChange.bind(this);
// 바인딩은 필수로 해주기 !!
this.handleClick = this.handleClick.bind(this);
// 함수 사용 시 바인딩은 필수
}

handleChange(e) {
// e >> event 객체
this.setState({
keyword: e.target.value
});

}

handleClick(key){
this.setState({
selectedKey : key
});

console.log(key);
}





Contact 클래스에서 rendering 하기 이 전 내용들입니다.

주의해야할 점은 rendering 전에는 constructor 정의 / 함수 정의 / 바인딩 및 state 초기값 설정 등을 진행하게 되는데 , 가시적으로 다음과 같이 진행하길 권고드립니다.






this.state = {
keyword: "", // input name ="keyword" 여기서 입력 하는 내용을 (인풋에 쓰는 값 >> state에 값 을 넣기 위함 )
// react-loader >> component가 수정되어서 reloading 될 때 state를 파괴하지 않고 유지 시켜줌
// 부작용 : react한 loader는 component가 수정 되어서 reloading 될 때
// constructor 실행 하지 않음 .
selectedKey : "",
contactData: [{
name: 'aspect',
phone: '000-0000-0001'
},
{
name: 'browny',
phone: '000-0000-0002'
},
{
name: 'cerkein',
phone: '000-0000-0003'
},
{
name: 'dramatic',
phone: '000-0000-0004'
}]
};




1. Contact 클래스의 state 변수는 총 3개이다. 
( keyword , selectedKey , contactData[배열] ) 

2. state 변수 선언은 생성자 내에서 진행되며 super(props); 다음에 해줘야한다.







this.handleChange = this.handleChange.bind(this);
// 바인딩은 필수로 해주기 !!
this.handleClick = this.handleClick.bind(this);
// 함수 사용 시 바인딩은 필수


}

handleChange(e) {
// e >> event 객체
this.setState({
keyword: e.target.value
});

}

handleClick(key){
this.setState({
selectedKey : key
});

console.log(key);
}

1. 함수 정의는 constructor 다음에 진행되며 사용시 바인딩은 constructor 내에서 해줘야 한다.

2. handleChange(e) 에서 e는 event (e) 의 객체로 어떠한 이벤트가 발생할 경우 해당 

타겟의 값을 가지고 온다.

3. selectedKey를 다음과 같이 setState의 변화할 값으로 정의해두었는데, 이렇게하면

constructor에서 selectedKey : " " 이런식으로 default 값을 정의해주는 것이 좋다. 
(명시적으로) 




  render() {
const mapToComponent = (data) =>{
data.sort((a,b) => { return a.name > b.name;});
data = data.filter((contact2) => {
return contact2.name.toLowerCase().indexOf(this.state.keyword) > -1;
});
return data.map((contact,i) => {
return (<ContactInfo
contact = {contact}
key = {i}
onClick={()=> this.handleClick(i)}/>);
});
};

return (
<div>
<h1>Contacts</h1>
<input
name="keyword"
placeholder="Search"
value={this.state.keyword}
onChange={this.handleChange}
// onChange는 자바스크립트 함수! (값이 변화할 때 호출되는 메소드)

/>
<div>{mapToComponent(this.state.contactData)}</div>
<ContactDetails
isSelected = {this.state.selectedKey != -1}
contact = {this.state.contactData[this.state.selectedKey]} />
</div>
);
}
}


Contact 클래스의 rendering 부분입니다.

render에서 크게 리턴은 3번 이루어집니다. 하지만 여기서 보여지는 것은 ContactInfo 클래스를 리턴하는 부분과 ContactDetails를 리턴하는 부분입니다. 






구조는 다음과 같습니다.




* index .js *

import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));



* App.js * 

class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>

<Contact />
</div>
);
}
}





index.js 에서는 App.js 를 import해와서 ReactDOM을 활용하여 뿌려줍니다. 


즉, App.js의 rendering 부분을 뿌려주는 것이죠

App.js 에서의 Rendering 부분은 Contact 클래스를 가지고 오는 것입니다.


즉, Contact클래스에서 rendering 하는 것이 중요한 것이죠.

그에 따라 Contact 클래스의 rendering 부분을 확인하면 크게 mapToComponent에서 ContactInfo 클래스가 리턴되어 rendering 되는 것

그리고 ContactDetails 클래스가 리턴 되는 것 이렇게 확인하실 수 있습니다.





  * Contact 클래스의 mapToComponent쪽 rendering *


const mapToComponent = (data) =>{
data.sort((a,b) => { return a.name > b.name;});
data = data.filter((contact2) => {
return contact2.name.toLowerCase().indexOf(this.state.keyword) > -1;
});
return data.map((contact,i) => {
return (<ContactInfo
contact = {contact}
key = {i}
onClick={()=> this.handleClick(i)}/>);
});
}; ... ... ...

<div>{mapToComponent(this.state.contactData)}</div>

다음과 같이 진행 구조를 갖는데, mapToComponent의 인자로는 Contact클래스의 state변수인 contactData를

갖게 되는 것입니다.


즉, 배열 형태의 json 값을 갖게 되는 것입니다.

따라서 data는 contactData의 각 배열 요소들이 되겠고, 이 값을 sort (오름차순 정열)를 합니다.

배열 요소에는 (json) name과 phone이 있었기 때문에 다음과 같이

a.name > b.name 형태의 부등식이 성립하게 되는 것입니다.

앞에 값이 더 크다고 설정한 경우 (반환값은 1 --> 참)

오름 차순이며, 그렇게 나온 값(data)을 filter를 통해서 원하는 값만 추출해낸다.



filter의 인자 contact2는 배열의 요소가 될 것이며 filter 함수의 콜백이자 조건이 될 것이다.

contact2.name을 가지고 오는데 indexOf() 라는 함수를 사용해서 keyword 값으로 들어온 것을 지속적으로

비교 분석하게 되고 -1 보다 크다고 하였으므로 0부터 즉, 일치하는 값이 처음부터 나오게 되면

필터링이 진행되게 된다.



this.state.keyword 값이 어디서 바뀌는 지 확인해보자. 확인하기 전에 keyword는 Contact 클래스의 state 값이구나

그러면 Contact 클래스 내의 rendering 부분에서 JSX 문법을 지키는 하에 만들어지겠다고 생각을 하고 들어가면

좋을 것 같습니다.



그렇게 필터링된 data값을 이제 mapping을 통해 새로운 배열을 만들어 냅니다.

여기서 만든 값은 contact가 되며 i는 index 값입니다.

(map 부분 다시 참조 --> callback 함수로 가질 수 있는 값 (변경할 값 , 인덱스))



그 이 후 ContactInfo 클래스의 props 값인 contact의 값으로 새로 필터링하여 맵핑한 배열 값을 넣어주고

key로는 인덱스를 넣어주게 됩니다.

그리고 이렇게 나온 결과값을 onClick() 할 경우 this.handleClick() 함수가 실행 되어 ,

selectedKey 값은 인덱스 값 (새로 생긴 배열의 key값)을 가지게 될 것입니다.


(selectedKey 값은 Contact 클래스의 state 변수입니다.)



그러면 ContactInfo 클래스에서는 무엇을 rendering 하는 지 확인해보겠습니다.






* ContactInfo.js *


import React from 'react';

export default class ContactInfo extends React.Component {
render() {
return (
<div onClick={this.props.onClick}>{this.props.contact.name} {this.props.contact.phone}</div>
);
}
}

ContactInfo에서 랜더링 하는 부분으로 props가 총 3가지였지만 한 개는 key 값으로 하였고

contact (새로 생긴 배열요소)와 onClick 메소드였기 때문에 다음과 같이 props 설정이 필요하다.


ContactInfo 클래스에서 rendering하는 부분이 Contact 클래스에서 첫 번째로 보여지게 되는 부분이며,

onClick 함수가 실행될 경우 , ContactInfo에서 rendering 하는 부분의 onClick으로 동일하게

작업되게 되는 것입니다.




(props의 선언을 통해)



this.props.onClick {this.props.contact.name} {this.props.contact.phone}








return (

<div>
<h1>Contacts</h1>
<input
name="keyword"
placeholder="Search"
value={this.state.keyword}
onChange={this.handleChange}
// onChange는 자바스크립트 함수! (값이 변화할 때 호출되는 메소드)

/>
<div>{mapToComponent(this.state.contactData)}</div>
<ContactDetails
isSelected = {this.state.selectedKey != -1}
contact = {this.state.contactData[this.state.selectedKey]} />
</div>
);
}

Contact 클래스에서 rendering하는 부분에서의 두 번째 리턴 하는 부분입니다.

nested 구조를 지키고자 <div> 태그 내에 선언되어 있으며 input태그로 값을 주게 됩니다. 

여기서 설정된 값은 (value) Contact클래스의 state 변수인 keyword 값이 되며 state 변수이기 때문에 유동적으로 계속 진행되게 되는 것입니다.

onChange() 함수를 통해서 handleChange 함수를 불러오게 되고 이 함수에서는 

keyword 값을 setState 하게 됩니다. 





뭔가 퍼즐이 맞춰가듯 맞아 들어가죠?


마지막으로 ContactDetails 클래스를 확인해보면 "isSelected props 값이 있고, 

contact props 값이 있구나! " 라고 생각한 다음 작업하면 됩니다.




ContactDetails의 props 값인 isSelected 값은 Contact 클래스의 state값인 selectedKey 값이며 selectedKey 값은 ContactInfo 클래스의 props 값인 onClick 함수를 통해 

새로 만들어진 배열의 요소들을 선택하게 되고 선택한 것들은 Contact 클래스의 state 함수인 handleClick() 이 호출되어 내부에 Contact 클래스의 state 변수인 selectedKey 값을 setState({ }) 하는 것입니다. (유동적으로 값을 변화시킨다.)




또한 contact라는 ContactDetails의 props 값은 Contact의 state 값인 contactData,

즉, Contact 클래스에서 rendering 되는 contactData의 배열 요소 중 인덱스가 

선택한 값 (selectedKey)으로 해서 나올 요소들을 rendering 한다.

contactData를 선택하였을 때 어떠한 값이 출력되는지 ContactDetails 클래스를 확인해보자




import React from 'react';


export default class ContactDetails extends React.Component {
render() {
const details = (
<div>
<p>{this.props.contact.name}</p>
<p>{this.props.contact.phone}</p>
</div>);
const blank = (<div>Not Selected</div>);


return (
<div>
{this.props.isSelected ? details : blank}
</div>
)
}
}

ContactDetails.defaultProps = {
contact:{
name : '',
phone : ''
}
};


ContactDetails 클래스의 rendering 부분을 확인하면 details와 blank 변수를 선언하고 

이에 대한 값을 return 해주고 있다.


details 변수 부분을 먼저보면 JSX 문법에 맞게 nested 구조로해서 contact의 name과

contact의 phone 부분을 props 변수로 선언해주고 있다. 



이는 ContactDetails의 contact (props) 라고 할 수 있다. 또한 이 부분이 contactData 배열을 가지고 오게 되므로 배열 요소의 name과 phone 부분을 다음 선언처럼 

가져올 수 있는 것이다.


또한 rendering 할 때에는 name과 phone을 개행하여 보여주게 되고 선택한 값의 인덱스의 배열 요소를 보여주게 될 것이다.




isSelected = {this.state.selectedKey != -1}

isSelected는 아래 return 값에서 props로 선언해주면서 -1이 아닐 경우 (참)

details를 뿌려주고 -1일 경우 blank를 뿌려준다 (삼항연산자)


ContactDetails.defaultProps = {
contact:{
name : '',
phone : ''
}
};


이 부분은 contact (ContactData)의 요소 - name , phone 의 props 초기 값을 설정하기 위함이다.




구조가 왔다갔다 하면서 많이 복잡했지만, 결국 한 쪽의 클래스의 state 값을 다른 한쪽 클래스의 props 로 받고 state 값은 생성자에 정의하며 setState는 rendering 이 후 

값을 변화 시켜주는 것, 그리고 할 때에는 바인딩을 꼭 해줘야 한다는 점 


이러한 것들의 종합된 코드가 아니였나 생각이 든다.


복잡하지만 차근차근 따라가면 그래도 잘 이해할 수 있을 것이다. 



감사합니다!

반응형

'React.js' 카테고리의 다른 글

주소록 추가 파트 * Immutability helper 심화 *  (0) 2018.02.16
Immutability helper  (0) 2018.02.14
sort / filter를 이용한 검색 소개  (0) 2018.02.06
react-hot-loader 소개  (0) 2018.02.05
Map() 소개  (0) 2018.02.01