리액트 네이티브 - Ⅱ

아현·2021년 3월 9일
0

React Native

목록 보기
3/7

2장 리액트 이해하기


1. state를 사용해 컴포넌트 데이터 다루기

리액트와 리액트 컴포넌트에서는 데이터를 만들고 다루는 방식 중 하나로 상태(state)를 사용합니다.

  • 컴포넌트의 state는 컴포넌트가 생성될 때 선언됩니다.
  • state는 setState 함수 호출을 통해 컴포넌트 내에서 갱신됩니다.

데이터를 다루는 또 다른 방식으로는 속성(props) 을 사용합니다.



컴포넌트의 상태 제대로 조작하기

  • 상태(state)는 컴포넌트가 다루는 값들의 집합체(collection)입니다.

  • 리액트에서는 UI를 state를 다루는 기계장치로 봅니다.

  • 컴포넌트가 setState 함수를 이용해서 state를 변경하게 되면 리액트는 컴포넌트를 다시 랜더링하게 됩니다.

  • 만일 자식 컴포넌트가 속성(props)으로 부모 컴포넌트 상태(state)를 받아서 사용하게 된다면 부모 컴포넌트의 상태(state)가 변경되어 자식 컴포넌트 역시 모두 다시 렌더링 됩니다.

  • state의 작동방식을 이해하는 것이 중요합니다. state에 의해서 상태를 유지하는 stateful 컴포넌트의 랜더링과 동작이 결정됙 때문입니다.

    • 컴포넌트의 state는 컴포넌트로 하여금 동적이고 인터랙티브(interactive)하게 해줍니다.
    • 상태(state)변경 가능(mutable)하다
    • 속성(props)변경 불가능(immutable)하다



초기 state 지정하기

  • 컴포넌트의 상태(state) 는 컴포넌트가 생성될 때 생성자나 속성 초기화를 이용해서 초기화됩니다.
  • 한 번 초기화된 state는 컴포넌트 내에서 this.state.를 통해서 사용할 수 있습니다.

<속성 초기화로 state 지정하기>


    import React from  'react'
    
    class MyComponent extends React.Component{
    	state={
            year: 2016,
            name: 'Nader Dabit',
            colors: ['blue']
        }

	render(){
           return(
               <View>
                   <Text>My name is: {this.state.name}</Text>
               	   <Text>The year is: {this.state.year}</Text>
                   <Text>My colors are: {this.state.colors[0]}</Text>
               </View>
            )
        }
    }
    
    

<생성자로 state 지정하기>


    import React, {Component} from  'react'
    
    class MyComponent extends Component{
        constructor(){	
      	  super()
          this.state={
              year: 2016,
              name: 'Nader Dabit',
              colors: ['blue']
          }
        }

	render(){
           return(
               <View>
                   <Text>My name is: {this.state.name}</Text>
               	   <Text>The year is: {this.state.year}</Text>
                   <Text>My colors are: {this.state.colors[0]}</Text>
               </View>
            )
        }
    }
    
    



상태(state) 갱신하기

  • 상태(state)는 this.setState() 의 호출을 통해서 갱신됩니다.

    • 호출할 때는 새롭게 state로 사용하고자 하는 객체를 전달합니다.
  • setState 메서드는 이전 state의 내용과 새로운 state의 내용을 병합하는데, 단순히 새로운 key-value로 이루어진 객체를 전달하면 기존의 state가 유지하던 다른 내용들은 그대로 유지하고, 새로운 내용이 추가됩니다.

  • 대개 render 메서드 앞에 사용자 정의 메서드를 정의하는 것이 좋은데 메서드를 정의하는 순서는 실제 동작에 영향을 미치지 않는다.


<state 갱신하기>


import React, {Component} from 'react'


class MyComponent extends Component{
    constructor(){
    	super()
      	this.state = {
        	year: 2018,
        }
    }
  
    updateYear(){
    	this.setState({
        	year: 2019
        })
    }
  
    render(){
    	return (
          <View>
            <Text onPress={() => this.updateYear()}>
            	
                The year is: {this.state.year}
            </Text>
          </View>
        )
    }
}

  • Text 엘리먼트 누르면 화살표대로 setState 객체의 흐름


state = {
    year: 2018
}this.setState = ({
    year: 2019
})

	⇩
    
state = {
	year: 2019
}
  • setState가 호출될 때마다 리액트는 ( render 메서드를 다시 호출해서) 컴포넌트와 자식 컴포넌트를 다시 렌더링한다.

  • 컴포넌트의 상태(state)를 변경하는 자체가 컴포넌트를 다시 렌더링하는 것을 의미하지는 않음으로 UI상에서의 변화는 일어나지 않는다.

  • this.setState는 state를 변경하고 다시 render를 호출하게 되는 역할을 수행한다.

    • state 변수를 직접 갱신하려고 하면 의도대로 되지 않는다.
    • setState 메서드가 호출되지 않고, 그래서 컴포넌트가 다시 랜더링되지 않디 때문에 UI는 갱신되지 않기 때문이다.



forceUpdate

  • state 자체를 변경하는 경우 리액트에서 강제로 갱신하는 방법

  • 이 메서드를 호출하면 컴포넌트에서 render 메서드를 호출하게 해 UI를 다시 렌더링하게 만든다.

  • 일반적이지도 않고 권장하지도 않는다.

    • 대부분 이런 재렌더링(rerendering)은 setState 메서드를 호출하거나 새로운 props를 전달하는 등의 다른 메서드를 사용해 다루게 된다.



<foreceUpdate로 강제로 다시 렌더링하기>


class MyComponent extends Component{
   constructor(){
      super()
      this.state = {
      	year: 2018
      }
    }
  
   updateYear(){
      this.state.year = 2019
   
   }
  
   update(){
   	this.forceUpdate()
   }
  
   render(){
     return(
       <View>
       	 <Text onPress={() => this.updateYear()}>
            The year is: {this.state.year}
         </Text>
         <Text onPress={() => this.update()}>
            Force Update
         </Text>
       
       </View>	  
    )
   }
  
}



<다양한 데이터 타입으로 상태(state) 지정하기>


class MyComponent extends Component{
   constructor(){
      super()
      this.state = {
      	year: 2018, //숫자
        leapYear: true, //Boolean
        topics: ['React', 'React Native', 'JavaScript'], //배열
        info:{ //객체
           paperback: true,
          length: '335 pages'
          type: 'programming'
        }
      }
   }
  
   render(){
      let leapyear = <Text>This is not a leapyear!</Text>
      if(this.state.leapYear){
         leapyear = <Text>This is a leapyear!</Text>
      }
     return (
       <View>
         <Text>{this.state.year}</Text>
         <Text>Length: {this.state.info.length}</Text>
         <Text>Type: {this.state.info.type}</Text>
         {leafyear}
       </View>
     )
   }
}



2. props를 사용해 컴포넌트 데이터 다루기

  • props(properties) 는 부모 컴포넌트로부터 전달된 속성값이거나, 컴포넌트가 상속받은 값이다.

  • 최상위에서 선언되고 전달받은 초기값을 변경해야만 props를 변경할 수 있습니다.



<props와 state 간의 대표적인 차이점과 유사점>

propsstate
외부 데이터내부 데이터
변경 불가능변경 가능
부모로부터 상속받는다컴포넌트에서 생성된다.
부모 컴포넌트가 변경할 수 있다.컴포넌트에서만 갱신될 수 있다.
props로 전달받을 수 있다.props로 전달받을 수 있다.
컴포넌트 내부에서 변경할 수 없다.컴포넌트 내부에서 변경할 수 있다.




<book에 대한 값을 선언하고 그 값을 자시 겈ㅁ포넌트에게 정적 props로 전달하는 예>


class MyComponent extends Component{
   render(){
      return(
      	<BookDisiplay book="React Native in Action" />
        //이렇게도 표현 가능
        //<BookDisiplay book={"React Native in Action"} />
      )
   }
}

class BookDisplay extends Component{
   render(){
      return(
        <View>
          <Text>{this.props.book}</Text>
        </View>
      )
   }
}
  • <BookDisplay />를 생성하면서 book이라는 속성을 전달하고 그 값을 문자열 "React Native in Action"으로 지정합니다.

  • 이런 방법으로 props로 전달받은 값은 자식 컴포넌트에서 this.props로 사용할 수 있다.



동적 속성(props)

  • 동적속성이라는 표현은 쉽게 말해서 외부를 통해서 변하는 속성이하고 이해하면 된다.



<동적 속성을 컴포넌트에 전달하는 예>


class MyComponent extends Component{
   render(){
     let book='React Native in Action'
      return(
      	<BookDisiplay book = {book} />
        
      )
   }
}

class BookDisplay extends Component{
   render(){
      return(
        <View>
          <Text>{this.props.book}</Text>
        </View>
      )
   }
}



<state를 사용해 동적 속성을 컴포넌트에 전달하는 예>


class MyComponent extends Component{
  constructor(){
    super()
    this.state = {
    	book: 'React Native in Action'
    }
  }
   render(){
      return(
      	<BookDisiplay book = {this.state.book} />
        
      )
   }
}

class BookDisplay extends Component{
   render(){
      return(
        <View>
          <Text>{this.props.book}</Text>
        </View>
      )
   }
}
  • BookDisplay에 props로 전달된 state의 값이 state를 변경하면 어떻게 갱신되는지 살펴보자

  • props는 변경 불가능하다는 사실을 기억하자, 따라서 부모 컴포넌트인 MyComponent의 state를 변경해야하는데, 이는 BookDisplay의 book props에 새로운 값을 제공하게 되어 부모 컴포넌트와 자식 컴포넌트 모드를 다시 렌더링하게 된다.

  1. state 변수를 선언

  2. state 변수를 갱신하는 setState를 작성

  3. 메서드와 state를 props로 자식 컴포넌트에 전달

  4. 메서드를 자식 컴포넌트에 있는 터치 핸들러에 연결



<동적 props 갱신하기>


class MyComponent extends Component{
  constructor(){
    super()
    this.state = {
    	book: 'React Native in Action'
    }
  }
  
   updateBook(){
      this.setState({
         book: 'Express in Action'
      })
   }
   render(){
      return(
      	<BookDisiplay 
          updateBook = {() = this.updateBook()}
          book = {this.state.book} />
        
      )
   }
}

class BookDisplay extends Component{
   render(){
      return(
        <View>
          <Text onPress={this.props.updateBook}>{this.props.book}</Text>
        </View>
      )
   }
}



속성(props)과 상태(state) 구조분해할당

  • this.state와 this.props로 state와 props를 참조하면 반복적이 되면서 DRY 원칙 (Don't Repeat Yourself! 반복하지 마라!)을 위배하게 된다.

  • 구조분해할당이란 자바스크립트의 새로운 특징으로 ES2015 스펙에 추가되었으며 리액트 네이티브 앱에서도 사용할 수 있습니다.

  • 구조분해할당 개념은 간단히 말해서 객체에서 속성들을 가져와서 앱에서 변수로 사용하라는 것이다.

const person = {name: 'Jeff', age:22}
const {age} = person
console.log(age) #22



<props와 state 비구조화하기>



class MyComponent extends Component{
  constructor(){
    super()
    this.state = {
    	book: 'React Native in Action'
    }
  }
  
   updateBook(){
      this.setState({
         book: 'Express in Action'
      })
   }
   render
      const {book} = this.state
      return(
      	<BookDisiplay 
          updateBook = {() = this.updateBook()}
          book = {book} />
        
      )
   }
}

class BookDisplay extends Component{
   render(){
      const {book, updateBook} = this.props
      return(
        <View>
          <Text onPress={updateBook}>{book}</Text>
        </View>
      )
   }
}

  • book을 참조할 때 컴포넌트에서 더 이상 this.state와 this.props를 참조하지 않아도 됩니다.

  • 대신에 state와 props에서 book변수를 가져왔으며, 변수 그 자체를 참조할 수 있습니다.

  • 이렇게 함으로써 이해하기 쉬워지고, state와 props가 더 많아지고 복잡해도 코드가 간결해집니다.



상태가 없는(stateless) 컴포넌트에서의 속성(props)

  • stateless 컴포넌트는 속성(props)에 대해서만 신경쓰고 자신의 state가 없기 때문에, 이런 컴포넌트는 재사용해야 하는 컴포넌트를 만들 때 상당히 유용합니다.

  • stateless 컴포넌트를 사용해 속성에 접근하려면 메서드의 첫번째 인수로 props를 전달합니다.



<stateless 컴포넌트에서의 속성(props)>


const BoookDisplay = (props) => {
   const{book, updateBook} = props
   return (
     <View>
       <Text onPress={updateBook}>
         {book}
       </Text>
     </View>
   )
}
  • stateless 컴포넌트는 함수형(functional) 컴포넌트 라고도 하는데, 자바스크립트에서 함수로 작성할 수 있끼 때문이다.



배열과 개체를 속성(props)으로 전달하기

  • 다른 데이터 타입에서도 props의 처리는 동일하게 작동합니다.
    • 배열을 전달하는 경우 props로 배열을 전달할 수 있고 객체에 대해서도 마찬가지입니다.

class MyComponent extends Compnent{
    constructor(){
      super()
      this.state = {
      	leapYear: true,
        info:{
           type: 'programming'
        }
      }
    }
  
   render(){
      return (
         <BookDisplay 
           leapYear={this.state.leapYear}
           info={this.state.info}
           topic={['React','React Native', 'JavaScript'}/>
      )
   }
}

const BookDisplay = (props) => {
   let leapYear
   let{topics} = props
   const {info} = props
   topics = topics.map((topic, i) => {
      return <Text>This is a leapyear!</Text>
   }
   if(props.leapYear){
      leapyear = <Text>This is a leapyear!</Text>
   }
   return (
     <View>
       {leapyear}
       <Text>Book type: {info.type}</Text>
       {topics}
     </View>
   )
}



3. 리액트 컴포넌트 스펙

  • 리액트와 리액트 네이티브 컴포넌트를 만들 때, 몇 가지 스첵과 생명주기를 연결해 컴포넌트가 수행하는 동작을 제어할 수 있다.

  • 컴포넌트 스펙(specification) 은 기본적으로 컴포넌트의 생명주기 동안 일어나는 여러 상황에 대해 컴포넌트가 대응하는 방식을 제공한다.

    • render 메서드
    • constructor 메서드
    • statics 객체 (클래스에서 사용할 수 있는 메서드를 정의할 때 사용)

    render 메서드로 UI 만들기

  • render 메서드는 컴포넌트가 생성될 때 필수적으로 필요한 유일한 메서드이다.

    • 하나의 자식 요소나 null 혹은 false 만을 반환한다.
    • 반환하는 자식요소는 View, Text 처럼 이미 선언된 컴포넌트이거나, 개발자가 만들어 파일로부터 가져온 사용자 정의 컴포넌트 이다.

 render(){
    return(
      <View>
        <Text>Hello!</Text>   
      </View>  
    )
 }
  • 괄호가 없으면 당연히 return문과 같은 줄에 위치낳ㄴ다.

 render(){
    return <View> <Text>Hello!</Text> </View>  
    
 }
  • render메소드는 다른 곳에서 정의한 컴포넌트를 반환할 수도 있다.

 render(){
    return <Somecomponent />
    
 }
  • 조건문을 확인하고, 로직을 수행하고, 값에 따라 다른 컴포넌트를 반환할 수 있다.
render(){
   if(something === true){
      return <SomeComponent />
   }else return <SomeOtherComponent />
    
 }



속성 초기화와 생성자 사용하기

  • 상태(state)는 생성자에서 만들 수도 있고 속성 초기화(property initializer) 를 사용해서 만들 수도 있다.

  • props 초기화는 리액트 클래스에서 state를 선언하는 간결한 방법을 제공한다.


class MyComponent extends React.Component{
   state={
      someNumber: 1,
      someBoolean: false
   }
}
  • 클래스를 사용하면서 생성자를 사용해 초기 state를 지정할 수 있다.

    • 클래스의 개념은 생성자와 마찬가지로 리액트나 리액트 네이티브에 한정된 것이 아닌 ES2015의 스펙일 뿐이고, 자바스크립트에서 클래스를 이용한 객체 생성과 초기화를 위한 기존의 프로토타입 기반의 상속과 관련해서 문법적으로 추가된 것이다.

    • 컴포넌트 클래스의 생성자에서 다른 속성들도 this.property (property는 지정하고자 하는 속성의 이름)과 같은 문법으로 설정할 수 있습니다.

    • this 키워드는 사용하고 있는 클래스의 인스턴스를 참조합니다.

constructor(){
   super()
   this.state = {
      someOtherNumber: 19,
      someOtherBoolean: true
   }
   this.name = 'Hello World'
   this.type = 'class'
   this.loaded = false
}
  • 리액트 클래스는 다른 클래스를 확장해서 만들어지기 때문에 생성자를 이용할 때에는 반드시 super 키워드를 this 키워드 전에 사용해야만 한다.

    • 생성자 안에 있는 특정 속성에 접근해야 한다면 생성자와 super 호출 시에 인수(파라미터)로 전달되어야 한다.
  • 속성을 이용해서 상태를 지정하는 것은 의도적으로 컴포넌트의 내부에서 사용하는 기능들의 초기 데이터(seed data)를 지정하는 게 아니라면 일반적으로 좋은 방식은 아니다.

  • state는 컴포넌트가 처음 마운트되거나 생성될 때만 생성된다.
    만일 동일한 컴포넌트를 서로 다른 props의 값을 이용해서 다시 렌더링하게 되면 이미 마운팅된 컴포넌트의 인스턴스는 새로운 props의 값으로 상태를 갱신할 수 없다.



<생성자 내에서 속성(props)를 사용해 상태(state)의 값을 지정하고 있다.>

constructor(props){
   super(props)
   this.state = {
     fullName: props.first + ' ' + props.last,
   }
}
  • 처음에 props로 컴포넌트에 "Nader Devit"를 전달한다고 가정하면 컴포넌트의 상태(state)는 fullName 속성에 "Nader Davit"이 됩니다.
    그런 다음 컴포넌트가 "Another Name"으로 재렌더링되면 생성자는 두 번 호출되지 않기에 fullName의 state 값은 "Nader Davit"으로 그대로 유지됩니다.



4. 리액트 생명주기 메서드

  • 다양한 메서드들이 컴포넌트 생명주기 동안 특정 시점에 실행되는데 이런 메서드를 생명주기 메서드(lifecycle method) 라고 한다.
  • 이들 메서드는 컴포넌트의 생성과 소멸 동안 다른 시점에서 개발자가 지정한 동작을 수행하기 때문에 작동 방식을 이해하는 것은 중요합니다.

  • 리액트 컴포넌트의 생명주기

    • 생성(마운팅, Mounting)
    • 갱신
    • 파기(언마운팅, unmounting)
  • 개발자는 생명주기와 관련해 세 가지 세트의 생명주기 메서드에 연결할 수 있습니다.

  1. 마운팅(생성): 컴포넌트가 생성될 때 일련의 생명주기 메서드들이 호출되기 시작되고 개발자는 이들 중 전부나 일부 메서드에 연결할 수 있는 옵션이 있습니다.
  • 생성자
  • getDerivedStateFromProps
  • render: UI를 렌더링하고 반환
  • componentDidMount
  1. 갱신: 컴포넌트가 갱신될 때 갱신 관련 생명주기 메서드들이 호출되기 시작합니다.
  • getDerivedStateFromProps(props 변경시)
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

갱신은 다음처럼 둘 중 하나로 이루어집니다.

  • setState나 forceUpdate 메서드가 컴포넌트 내에서 호출될 때
  • 새 props가 컴포넌트로 전달될 때
  1. 언마운팅(파기): 컴포넌트가 언마운팅될 때 최종 생명주기 메서드가 시작됩니다.
  • componentWillUnmount 메서드가 있습니다.



static getDerivedStateFromProps 메서드

  • static 클래스 메서드로 컴포넌트가 생성될 때와 컴포넌트가 새 props를 전달받을 때 모두 호출됩니다.

  • 이 메서드는 새로운 props와 가장 최근의 state를 인수로 전달받아서 하나의 객체를 반환한다.

  • 객체의 데이터는 컴포넌트의 상태(state)로 갱신된다.


export default class App extends Component{
   state = {
     userLoggedIn: false
   }
   
   static getDerivedStateFromProps(nextProps, nextState){
      if(nextProps.user.authenticated){
         return{
            userLoggedIn: true
         }
      }
      
     return null
   }
   render(){
      return(
        <View style={styles.container}>
          {
             this.state.userLoggedIn && (
                <AuthenticatedComponent />
             )
          }
        </View>
      )
   }
}



componentDidMount 생명주기 메서드

  • 컴포넌트가 로딩되고서 바로 한 번만 호출됩니다.
  • Ajax호출로 가져온 데이터를 처리하거나, 지정된 실행 후에 실행되는 setTimeout 처리를 하거나, 다른 자바스크립트 프레임워크들과 통합하기에 적절한 위치입니다.

class MainComponent extends Component{
   constructor(){
      super()
      this.state = {loading: true, data: {}}
   }
  componentDidMount(){
     #simulate ajax call (Ajax 호출을 한다면)
     
     setTimeout(() => {
        this.setState({
           loading: false,
           data: {name: 'Nader Dabit', age: 35}
        },2000)
     })
  }
  
  render(){
     if(this.state.loading){
       return <Text>Loading</Text>
     }
    const {name, age} = this.state.data
    return(
      <View>
        <Text>Name: {name}</Text>
        <Text>Age: {age}</Text>
      </View>
    )
  }
}



shouldComponentUpdate 생명주기 메서드

  • Boolean을 반환하며 개발자로 하여금 컴포넌트의 렌더링을 할 것인지를 결정할 수 있다.

  • 새로운 state, props가 컴포넌트나 자식 컴포넌트의 렌더링이 필요하지 않다고 판단되면 false를 반환한다.

  • 만일 컴포넌트를 다시 렌더링하고 싶다면 true를 반환한다.


class MainComponent extends Component{
   shouldComponentUpdate(nextProps, nextState){
      if(nextProps.name !== this.props.name){
         return true
      }
      return false
   }
   render(){
     return <SomeComponent />
   }

}



componentDidUpdate 생명주기 메서드

  • 컴포넌트가 갱신되면서 재렌더링된 후에 바로 호출됩니다.
  • 이전 state와 이전 props를 인수로 가집니다.
    • 리액트 17버전에서는 UNSAFE_componentWillUpdate()로 변경되었다.
class MainComponent extends Component{
   componentDidUpdate(prevProps, prevState){
      if(prevState.showToggled === this.state.showToggled){
         this.setState({
            showToggled: !showToggled
         })
      }
   }
   render(){
     return <SomeComponent />
   }

}



componentWillUnmount 생명주기 메서드

  • 앱에서 컴포넌트가 파기되기 전에 호출됩니다.
  • componentDidMount 메서드에서 설정된 필요한 정리를 하고, 리스너를 삭제하고, 타이머를 제거하도록 지정할 수 있습니다.

class MainComponent extends Component{
   handleClick(){
      this._timeout = setTimeout(() => {
         this.openWidget();
      },2000)
   }
  
  componentWillUnmount(){
     clearTimeout(this._timeout);
  }
  render(){
    return <SomeComponent
             handleClick ={ () => this.handleClick()} />
  }
}
profile
For the sake of someone who studies computer science

0개의 댓글