Back Ground

React - 컴포넌트 반복 본문

Javascript/React.js

React - 컴포넌트 반복

Back 2018. 10. 28. 16:59



IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component }  from 'react';
class IterationSample extends Component{
    render(){
        return(
            <ul>
                <li>눈사람</li>
                <li>얼음</li>
                <li></li>
                <li>바람</li>
            </ul>
        );
    }
export default IterationSample;
cs


이런식으로 <li>...</li>를 연속으로 사용 해야 할때 반복 코드를 이용해서 작성해보도록 하겠다 




자바스크립트 배열의 map() 함수 


자바스크립트 배열 객체의 내장 함수인 map함수를 사용하여 반복되는 컴포넌트를 랜더링할 수 있다.

map 함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 프로세싱한 후 그 결과로 새로운 배열 생성한다.


1
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // new_array의 새 요소 반환 }[, thisArg])
cs

arr.map( callback() , [thisArg] ) 


map의 callback은 새로운 배열의 요소를 생성하는 함수가 있는데 

- currentValue : 현재 처리하고 있는 요소

- index : 현재 처리하고 있는 요소의 index값

- array : 현재 처리하고 있는 원본 배열


map의 thisArg은 (선택항목) callback 함수 내부에서 사용할 this 레퍼런스 


출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map






map 함수 사용 예

map함수를 사용하여 배열 [ 1, 2 , 3 , 4, 5 ]의 각 요소를 제곱해서 새로운 배열을 생성해 보자


1
2
3
4
5
6
7
var numbers = [1,2,3,4,5];
 
var processed = numbers.map(function(num){
    return num * num;
});
 
console.log(processed);
cs

그러면 배열 순서대로 num에 배열 값이 들어간다.


결과를 보면 

[ 1*1, 2*2, 3*3, 4*4, 5*5 ] 가 연산처리 된

[ 1, 4, 9, 16, 25] 가 나오게 된다.





데이터 배열을 컴포넌트 배열로 map 하기



IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component,Fragment } from 'react'
 
export default class IterationSample extends Component {
  
 render() {
     const names =['눈사람','얼음','눈','바람'];
     const nameList = names.map( 
         (name=> ( <li>{name}</li> ) 
     );
     console.log(nameList);
    
    return (
        <ul>
          {nameList}
        </ul>
    
    )
  }
}
 
cs

 


<li></li>의 JSX코드로 된 배열을 생성 한 후 nameList에 담는다


JSX는 { <li></li> } 가 아닌 (<li><li>)에 담아야한다. 

참고 : http://backback.tistory.com/296



후에  App.js 에 컴포넌트를 불러오게 되면 


 DOM에는 전혀 문제없어보이지만 개발자도구에보면 

'key' prop이 없다고 경고 메시지가 나오게 된다. 










Key


리액트에서 key는 컴포넌트 배열을 랜더링했을 때 어떤 원소에 변동이 있는지 알아내기 위해 사용.

key가 없을 때는 가상 DOM을 비교하는 과정에서 리스트를 순차적으로 비교 하여 변화를 감지해야 하는데

key가 있을 때는 이 값을 사용하여 어떤 변화가 일어났는지 더욱 빠르게 알아 낼수 있다.



key설정


key값을 설정할 떄는 map함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯하면 된다.

당연 key값은 유일해야 한다.


사용 예)

1
2
3
4
5
6
7
const articleList = articles.map(article => (
    <Article
        title={articles.title}
        writer={articles.writer}
        key={articles.id}
    />
);
cs



그러면 아까 전의 예제를 응용 해보자



IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component,Fragment } from 'react'
 
export default class IterationSample extends Component {
  
 render() {
     const names =['눈사람','얼음','눈','바람'];
     const nameList = names.map( 
          (name,index=> ( <li key={index}>{name</li>
     );
     console.log(nameList);
    
    return (
        <ul>
          {nameList}
        </ul>
    
    )
  }
}
cs




그러면 배열을 state에 초기 데이터 담아서 응용 해보자


IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component,Fragment } from 'react'
 
export default class IterationSample extends Component {
  
  state = { 
    names : ['눈사람','얼음','눈','바람'],
  }
  
  render() {
    const nameList = this.state.names.map( (name,index) => (<li key={index}>{name}</li>) ); 
    return (     
      <ul>
          {nameList}
      </ul>
    )
  }
}
 
cs





데이터 추가 기능 


state names의 배열에 데이터를 추가하는 기능을 만들어보자 


IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { Component,Fragment } from 'react'
export default class IterationSample extends Component {
  
  state = { 
    names : ['눈사람','얼음','눈','바람'],
    name : '',
  }
  
  handleChange = (e) =>{
    this.setState({
      name : e.target.value
    });
  }
  handleInsert = () =>{
    this.setState({
      names : this.state.names.concat(this.state.name),
      name:'',
    });
  }
  render() {
    const nameList = this.state.names.map( (name,index) => (<li key={index}>{name}</li>) ); 
    return (
      <Fragment>
        <br/><br/>
        <input
          onChange={this.handleChange}
          value={this.state.name}
        />
        <button onClick={this.handleInsert}>추가</button>
        <ul>
          {nameList}
        </ul>
      </Fragment>
    )
  }
}
 
cs


input값은 기본 값이 공백으로 state에 저장했고,

input과 button에서 필요한 이벤트 핸들러 메서드를 준비하여 각 요소에 이벤트를 설정



1
2
3
4
5
6
  handleInsert = () =>{
    this.setState({
      names : this.state.names.concat(this.state.name),
      name:'',
    });
  }
cs

이 메소드에서 데이터를 추가하는 방식으로 하는 이유는

기존의 자바스크립트에서는 this.state.names.push('...')로 배열을 수정해도 되지 않나 싶은데


state는 언제나 setState메서드로 업데이트해야 하고 직접 접근하여 수정하면 안된다 !

그렇기 때문에 push같은 함수를 쓴다면 기존 배열 자체가 변형되므로 이는 잘못된 사용이다

( 자동으로 리렌더링을 트리거하지 않기 때문 )


여기선 기존 배열과 새 값을 합치는 concat을 이용하여 사용하면 오류없이 작동한다.







데이터 제거 기능 


더블 클릭 이벤트 발생 시 데이터를 지워보겠다.


IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import React, { Component,Fragment } from 'react'
 
export default class IterationSample extends Component {
  
  state = { 
    names : ['눈사람','얼음','눈','바람'],
    name : '',
  }
  
  handleChange = (e) =>{
    this.setState({
      name : e.target.value
    });
  }
 
  handleInsert = () =>{
    this.setState({
      names : this.state.names.concat(this.state.name),
      name:'',
    });
  }
 
  handleRemove = (index) =>{
    
    const {names} = this.state;
 
    
    this.setState({
      names: [
        ...names.slice(0,index),
        ...names.slice(index + 1, names.length),
      ]
    });
  }
 
 
 
  render() {
 
    const nameList = this.state.names.map(
        (name,index) => (
          <li key={index}
              onDoubleClick={() => this.handleRemove(index)}
          >
          {name}</li>
          ) 
      ); 
    return (
      <Fragment>
        <br/><br/>
        <input
          onChange={this.handleChange}
          value={this.state.name}
        />
        <button onClick={this.handleInsert}>추가</button>
        <ul>
          {nameList}
        </ul>
      </Fragment>
    )
  }
}
 
cs



여기 지우는 이벤트 메소드에서 보면

1
2
3
4
5
6
7
8
9
10
handleRemove = (index) =>{
    const {names} = this.state;    
    this.setState({
      names: [
        ...names.slice(0,index),
        ...names.slice(index + 1, names.length),
      ]
    });
  }
 
cs


일단 

1
const {names} = this.state;
cs

이 부분이 익숙하지 않을 수 있으니 ES5 방식으로 하자면 

1
var names = this.state.names;
cs
이런식으로 지정 해둔 것 이다. 익숙하지 않을 수 있기에 한번 더 설명하고 넘어간다.


names를 업데이트 해줄건데 
...names는 ES6 전개 연산자 문법인데 

하는 역할은 ...뒤에 위치한 배열 값을 그대로 꺼내서 현재 배열에 복사하는 것이다.

이 부분인데
1
2
3
4
names: [
    ...names.slice(0,index),
    ...names.slice(index + 1, names.length),
]
cs

.slice()함수는 배열의 범위값을 구해주는것인데 

arr.slice(begin, end) 

slice( 시작 index 부터 , 끝 index 전까지)



ES5 방식

1
2
const numbers = [1,2,3,4,5];
const moreNumbers = [ ...numbers, 6 ]; //[1,2,3,4,5,6]
cs
즉, ES5로 생각하면 이것과 같다.

참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/slice




코드에서는

.slice(0,index)

0부터 선택한 index전까지 원소들을 가진 새 배열을 만든다.

또 index + 1부터 마지막까지 원소들을 가진 새 배열을 만든 후, 


전개 연산자를 사용하여 배열 하나로 합쳐준다.

여기에서 전개 연사자를 사용한 이유는 소스 코드의 간결함과 가독성 때문이다

전개연사자를 사용하지 않았다면 slice한 배열을 concat했어야 하는데 그러면 코드가 너무 길다.


ES5 방식

1
2
3
this.state.names.slice(0,index).concat(
      this.state.names.slice(index+1this.state.names.length)
cs


동작

예 ) names = [ 0, 1, 2, 3, 4, 5, 6 ]

.slice( 0 , index) // index = 4

=> [0,1,2,3]


.slice(index + 1 , this.state.names.length) (5 , 7) 

=> [0,1,2,3]  + [ 5,6 ]  = [0,1,2,3,5,6]



전개 연산자를 쓰면 저렇게 간결하게 바뀌게 된다.




삭제 함수를 더 간단하게 작성하고 싶다면 

filter를 사용한다.


IterationSample.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import React, { Component,Fragment } from 'react'
 
export default class IterationSample extends Component {
  
  state = { 
    names : ['눈사람','얼음','눈','바람'],
    name : '',
  }
  
  handleChange = (e) =>{
    this.setState({
      name : e.target.value
    });
  }
 
  handleInsert = () =>{
    this.setState({
      names : this.state.names.concat(this.state.name),
      name:'',
    });
  }
 
  handleRemove = (index) =>{
    const {names} = this.state;
    /*
    this.setState({
      names: [
        
        //ES6 전개 연산자
        // ...names.slice(0,index),
        // ...names.slice(index + 1, names.length),
        
        //ES5
        // this.state.names.slice(0,index).concat(
        //      this.state.names.slice(index+1, this.state.names.length)
        // ) 
      ]
    });
    */
    // 필터링
    this.setState({
      //filter로 index번째를 제외한 원소만 있는 새 배열 생성
      names: names.filter( (item, i)=> i !== index )
    });    
  }
 
 
 
  render() {
 
    const nameList = this.state.names.map(
        (name,index) => (
          <li key={index}
              onDoubleClick={() => this.handleRemove(index)}
          >
          {name}</li>
          ) 
      ); 
    return (
      <Fragment>
        <br/><br/>
        <input
          onChange={this.handleChange}
          value={this.state.name}
        />
        <button onClick={this.handleInsert}>추가</button>
        <ul>
          {nameList}
        </ul>
      </Fragment>
    )
  }
}
 
cs





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

React - 함수형 컴포넌트  (0) 2018.11.01
React - 라이프 사이클  (1) 2018.10.31
React - ( <mark up> ) 표현  (0) 2018.10.27
React - ref : DOM에 이름 달기  (0) 2018.10.20
React - 이벤트 핸들링  (0) 2018.10.20
Comments