관리 메뉴

Silver Library (Archived)

뭘 놓쳤을까? - react state 편 본문

Face the fear, build the future/Revision Sector

뭘 놓쳤을까? - react state 편

Chesed Kim 2022. 6. 9. 17:07
반응형

정말 무궁무진하게 사용 할 수 있을 것 같은 state hook 인데, 한번 정리 해 볼겸 여기에 적어보겠습니다.

 

react 는 javascript library 죠. 그리고 state 는 react 의 hook 중 하나입니다. 이 state 덕분에, 기존의 틀을 하나 만들어 두고; 여러개의 component 의 행동을 제어할 수 있게 해주는, 그리고 이를 위해 필요한 구성이 바로 state 내부에 구성 된 property 입니다.

 

What is State?

The state is an instance of React Component Class can be defined as an object of a set of observable properties that control the behavior of the component. In other words, the State of a component is an object that holds some information that may change over the lifetime of the component.

 

그렇다면, 이걸 언제 쓸까요? 주 목적은 '반복적인 코드를 줄이고, functional programming 을 지향' 하는 걸로 보여집니다.

그런데 쓰다보면, 꼭 function 만 쓰는게 아니라, const 도 쓰고, constructor 도 쓰고, 다양해 보입니다.

 

중요한 점 하나만 언급 해 두자면, state 는 오직 class component 에서만 생성 될 수 있습니다. 그 이유로는, state 가 class constructor 내부에 자리 잡기 때문이라고 합니다.

 

가장 간단한 예시를 하나 언급해보겠습니다. 아래는 제가 생성한 react library 내부에 있는 App.tsx 코드 구성입니다.

import React, { useState } from 'react'
import './App.css'

export default function App() {
    const [count, setCount] = useState(0)

    function decrease() {
        setCount(prevCount => prevCount - 1)
    }
    function increase() {
        setCount(prevCount => prevCount + 1)
    }
    
    return (
        <>
            <button onClick={decrease}>-</button>
            <span>{count}</span>
            <button onClick={increase}>+</button>
        </>
    )
}

저걸 돌려보면, 그냥 +, - 나오고, 반응해서 오르고 내리는 타이머 같은 기능에 불과합니다.

 

우선 export default 는 그냥 아래에 export default App 를 원치 않아서 저렇게 작성 한 겁니다. 

그리고 바로 아래에 보이는 [count, setCount] = useState(0) 는, 일종의 고정형입니다.

 

이거 count 내용을 업데이트 하겠다고 아래 처럼 하다가는, react 에서는 작동을 안하게 됩니다.

this.state.attribute = "new-value";

따라서, 아래와 같이 해야만 합니다.

export default function App() {
    const [count, setCount] = useState(0)

    function decrease() {
        setCount(prevCount => prevCount - 1)
    }
    function increase() {
        setCount(prevCount => prevCount + 1)
    }

구성이 다 끝났으면, 이제 어떤 포맷으로 출력하게 할 지를 정해야 합니다.

    return (
        <>
            <button onClick={decrease}>-</button>
            <span>{count}</span>
            <button onClick={increase}>+</button>
        </>
    )
}

이 정도 까지는 쉽게 이해가 될 것 입니다.

그렇다면, State 내부에 객체(object) 가 들어서면 어떻게 사용 가능할까요? 차이점에 주목해보세요.

import React, { useState } from 'react'

export default function App() {
    const [state, setState] = useState({ count: 4, theme: 'sky' })
    const count = state.count
    const theme = state.theme

    function decreaseCount() {
        setState(prevState => {
            return { ...prevState, count: prevState.count - 1 }
        })
    }

    function increaseCount() {
        setState(prevState => {
            return { ...prevState, count: prevState.count + 1 }
        })
    }

    return (
        <>
            <button onClick={decreaseCount}>-</button>
            <span>{count}</span>
            <button onClick={increaseCount}>+</button>
        </>
    )
    
}

export default 부분은 설명 할 것 없겠지만, 그 다음 useState 옆쪽에 (0) 만 있던게 저런 식으로 바뀌었습니다.

state 내부에 object 가 부여 된 상황이라고 볼 수 있습니다.

이 경우, 별도로 해당 object 값을 위해 state 와 연결 시킨 형태의 const 변수 를 따로 구성 한 뒤: 아래 setState 쪽에서 return 해 줄 parameter 를 입력합니다. 

 

이렇게 object 를 state 내부에 넣은 뒤 각 component 내부에 return 해 주려면, return { ...prevState, } 를 넣어 주고 진행 해야 합니다. 그 후, 호출할 해당 변수와, 이 함수에서 실행할 명령어를 구성 해 주면 (setState 의 기본 구성을 기반으로 해당 decreaseCount 전용 함수 내부 component 에 작성 할 내용) 의도한 명령 결과가 출력 될 것 입니다.  

 

참고로 theme 의 경우 string 으로 return 되기에 그냥 count 옆에 해당 sky 문자가 그대로 출력됩니다. 마찬가지로, count 는 number 이므로 숫자열로 출력 됩니다.


아주 짧게, 언급하자면 위의 두번째 방식대로 state 내부에 두 개의 object 를 집어 넣으면 '한 곳에 있는 것' 이므로 count 따로, theme 따로 구성하기에는 곤란한 점이 있어 보입니다.

 

state 내부에 따로 object 를 구성 할 필요가 있다면, 아래와 같이 하면 됩니다.

import React, { useState } from 'react'

export default function App() {
    const [count, setCount] = useState(4)
    const [theme, setTheme] = useState('sky')

    function decreaseCount() {
        setCount(prevCount => prevCount - 1)
        setTheme(prevCount => prevCount === 'sky' ? 'low' : 'sky')
    }

    function increaseCount() {
        setCount(prevCount => prevCount + 1)
        setTheme(prevCount => prevCount === 'sky' ? 'high' : 'sky')
    }

이와 같이 구성 하면, count 따로, theme 따로 구성을 할 수 있습니다.


그럼, 아주 많이 본 this.state 는 어떻게 쓰고, 언제 쓸까요?

사실 이건 모를 때 보면 헷깔리면서도 살얼음 걷듯이 사용 할 수 있는 기능이라고 생각합니다.

 

여러가지 더 나은 예시가 있겠지만, 가장 확실한 사실은 constructor(props) 로 구성 할 때 사용 될 수 있다는 점 입니다.

// app.js

import React, { Component} from 'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isHungry: true,
      topping: "Pepperoni",
      slices: 8
    }
  }

  render() {
    return (
      ...
    )
  }
}

constructor 는 생성자입니다. 이건 javascript 내 자료 구조 이론에서 다루는 부분인데, 아마도 state 와 연관성이 있었기에 이렇게 사용이 가능하다고 보여집니다.

 

저도 super 가 뭘 하는 거고, constructor 가 뭘 하는 녀석이었더라...를 잊어 버렸기에 직접 찾아봤습니다.

The constructor is a method used to initialize an object's state in a class. It automatically called during the creation of an object in a class. The concept of a constructor is the same in React. The constructor in a React component is called before the component is mounted. When you implement the constructor for a React component, you need to call super(props) method before any other statement. If you do not call super(props) method, this.props will be undefined in the constructor and can lead to bugs.

 

라고 하는데, 결론만 말하자면 javascript (적어도 제 경험에 한해서는) 에서 생성자 constructor 를 사용 했듯이 react 에서도 마찬가지로 component 가 mounting 되기 전에 호출(call) 된다는 점. 그리고 super(props) 를 호출(call) 해야만 this.props 가 사용이 가능하다 는 점 입니다.

 

도대체 이 this.state 는 정체가 뭘까요? 일단 this 가 constructor 내부의 (props) 를 지칭 하는 건 알겠는데, 뭘까요?

 

짧게, props 의 정의를 봅시다.

Props are pieces of data passed into a child component from the parent while state is data controlled within a component

Props 를 component 로 전달하는 과정은 이렇습니다.

// app.js

<App prop='Some data for a prop' />

Many times state will be used to pass data into a child component via props. There are even ways to manipulate a parent component's state from a child component. 

 

즉, 기존에 알고 있었던 this.state 라는 것은 'state management in react component' 의 과정 이라는 것 입니다.

저도 기억이 가물하지만, this.state 가 나올 때, 적어도 props 가 적어도 아예 안 나온 적은 없었던 것 같습니다.

 

짧게 예를 보겠습니다. 아래의 eatSlice 에 주목해주세요.

this.eatSlice 도 있고, this.state.slices 도 있습니다.

 

이 this.state.slices 는 const totalSlices 라는 변수가 가리키는 표현식(?) 인데, 이게 속한 곳이 eatSlice() 입니다.

eatSlice 는 뭘 하기 위해 난해하게 저렇게 존재하고 있는 걸까요?

// app.js

import React, { Component} from 'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isHungry: true,
      topping: "Pepperoni",
      slices: 8
    }
    this.eatSlice = this.eatSlice.bind(this);
  }

  eatSlice() {
    const totalSlices = this.state.slices - 1;
    this.setState({
      slices: totalSlices
    });
  }

  render() {
    return (
      ...
    )
  }
}

If we assume this function will be fired when a button is clicked, then each time the user clicks that button our slices in state will go down by one (even into negatives because we have not created logic to prevent that). Each time the state changes from the button click, our component will re-render with the new data.

 

This allows users to modify data on a page in real time, which is awesome. We can also pass our state into a child component as props.

 

마지막으로 하나만 더 예시를 들어보겠습니다.

// render method - as one of react component 

import React from 'react'

export default class Operator extends React.Component {
	constructor (props) {
    console.log('Operator')
    super(props)
    
    this.state = {
    	operator: 0
    }
    
    this.increment = () => this.setState({operator: this.state.opeartor + 1})
    this.decrement = () => this.setState({operator: this.state.opeartor - 1})    
}

	render () {
    	console.log('Render')
        
        return <div>
        	<button onClick={this.increment}>Increment</button>
            <button onClick={this.decrement}>Decrement</button>
            <div className='operator'>
            	Operator: {this.state.operator}
            </div>
        </div>
    }
}

// lifecycle method

import React from 'react'

export default class Operator extends React.Component {
	constructor (props) {
    console.log('Operator')
    super(props)
    
    this.state = {
    	operator: 0
    }
    
    this.increment = () => this.setState({operator: this.state.opeartor + 1})
    this.decrement = () => this.setState({operator: this.state.opeartor - 1})    
}

// this is newly added
componentDidMount() {
	console.log('Component has been sucessfully Mounted')
}

	render () {
    	console.log('Render')
        
        return <div>
        	<button onClick={this.increment}>Increment</button>
            <button onClick={this.decrement}>Decrement</button>
            <div className='operator'>
            	Operator: {this.state.operator}
            </div>
        </div>
    }
    componentDidUpdate(prevProps, prevState, snapshot) {
 		console.log('Component's been updated')   
	}
    
    componentWillUnmount() {
    	console.log('Component Will Unmount')
}
import React from 'react'
import ReactDOM from 'react-dom'
import Counter from './counter'

class App extends React.Component {
	constructor (props) {
    	super(props)
        
    this.state = {
    	mount: true
    }
    
    this.mountCounter = () => this.setState({mount: true})
    this.unmountCounter = () => this.setState({mount: fales})
}

	render () {
    	return <div>
        	<button onClick={this.mountCounter} disabled={this.state.mount}>Mount Counter</button>
            <button onClick={this.mountCounter} disabled={this.state.mount}>Unmount</button>
            {this.state.mount ? <Counter /> : null}
        </div>
	}
}

ReactDOM.render(<App/>, document.getELementById('root))

 

여기 까지가, this.state 의 주 목적이기도 했던 어떻게 state 를 변경 할 수 있나요? 에 대한 글 이었습니다.

 

사실 한국어로 대부분 작성 한 주제에 이렇게 끝내는 건 좀 무책임하지만, 참고 링크를 남겨둔 채 나머지는 다음에 또 써보겠습니다.