REACT 5

PARK·2021년 1월 13일
0

REACT Tutorial Beginners

목록 보기
5/6
post-thumbnail

블로그 내용은
Programming with Mosh React Tutorial for Beginners [React js]
을 공부하는 과정에서 작성되었습니다.

<작성 코드>

import React, { Component, Fragment } from 'react';

class Counter extends Component {
    state = {
        value : this.props.counter.value,
    };
    render() { 
        return (
            <div> 
                <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
                <button onClick={this.handleIncrement} className="btn btn-secondary btn-sm">Increment</button>
                <button onClick={() => this.props.onDelete(this.props.counter.id)} className="btn btn-danger btn-sm m-2">Delete</button>
            </div>
        );
    }
    getBadgeClasses() {
        let classes = "badge m-2 badge-";
        classes += this.state.value === 0 ? "warning" : "primary"; //엘로우 블루
        return classes;
    }
    formatCount() {
        const {value} = this.state; 
        const x = "ZERO";
        return value === 0 ? x : value;
    }
    handleIncrement = () => {    
        console.log("Increment Clicked", this);  
        this.setState({value: this.state.value + 1});
    }
}
export default Counter;
//Counters
import React, { Component } from 'react';
import Counter  from './counter';

class Counters extends Component {
    state = { 
        counters: [
            {id:1, value: 1},
            {id:2, value: 0},
            {id:3, value: 0},
            {id:4, value: 0}
        ]
     };
    render() { 
        return (<div>
            { this.state.counters.map(counter => 
            <Counter 
            key={counter.id} 
            onDelete = {this.handleDelete}
            counter = {counter} />) }
        </div>);
        
    }
    handleDelete = counterId => {
        const counters = this.state.counters.filter(c =>c.id !== counterId );
        this.setState({counters:counters}); //key = value 같을때 counters로 쓸수잇음
    }
}
 
export default Counters;

Single Source of Thuth

이제 버튼을 누르면 카트 내용이 리셋되는 버튼을 만들어 봅시다.
Counters를 다음과 같이 편집합니다.

 handleReset = () => {
        const counters = this.state.counters.map(c => {
            c.value = 0;
            return c;
        })
        this.setState({counters})
}

counters에 handleReset 메서드를 추가합시다. value값을 모두 0으로 초기화 시켜 줍니다.

     <button className="btn btn-primary btn-sm m-2" onClick ={this.handleReset}>
     Reset</button>

render에는 버튼을 추가하고 handleReset을 연결해줍니다.
자, 프로그램을 돌려보면 실제로 개발자도구를 통해 값을 확인하면 value값은 0이되지만 어플리케이션에는 반영이 안되는 모습을 확인하실겁니다. 이유는 다음과 같습니다.

Counters에도 value가 있고 Counter에도 value가 있습니다. 그것도 state로 저장되어 있습니다. state는 처음으로 컴포넌트가 만들어질 때 초기화됩니다. 우리는 handReset을 통해 Counters의 value를 초기화하는 것이지 Counter의 value를 초기화하지 못합니다.(하기 위해서는 당연히 Counter에서 setState를 사용해야합니다.) 이러한 상황을 보고 Single Source of Thuth라고 합니다. 소스가 단 '하나'여야 된다는 말입니다. 부모인 Counters에서 아래로 뿌리는 형태인 하향식 데이터 흐름을 만들어서 문제를 해결해 봅시다.

Removing the Local State

state = {
        value : this.props.counter.value,
    };  //삭제

counter에서 state를 삭제합니다. 그리고 this.props.counter을 사용하여 모두 대체합니다.

handleIncrement = () => {    
        console.log("Increment Clicked", this);  
        this.setState({value: this.state.value + 1});
    }

handleIncrement는 보시면 알겠지만 setState를 통해 본인의 value에 접근하고 있습니다. 하지만 우리는 부모의 value를 접근해야하기 때문에 자식인 counter에서는 더 이상 접근할 수 없습니다. 따라서 이 메서드는 부모에 다시 만들어 줍시다.

//Counter의 Incement버튼
onClick={() => this.props.onIncrement(this.props.counter)
//counters의 render에 추가
onIncrement = {this.handIncrement}

Counter는 부모로부터 onIncrement을 받고 this.props.counter을 이벤트를 통해 위로 올려보냅니다.

//counters
 handIncrement = (counter) => {
        1 const counters = [...this.state.counters];
        2 const index = counters.indexOf(counter);
        3 counters[index] = {...counter };
        4 counters[index].value++; 
        5 this.setState({counters});
    }

부모에 메서드를 추가합니다. 코드가 꽤 복잡합니다
1.전개연산자를 통해서 const counters에 똑같은 배열을 저장하고 2. indexOf를 통해서 자식으로부터 온 index값을 찾는 모습입니다.
3. 그 다음은 찾은 인덱스를 이용해서 해당 배열을 변경해주고
4. 값을 늘려주고
5. 마지막으로 state에 저장하는 모습입니다.

Multiple Components in Sync

이제 애플리케이션에 NavBar를 만들려고합니다. 선택한 품목을 더해주는 바입니다. Components에 Navbar을 생성하고 작성해 줍시다.

import React, { Component } from 'react';
class Navbar extends Component {
    	render() { 
    	return ( 
        <nav className="navbar navbar-light bg-light">
        <div className="container-fluid"><a className="navbar-brand" href="#">Navbar</a></div>
        </nav> );
    }
}
export default Navbar ;

이제 우리는 다음 사진과 같은 형태에 트리를 생성할 것입니다.

Lifting the State UP

대대적인 작업이 필요합니다. 우리가 앞서 살펴봤지만 맨 위에 있는 부모에서 아래로 내려가는 하향식 데이터의 흐름을 구현해야 합니다. 그렇지 않으면 state로 인해 event가 작동할 시, 우리가 원하는 대로 컴포넌트가 작동하지 못합니다. 또 NavBar에서 사용하기 위해서도 역시 부모인 App에서 내려줘야 합니다.

//App
class App extends Component {
  state = { 
    counters: [
        {id:1, value: 1},
        {id:2, value: 0},
        {id:3, value: 0},
        {id:4, value: 0}
    ]
 };
 handleDelete = counterId => {
    const counters = this.state.counters.filter(c =>c.id !== counterId );
    this.setState({counters:counters}); //key = value 같을때 counters로 쓸수잇음
}
handleReset = () => {
    const counters = this.state.counters.map(c => {
        c.value = 0;
        return c;
    })
    this.setState({counters})
}
handleIncrement = counter => {
    const counters = [...this.state.counters];
    const index = counters.indexOf(counter);
    counters[index] = { ...counter };
    counters[index].value++; 
    this.setState({counters});
}
    render() { 
    return (
      <Fragment>
        <Navbar totalCounters = {this.state.counters}/>
        <main className = "container">
          <Counters
          onDelete = {this.handleDelete}
          onIncrement = {this.handleIncrement}
          onReset = {this.handleReset}
          counters = {this.state.counters}
          />
        </main>
      </Fragment>
      );
  }
 }
//Counters
class Counters extends Component {
    render() { 
        return (
        <div>
            <button className="btn btn-primary btn-sm m-2" onClick ={this.props.onReset}>
            Reset</button>
            { this.props.counters.map(counter => 
            <Counter 
            key={counter.id} 
            onDelete = {this.props.onDelete}
            onIncrement = {this.props.onIncrement}
            counter = {counter} />) }
        </div>);     
    }
}

그리고 이제 카운트를 위해 만든 Navbar를 구현할 코드를 작성합시다

<Navbar totalCounters = {this.state.counters.filter(c => c.value > 0).length}/>

함수를 props로 넘기는 것이 아니라 바로 넘겼습니다.

<nav className="navbar navbar-light bg-light">
        <div className="container-fluid"><a className="navbar-brand" href="#">
           Navbar <span className="badge badge-pill badge-secondary">{ this.props.totalCounters}</span></a>
        </div>
        </nav>

Navbar에서는 totalCounters를 받아왔습니다.

여기까지 우리가 원했던 카트를 만들어봤습니다.
다음엔 코드를 보완하고 lifehooks에 대해 알아봅시다.

참고자료
https://ko.reactjs.org/docs/getting-started.html

https://www.youtube.com/watch?v=Ke90Tje7VS0&t=916s

https://ttum.tistory.com/15

profile
익숙한 것에 작별을 고해야한다

0개의 댓글