리액트 교과서 Chapter08(확장성을 고려한 React 컴포넌트)

기본속성

컴포넌트의 개발 시 모든 속성을 주입 받을 시 누락 된다면 해당 컴포넌트는 동작 하지 않을 것 이다.
또한 매번 컴포넌트 사용 시 모든 옵션들을 주입 받는 것 또한 코드가 복잡해진다.

이를 해결하기 위해 컴포넌트 작성 시 기본속성을 작성할 수 있다.


class HelloWorld extends React.Component {
  constructor(prop) {
    super(prop)
  }

  render() {
    return (
      <div>
        <p >{this.props.title}</p>
      </div>
    )
  }
}

HelloWorld.defaultProps = {
  title: 'Hello World'
}


const el =document.getElementById('content')
var root = ReactDOM.createRoot(el)

root.render(
  <div>
    <HelloWorld title="Hello"></HelloWorld>
    <HelloWorld></HelloWorld>
  </div>
)

 

자식컴포넌트 랜더링

컴포넌트에 {this.props.children}를 사용하여 해당 컴포넌트 내부에 있는 자식 컴포넌트를 렌더링 할 수 있다.

// 자식 엘리먼트 렌더링
class Content extends React.Component {
  constructor(prop) {
    super(prop)
  }

  render() {
    return (
      <div>
        <p >{this.props.title}</p>
        {this.props.children}
      </div>
    )
  }
}

Content.defaultProps = {
  title: 'Content!!'
}
//

const el =document.getElementById('content')
var root = ReactDOM.createRoot(el)

root.render(
  <div>
    <HelloWorld title="Hello"></HelloWorld>
    <HelloWorld></HelloWorld>

    <Content>
      <h1>Content Hi</h1>
    </Content>

    <></>
  </div>

 

펼침 연산자의 사용

모든 주입 받은 속성을 펼침 연산자를 사용하여 전달한다.

 render() {
    return (
      <a {...this.props} {...this.state}>네이버</a>
    )
  }

 

고차 컴포넌트

고차 컴포넌트는 함수의 파라미터로 리액트 컴포넌트를 전달 받아 사용하는 컴포넌트를 말한다.
근래에는 함수형 컴포넌트의 훅(hook)을 사용하기 때문에 많이 사용하지 않는다.

 

고차컴포넌트에 사용할 컴포넌트(내부 컴포넌트) 정의

Elements.jsx

class HiButton extends React.Component {
  render() {
    return (
      <button
        onClick={this.props.handleClick}
      >{this.props.label}</button>
    )
  }
}

class HiLink extends React.Component {
  render() {
    return (
      <a
        onClick={this.props.handleClick}
        href="#"
      >{this.props.label}</a>
    )
  }
}

class Logo extends React.Component {
  render() {
    return (
      <a
        onClick={this.props.handleClick}
        href="#"
      >{this.props.label}</a>
    )
  }
}

 

고차 컴포넌트(외부 컴포넌트) 함수 정의
load-website.jsx

// 컴포넌트를 파라미터로 받음
const LoadWebsite = (Component) => {
  class _LoadWebsite extends React.Component {
    constructor(props) {
      super(props)
      // 상태 생성
      this.state = {label: 'Run', handleClick: this.handleClick.bind(this)} 
    }
    // 함수생성
    getUrl() {
      return 'https://facebook.github.io/react/docs/top-level-api.html'
    }
    handleClick(event) {
      document.getElementById('frame').src = this.getUrl()
    }
    componentDidMount() {
      console.log(ReactDOM.findDOMNode(this))
    }
    render() {
      console.log(this.state)
      // 입력받은 컴포넌트 렌더링
      // 상위 컴포넌트의 상태를 하위 컴포넌트의 속성으로 사용
      return <Component {...this.state} {...this.props} />
    }
  }
  _LoadWebsite.displayName = 'EhnancedComponent'

  return _LoadWebsite
}

 

고차 컴포넌트의 사용
HiorContent.jsx

// 고차 컴포넌트를 생성하는 함수의 파라미터로 내부 컴포넌트 정의
let EnhancedButton = LoadWebsite(HiButton)
let EnhancedLink = LoadWebsite(HiLink)
let EnhancedLogo = LoadWebsite(Logo)

class HiOrContent extends React.Component {
  render() {
    return (
      <div>
        <EnhancedButton />
        <br />
        <br />
        <EnhancedLink />
        <br />
        <br />
        <EnhancedLogo />
        <br />
        <br />
        <iframe id="frame" src="" width="600" height="500"/>
      </div>
    )
  }
}

let el2 =document.getElementById('hi-content')
var root = ReactDOM.createRoot(el2)

root.render(
  <div>
    <HiOrContent> </HiOrContent>
  </div>
)

 

프레젠테이션 컴포넌트와 컨테이너 컴포넌트

데이터를 보여주는 컴포넌트와 데이터를 서버로 부터 불러오는 컴포는트를 분리하여 작성하는 것이
간결한 소스와 유지보수에 유용하다

 


https://github.com/Kmmanki/react_note/tree/master/chapter08

 

 

GitHub - Kmmanki/react_note: 리액트 교과서 따라하기

리액트 교과서 따라하기. Contribute to Kmmanki/react_note development by creating an account on GitHub.

github.com

 

 

리액트 교과서 Chapter07(React에서 폼 다루기)

제어 컴포넌트

onChange를 사용하여 event의 value를 setState로 상태 변경

class HelloWorld extends React.Component {
  constructor(prop) {
    super(prop)
    this.state = {
      userName: '',
      userNum: ''
    }
    this.changeUserName = this.changeUserName.bind(this)
    this.changeUserNum = this.changeUserNum.bind(this)
    this.submitBtn = this.submitBtn.bind(this)
  }

  changeUserName (event) {
    this.setState({
      userName: event.target.value
    })
  }

  changeUserNum (event) {
    let newVal = event.target.value.replace(/[^0-9]/ig, '')

    this.setState({
      userNum: newVal
    })
  }

  submitBtn (event) {
    console.log(this.state)
  }

  render() {
    return (
      <div>
        <input value={this.state.userName} onChange={this.changeUserName} ></input>
        <br></br>
        <input value={this.state.userNum} onChange={this.changeUserNum} ></input>
        <br></br>
        <button onClick={this.submitBtn}>submit</button>
      </div>
    )
  }
}

 

리스트의 Object 변경

class RadioComp extends React.Component {
  constructor(prop) {
    super(prop)
    this.state = {
      checkboxGroup:[
        {
          item: 'node',
          checked : false
        },
        {
          item: 'react',
          checked : false
        },
        {
          item: 'express',
          checked : false
        },
        {
          item: 'mongoDb',
          checked : false
        }
      ] 
    }
    this.changeRadio = this.changeRadio.bind(this)
  }

  changeRadio (event) {
    console.log(event)
    // 값중 일부를 변경하기 위해 객채를 복제
    let obj = Object.assign(this.state.checkboxGroup)

    // 모든 체크를 비활성화
    obj.forEach(element => {
      element.checked = false
    });

    // 입력받은 값만 checked를 true로 변경
    let idx = obj.findIndex((element, index, array) => element.item === event.target.value)
    obj[idx] = {item: event.target.value, checked: event.target.checked}

    // checkboxGroup를 변경
    this.setState({
      checkboxGroup: obj
    })
  }

  render() {
    return (
      <div>
        {
          this.state.checkboxGroup.map((checkbox, index) => {
            return (
              <div key={index}>
                <label>{checkbox.item}</label>
                <input type="radio" value={checkbox.item} checked={checkbox.checked} onChange={this.changeRadio} ></input>
              </div>
            )
          })

        }
        <button  onClick={this.submitBtn}>submit</button>
      </div>
    )
  }
}

https://github.com/Kmmanki/react_note/tree/master/chapter07

 

 

GitHub - Kmmanki/react_note: 리액트 교과서 따라하기

리액트 교과서 따라하기. Contribute to Kmmanki/react_note development by creating an account on GitHub.

github.com

 

 

리액트 교과서 Chapter06(React에서 이벤트 다루기)

이벤트 등록을 하는 방법

함수에 현재 컴포넌트를 bind 해주어야한다.
해당 바인드를 하지 않는 경우는 아래와 같다.

  • 함수에서 Component 인스턴스를 사용하지 않을 때
  • Arrow 함수를 사용 할 때
class HelloWorld extends React.Component {
  constructor(prop) {
    super(prop)
    this.state = {
      count : 0
    }
    // 바인드 방식 1
    this.minusBtn = this.minusBtn.bind(this)
  }
  plusBtn (event) {
    this.setState({
      count: ++this.state.count
    })
  }
  minusBtn (event) {
    this.setState({
      count: --this.state.count
    })
  }
  render() {
    return (
      <div>
        <span>{this.state.count}</span>
        // 바인드 방식 2
        <button onClick={this.plusBtn.bind(this)} >+</button>
        <button onClick={this.minusBtn} >-</button>
        <button onClick={(event) => {
          this.setState({
            count: 0
          })
          }} >clear</button>
      </div>
    )
  }
}

 

합성이벤트

minusBtn (event)함수에서 event가 합성이벤트이다.
event는 표준화를 위한 React 객체다.
내부 프로퍼티 및 함수는 아래와 같다.

  • currentTarget: 이벤트를 캡쳐한 요소의 DOMEventTarget(대상 요소 및 부모요소)
  • target: DOMEventTarget가 발생한 요소
  • preventDefault(): 링크 나 폼 전송 등 기본 동작을 방지하는 메소드
  • isDefaultPrevented(): 기본동작이 방지 되었을 때 True를 반환
  • stopPropagation(): 이벤트 전파 중단
  • isPropagationStopped: 이벤트 전파가 중단되었다면 true 반환
  • type: 태그명 문자열
  • persist(): 합성 이벤트를 이벤트 풀에서 꺼낸 후 사용자 코드에서 이벤트에 대한 참조를 유지할 수 있도록 한다.
  • isPersistent(): 합성 이벤트를 이벤트 풀에서 꺼낸경우 true를 반환

입력이 된 텍스트 상자의 value를 가져오는 방법

...

getNameInputValue(event) {

console.log(e.target.value)

}

...

 

 

컴포넌트간 통신

컴포넌트간 데이터의 변경을 주고 받아야 한다면 부모 컴포넌트에서 상태를 관리하고 하위에는 변경에 대한 메소드
변경된 값들을 전달하는 것이 좋은 패턴이다.

  1. parent -> content로 상태 전달
  2. parent -> btn으로 함수전달
  3. btn -> parent 함수 호출
  4. parent -> content 변경된 상태 전달
class Parent extends React.Component {
  constructor(prop) {
    super(prop)
    this.state = {
      counter: 0
    }
  }
  updateCounter(e) {
    console.log('wow')
    this.setState({counter : ++this.state.counter})
  }
  render() {
    return <div>
      <ChildContent counter={this.state.counter}></ChildContent>
      <ChildBtn updateCounter={this.updateCounter.bind(this)}></ChildBtn>
    </div>
  }
}
class ChildContent extends React.Component {

  render() {
    return <span>{this.props.counter}</span>
  }
}
class ChildBtn extends React.Component {
  render() {
    console.log(this.props)
    return <button onClick={this.props.updateCounter}>+</button>
  }
}

 

React가 지원하지 않는 이벤트 처리

resize 이벤트는 React에서 지원하지 않는다.
이를 해결하기 위해 라이프 사이클 메소드를 사용한다.

// 동작하지 않음
return <div onResize={this.handleResize}></div>

 

동작

  1. window에 이벤트 리스너 등록
  2. 화면의 크기가 변동되면 상태 갱신
  3. 상태 갱신으로 인한 동적 style 갱신
class Radio extends React.Component {
  constructor(prop) {
    super(prop)
    this.handleResize = this.handleResize.bind(this)
    let i = 1
    this.state = {
      outerStyle: this.getStyle(4, i),
      innerStyle: this.getStyle(1, i),
      selectedStyle: this.getStyle(2, i),
      taggerStyle: {top: 20, width: 25, height: 25}
    }
  }
  getStyle (i, m){
    let value = i * m
    return { top: value, bottom: value, left: value, right: value}
  }
  
  componentDidMount() {
    window.addEventListener('resize', this.handleResize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize)
  }
  handleResize() {
    let w = 1+ Math.round(window.innerWidth / 300)
    this.setState({
      taggerStyle: {top: w*10, width : w*10, height: w*10 },
      textStyle: {left: w*13, fontSize: 7*w}
    })
  }

  render() {
    return <div>
      <div className="radio-tagger" style={this.state.taggerStyle}>
        <input type="radio" name={this.props.name} id={this.props.id} />
        <label htmlFor={this.props.id}>
            <div className="radio-text" style={this.state.textStyle}>{this.props.label}</div>
            <div className="radio-outer" style={this.state.outerStyle}>
              <div className="radio-inner" style={this.state.innerStyle}>
                <div className="radio-selected" style={this.state.selectedStyle}></div>
              </div>
            </div>
        </label>
      </div>
    </div>
  }
}

리액트 교과서 chapter05
(React 컴포넌트의 라이프사이클 이벤트)

React는 라이브 사이클 이벤트를 기반으로 컴포넌트의 동작을 제어하고 사용자 정의 할 수 있다.

  • 마운팅 이벤트: React 엘리먼트를 DOM노드에 추가할 때 발생
  • 갱신이벤트: 속성이나 상태가 변경되어 React 엘리머트를 갱신할 때 발생
  • 언마운팅 이벤트 : React 엘리먼트가 DOM에서 제거될 때 발생

마운팅과 언마운팅의 경우 DOM의 추가, 삭제 과정에 동작하므로 1번만 동작하며, 갱신은 상태의 변경에 따라 동작하기 때문에 여러번 동작 될 수 있다.

  • contructor: 상태의 초기화 시 사용 된다.
  • 마운팅
    • componentWillMount(): DOM에 삽입 전에 실행된다.
    • componentDidMount(): DOM에 삽입 후에 실행된다.
  • 갱신
    • componentWillReceiveProps(nextProps): 컴포넌트가 속성을 받기 직전에 실행
    • shoudComponentUpdate(nextProps, nextState): 컴포넌트가 갱신되는 조건을 정의해서 재랜더링을 최적화 할 수 있다. boolean 값을 반환
    • componentWillUpdate(nextProps, nextState): 컴포넌트가 갱신되기 직전에 실행된다.
    • componentDidUpdate(prevProps, prevState): 컴포넌트가 갱신된 후에 실행된다.
  • 언마운팅
    • componentWillUnmount(): DOM에서 제거하기 전 실행되며, 구독한 이벤트를 제거하거나 다른 정리작업에 수행 할 수 있다.

 

라이프 사이클의 사용 및 관찰

class Clock extends React.Component {

  constructor(props) {
    super(props)
    this.launchClock()
    this.state = {
      currentTime : (new Date()).toLocaleString('kr'),
      testState : 'testState',
      counter : 0 
   }
  }

  launchClock() {
    setInterval(() => {
      this.setState({
        currentTime: (new Date()).toLocaleString('kr'),
        counter: ++this.state.counter
      })
    }, 1000)
  }

  render () {
    if (this.state.counter > 2) {
      return <Logger time={this.state.currentTime}></Logger>
    }
    }
}

class Logger extends React.Component {
  constructor (props) {
    super(props)
    console.log('constructor 실행')
  }
  componentWillUnmount() {
    console.log('componentWillUnmount 실행')
  }
  componentDidMount() {
    console.log('componentDidMount 실행')
  }
  componentWillReceiveProps(nextProps) {
    console.log('componentWillReceiveProps 실행')
    console.log('새로운 속성', nextProps)
  }
  shoudComponentUpdate(nextProps, nextState) {
    console.log('shoudComponentUpdate 실행')
    console.log('새로운 속성', nextProps)
    console.log('새로운 상태', nextState)

  }
  componentWillUpdate(nextProps, nextState){
    console.log('componentWillUpdate 실행')
    console.log('새로운 속성', nextProps)
    console.log('새로운 상태', nextState)
  }
  componentDidUpdate(prevProps, prevState){
    console.log('componentDidUpdate 실행')
    console.log('이전 속성', prevProps)
    console.log('이전 상태', prevState)
  }
  componentWillUnmount(){
    console.log('componentWillUnmount 실행')
  }
  render () {
    return <div>{this.props.time}</div>
  }
} 

 

componentDidMount의 활용

기본적인 템플릿이 mount 된 뒤 데이터 조회

  class UserList extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        userList : [{id: '1', username:'1'}]
      }
    }
      componentDidMount() {
        this.getUserList()
      }

      getUserList() {
        fetch('https://jsonplaceholder.typicode.com/users/')
        .then((response) => {return response.json()})
        .then((users) => {
          this.setState({userList : users})
        })
      }

      render() {
        return <div>
          <table>
            <tbody>
              {this.state.userList
              .map((user) => 
                <tr key={user.id} id={user.id}>
                  <td>{user.username}</td>
                  <td>{user.email}</td>
                </tr>
              )}
            </tbody>
          </table>
        </div>

      }
  }

 


https://github.com/Kmmanki/react_note/tree/master/chapter05

 

 

GitHub - Kmmanki/react_note: 리액트 교과서 따라하기

리액트 교과서 따라하기. Contribute to Kmmanki/react_note development by creating an account on GitHub.

github.com

 

 

 

+ Recent posts