React 와 Data

55546 단어 ReactReact

리액트와 데이터

지금까지는 컴포넌트를 하나만 사용해서 프로그램을 만들었다. 실제로 리액트는 컴포넌트를 여러 개 만들고, 이러한 컴포넌트를 조합하면서 프로그램을 만든다.

지금 보고있는 책(혼공스)는 리액트 책이 아니여서 기본적인 내용만 다룬다.

지금 파트에서는 여러 개의 컴포넌트를 선언하고, 리액트의 기본적인 내용만으로 컴포넌트끼리 커뮤니케이션하는 방법을 살펴본다. 다만 리액트 기본만으로 컴포넌트끼리 커뮤니케이션하게 만드는 것은 굉장히 복잡하다. Flux 패턴할 수 있게 해주는 라이브러리들을 사용하면 코드가 훨씬 깔끔해진다.

다만 이 책의 범위에서는 Flux 패턴까지 다루지는 않는다고 한다. 기본을 익히기 위한 고전적인 내용이라고 생각하면 될 것같다.

여러 개의 컴포넌트 사용하기

지금까지는 컴포넌트로 App 컴포넌트만 선언하고 활용했다. 이번에는 Item 컴포넌트를 추가로 만들어서 사용해보려고한다.

// 애플리케이션 클래스 생성하기
class App extends React.Component {
  render() {
    return <ul>
      <Item/>
      <Item/>
      <Item/>
    </ul>
  }
}

class Item extends React.Component {
  render() {
    return <li>Item 컴포넌트</li>
  }
}

// 출력하기
const container = document.getElementById('root')
ReactDOM.render(<App />, container)

실행하면

App 컴포넌트에서 Item 컴포넌트로 어떤 데이터를 전달하고 싶을 때는 컴포넌트의 속성을 사용한다. 다음은 App 컴포넌트에서 Item 컴포넌트로 value 속성을 전달하고, Item 컴포넌트에서 value 속성을 출력하는 예이다.

Item 컴포넌트에 속성 전달하기

// 애플리케이션 클래스 생성하기
class App extends React.Component {
  render() {
    return <ul>
      <Item value="Item 컴포넌트 1번" />
      <Item value="Item 컴포넌트 2번" />
      <Item value="Item 컴포넌트 3번" />
    </ul>
  }
}

class Item extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return <li>{this.props.value}</li>
  }
}

const container = document.getElementById('root')
ReactDOM.render(<App/>, container)

실행하면

부모에서 자식의 state 속성 변경하기

부모 컴포넌트에서 자식 컴포넌트로 어떤 데이터를 전달할 때는 속성(this.props)을 사용한다. 부모 컴포넌트에서 자식으로 어떤 데이터를 전달한 뒤 화면 내용을 변경할 때도 속성(this.props)를 사용한다.

다음 코드는 부모 컴포넌트에서 시간을 구하고, 이를 속성을 통해 자식 컴포넌트에게 전달하는 예이다. 이때 중요한 부분은 바로 componentDidUpdate() 메소드 부분이다.

부모에서 자식의 state 속성 변경하기

// 애플리케이션 클래스 생성하기
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      time: new Date()
    }
  }

  componentDidMount() {
    // 컴포넌트가 화면에 출력되었을 때
    this.timerId = setInterval(() => {
      this.setState({
        time: new Date()
      })
    }, 1000)
  }

  componentWillUnmount() {
    // 컴포넌트가 화면에서 제거될 때
    clearInterval(this.timerId)
  }

  render() {
    return <ul>
      <Item value={this.state.time.toLocaleString()} />
      <Item value={this.state.time.toLocaleString()} />
      <Item value={this.state.time.toLocaleString()} />
    </ul>
  }
}

class Item extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: props.value
    }
  }

  componentDidUpdate(prevProps) {
    if(prevProps.value !== this.props.value) {
      this.setState({
        value: this.props.value
      })
    }
  }

  render() {
    return <li>{this.state.value}</li>
  }
}

// 출력하기
const container = document.getElementById('root')
ReactDOM.render(<App/>, container)

실행하면

위와 같이 출력된다.

componentDidUpdate() 메소드는 컴포넌트에 변경이 발생했을 때 호출되는 메소드이다. 이를 오버라이드해서 사용하고 있는 것이다. componentDidUpdate() 메소드는 매개변수로 변경 이전의 속성(prevProps)이 들어온다. 이 속성 값과 현재 속성 값을 비교해서 변경이 있는 경우(다른 경우)에만 setState() 메소드를 호출해서 화면에 변경 사항을 출력한다. componentDidUpdate() 메소드 부분이 없으면 시간은 변하지 않는다.

componentDidUpdate() 메소드가 없어도 render() 메소드는 변경 사항이 있을 때 실행되므로 시간이 변경되어야 한다고 생각할 수도 있다. 그런데 render() 메소드는 단순하게 컴포넌트를 조합해서 문서 객체를 만든 뒤 화면에 출력하는 메소드가 아니다. 내부적으로 쓸데없는 변경 등을 막아 애플리케이션의 성능을 높일 수 있게 다양한 처리를 해준다. 그래서 이러한 패턴의 코드를 사용하는 것이다.

자식에서 부모의 state 속성 변경하기

반대로 자식 컴포넌트에서 부모 컴포넌트의 상태를 변경할 때는 메소드를 사용한다. 부모 컴포넌트에서 자신(부모)의 속성을 변경하는 메소드를 자식에게 전달한 뒤, 자식에서 이를 호출하게 만드는 것이다. 조금 복잡할 수도 있다.

// 애플리케이션 클래스 생성하기
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: ''
    }
    this.changeParent = this.changeParent.bind(this)
  }

  render() {
    return <div>
      <CustomInput onChange={this.changeParent} />
      <h1>{this.state.value}</h1>
    </div>
  }

  // 자신의 속성을 변경하는 메소드이다.
  // 내부에서 this 키워드를 사용하므로, this 바인드를 했다.
  changeParent(event) {
    this.setState({
      value: event.target.value
    })
  }
}

class CustomInput extends React.Component {
  // input 태그에 변경 사항이 있을 때, 부모로부터 전달받은 메소드를 호출한다.
  render() {
    return <div>
      <input onChange={this.props.onChange}/>
    </div>
  }
}

// 출력
const container = document.getElementById('root')
ReactDOM.render(<App/>, container)

실행하면

코드의 실행이 이리저리 이동해서 조금 복잡하다.

리액트로 만드는 할 일 목록 애플리케이션

이 책에 마지막 예제이다.

// 애플리케이션 클래스 생성하기
      class App extends React.Component {
        constructor (props) {
          super(props)
         
          // 지난 설정 불러오기
          this.state = this.load()

          // 메소드 바인드
          this.textChange = this.textChange.bind(this)
          this.textKeyDown = this.textKeyDown.bind(this)
          this.buttonClick = this.buttonClick.bind(this)
          this.removeItem = this.removeItem.bind(this)
          this.changeCheckData = this.changeCheckData.bind(this)
        }

        save () {
          localStorage.state = JSON.stringify(this.state)
        }

        load () {
          let output
          try { output = JSON.parse(localStorage.state)
          } catch (e) {}

          // 속성이 제대로 존재하는지 확인
          if (output !== undefined
            && output.keyCount !== undefined
            && output.currentValue !== undefined)
          {
            output = JSON.parse(localStorage.state)
          } else {
            output = {
              keyCount: 0,
              currentValue:'',
              todos: []
            }
          }
          return output
        } 

        componentDidUpdate () {
          this.save()
        }

        render () {
          return <div>
            <h1>할 일 목록</h1>
            <input
              value={this.state.currentValue}
              onChange={this.textChange}
              onKeyDown={this.textKeyDown} />
            <button onClick={this.buttonClick}>추가하기</button>
            <div>
              {this.state.todos.map((todo) => {
                return <TodoItem
                  dataKey={todo.key}
                  isDone={todo.isDone}
                  text={todo.text}
                  removeItem={this.removeItem}
                  changeCheckData={this.changeCheckData} />
              })}
            </div>
          </div>
        }

        textChange (event) {
          this.setState({
            currentValue: event.target.value
          })
        }

        // 입력양식에서 ENTER키를 입력했을 때도 버튼을 클릭한 것과 같은 효과를 낸다.
        textKeyDown (event) {
          const ENTER = 13
          if (event.keyCode === ENTER) {
            this.buttonClick()
          }
        }

        buttonClick (event) {
          if (this.state.currentValue.trim() !== '') {
            // 전개 연산자를 활용해서 기존의 배열을 복사하고, 뒤에 요소를 추가한다.
            // setState() 메소드를 호출하지 않으면 배열의 변경이 화면에 반영되지 않으므로,
            // 이런 코드를 사용한다.
            this.setState({
              todos: [...this.state.todos, {
                key: this.state.keyCount.toString(),
                isDone: false,
                text: this.state.currentValue
              }]
            })
            this.state.keyCount += 1
            this.state.currentValue = ''
          }
        }

        removeItem (key) {
          // filter() 메소드를 활용해서 기존의 배열에서 요소를 제거한다.
          this.setState({
            todos: this.state.todos.filter((todo) => {
              return todo.key !== key
            })
          })
        }

        changeCheckData (key, changed) {
          // 배열을 전개 연산자로 복사
          let target = [...this.state.todos]
          // 변경된 요소를 찾고, isDone 속성을 변경한다.
          target.filter((todo) => todo.key === key)[0].isDone = changed
          this.setState({
            todos: target
          })
        }
      }

      class TodoItem extends React.Component {
        constructor (props) {
          super(props)
          this.state = {
            isDone: props.isDone
          }
          this.checkboxClick = this.checkboxClick.bind(this)
        }

        render () {
          const textStyle = {}
          textStyle.textDecoration
            = this.state.isDone ? 'line-through' : ''
          return (
            <div style={textStyle}>
              <input
                type="checkbox"
                checked={this.state.isDone}
                onChange={this.checkboxClick} />
              <span>{this.props.text}</span>
              <button onClick={()=>this.props.removeItem(this.props.dataKey)}>제거
              </button>
            </div>
          )
        }

        checkboxClick () {
          const changed = !this.state.isDone
          this.props.changeCheckData(this.props.dataKey, changed)
        }
        componentDidUpdate (prevProps) {
          if (prevProps.isDone !== this.props.isDone) {
            this.setState({
              isDone: this.props.isDone
            })
          }
        }
      }

      // 출력하기
      const container = document.getElementById('root')
      ReactDOM.render(<App />, container)

실행하면

잘 출력된다.

좋은 웹페이지 즐겨찾기