WEB/React

zeroCho[0/8] 구구단 - state, JSX, babel, Fragment, setState, ref

Harimad 2022. 3. 3. 22:55

목차

더보기

1-1. 리액트를 왜 쓰는가

학습목표: React.createClass -> Class -> Hooks (함수형) | Class 와 Hooks 둘다 배울 것

Q. 리액트를 쓰는 이유? jquery로도 잘써왔는데 왜?

1. 사용자 경험
  - SPA(Single Page Application) - 앱과 비슷한 느낌. 사용자 interface, 사용자 경험에 좋다
2. 재사용 컴포넌트
  - 유지보수에 좋다.
3. 데이터-화면 일치
  - 데이터 처리가 쉽다

 

1-2. 첫 리액트 컴포넌트

코드의 실행 흐름을 이해하고 실행 결과를 예측할 수 있어야한다.

그래야 뒤로 갈수록 헤깔리지 않는다.

<html>
<head>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
    <div id="root"></div> <!--결과: <div id="root"><button>Like<button></div>-->
    <script>
        const e = React.createElement;

        class LikeButton extends React.Component {
            constructor(props) {
                super(props);
            }
            render() {
                return e('button', null, 'Like'); // <button>Like<button>
            }
        }
    </script>
    <script>
        ReactDOM.render(e(LikeButton), document.querySelector('#root'));
    </script>
</body>
</html>

1. head 태그에서 react와 react-dom cdn을 가져온다.

2. body 태그에 id가 root인 div태그를 만든다. 여기에 script 태그에서 만든 컴포넌트를 삽입할 예정이다.

3. react element를 만드는 React.createElement 함수를 변수 e에 담는다

4. React.Component를 상속받은 LikeButton 컴포넌트를 정의한다.

4-1. render함수에 return 값으로 e('button', null, 'Like')를 준다.

5. ReactDOM.render를 이용해서 LikeButton컴포넌트를 만든다음 id가 root인 태그에 삽입시킨다.

 

참고: https://ko.reactjs.org/tutorial/tutorial.html

 

자습서: React 시작하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

1-3. HTML 속성과 상태(state)

 

바뀔 여지가 있는 부분이 상태(state)이다.
그걸 constructor안의 this.state = {} 안에 적어준다.
this.state부분의 상태값들은 컴포넌트 안에서 this.setState() 함수로 핸들링한다.

버튼을 눌러서 상태가 바뀌면 알아서 화면이 바뀐다. (놀라운 부분) <- 화면과 데이터를 일치 시켜준다

버튼을 하나만 만드는데 이렇게 길게 써야하나 싶지만,

코드가 복잡해지면 이런 방식이 훨씬 유지보수가 쉽다는 것을 깨달을 수 있다.

<script>
const e = React.createElement;

class LikeButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = { //state 값 담기
            liked: false,
        };
        this.handsome = {
            agree: 1,
        }
    }
    render() {
        return e(
            'button',
            {
                onClick: () => {
                    this.setState({ //여기서 this.setState()로 핸들링
                        liked: !this.state.liked, handsome: this.handsome.agree++
                    })
                },
                type: 'submit',
            },
            this.state.liked === true ? 'Liked' : 'Not Liked'
        );
    }
}
</script>

<script>
ReactDOM.render(e(LikeButton), document.querySelector('#root'));
</script>

아래는 실행 코드

 

 

1-4. JSX와 바벨(babel)

 

위의 형식은 너무 복잡하고 쓰기가 불편하다.그래서 더 편한 방식을 만든게 JSX문법을 쓰는 것이다. JSX는 JS + XML을 합친 형태이다.JS에 Html을 같이 쓸 수 있는 형태라고 이해하면 된다.JSX를 쓰기위해서는 babel cdn를 Tag에 달아야하며 script Tag에 type을 따로 추가 시켜야한다.

 

LikeButton 컴포넌트의 render함수에서 JSX형식으로 button을 return 하면 된다.그러면 LikeButton 컴포넌트를 ReactDOM.render() 할때 재사용 할 수 있다.

<html>
<head>
	<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
	<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
	<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>  <!-- JSX를 쓰기 위해 Babel cdn 추가-->
</head>

<body>
	<div id="root"></div>
	<script type="text/babel"> //babel 쓰기위해 type도 추가
		class LikeButton extends React.Component {
			constructor(props) {
				super(props);
				this.state = {
					liked: false,
				};
			}
			render() {
				// JSX ( JS + XML )
				return <button type="submit" onClick={() => { this.setState({ liked: !this.state.liked }) }}>
					{this.state.liked === true ? '좋아요' : '싫어요'}
				</button>;
			}
		}
	</script>
	<script type="text/babel">
		ReactDOM.render(
			<>
				<LikeButton />
				<LikeButton />
				<LikeButton />
				<LikeButton />
			</>,
			document.querySelector('#root'));
	</script>
</body>
</html>

아래는 실행 코드

 

1-5. 첫 번째 Q&A

1-6. 구구단 리액트로 만들기

위의 예제에서 바뀌는 state는 4군데 이다.

1. 변하는 값인 state 4개를 constructor안에 this.state에 객체 형태로 담아둔다.

2. render함수안에 return을 하는데 xml문법에 맞게 쓴다. ( <input> 에 /를 안넣으면 오류가 난다. <input/>로 쓸 것 )

3-1 <input> 태그의 함수는 onChange 처럼 CamelCase형식으로 쓴다.

3-2 onChange의 콜백함수는 JS에서 쓰는 방식과 똑같이 쓰면된다.

3-3 중요하것은 숫자를 입력한 값, 즉 e.target.value를 this.setState로 value값에 넣어주는 것이다.

<html>

<head>
<meta charset="UTF-8" />
<title>구구단</title>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>

<body>
<div id="root"></div>
<script type="text/babel">
class GuGuDan extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            first: Math.ceil(Math.random() * 9),
            second: Math.ceil(Math.random() * 9),
            value: '',
            result: '',
        };
    }

    render() {
        return (
            <div>
                <div>{this.state.first} 곱하기 {this.state.second}는?</div>
                <form>
                    <input type="number" value={this.state.value} onChange={(e) => { this.setState({ value: e.target.value }) }} />
                    <button>입력!</button>
                </form>
                <div>{this.state.result}</div>
            </div>
        );
    }
}

</script>
<script type="text/babel">
ReactDOM.render(<GuGuDan />, document.querySelector('#root'));
</script>
</body>

</html>

 

 

 

1-7. 클래스 메서드

깃 링크:https://github.com/Harimad/zeroReact/commit/7072df5c09dcb0e04f7006ffda826b404d6458c5


1. 변할 것들은 state로 만든다.

2. 변할 위치(render()의 return부분)에 state를 잘 넣어준다.

3. event의 콜백함수는 따로 밖에서 만들고 호출하는 방식으로 한다. (JSX와 JS를 웬만하면 섞어쓰지 말자)

 

<body>
<div id="root"></div>
<script type="text/babel">
class GuGuDan extends React.Component {
    constructor(props) {
        super(props);
        this.state = {  // 1
            first: Math.ceil(Math.random() * 9),
            second: Math.ceil(Math.random() * 9),
            value: '',
            result: ``,
        };
    }

    onSubmit = (e) => { // 3
        e.preventDefault();
        if (parseInt(this.state.value) === this.state.first * this.state.second) {
            this.setState({
                result: `${this.state.first} * ${this.state.second} = ${this.state.first * this.state.second} 정답!`,
                first: Math.ceil(Math.random() * 9),
                second: Math.ceil(Math.random() * 9),
                value: '',
            });
        } else {
            this.setState({
                result: `${this.state.value} 땡!\n`,
                value: '',
            });
        }
    };

    onChange = (e) => { // 3
        this.setState({ value: Number(e.target.value) });
    };

    render() { // 2
        return (
            <div>
                <div>{this.state.first} 곱하기 {this.state.second}는?</div>
                <form onSubmit={this.onSubmit}>
                    <input type="number" value={this.state.value} onChange={this.onChange} />
                    <button>입력!</button>
                </form>
                <div>{this.state.result}</div>
                <hr /><br />
            </div>
        );
    }
}

</script>
<script type="text/babel">
ReactDOM.render(<><GuGuDan /><GuGuDan /><GuGuDan /></>, document.querySelector('#root'));
</script>
</body>

실행 결과


Q. e.preventDefault()의 역할?

기본적으로 이벤트 발생 시 브라우저가 실행하는 동작
(폼인 경우 action 링크로 이동, a 태그 클릭 시 해당 사이트로 이동)을 취소합니다.

 

1-8. Fragment와 기타 팁들

깃 링크: https://github.com/Harimad/zeroReact/commit/08d8c15e9276428ff80730fcc685ffa9852205ad


1. 컴포넌트를 return 할때 <div>태그 bla bla~~~~</div> 이것 처럼 div안에 태그를 넣으면 root 하위에 div가 또 생겨서 css작업을 할때 등등 번거로워질 수 있다.

1-1. return <> 태그들 </>를 하거나 <React.Fragment> 태그들 </React.Fragment>를 쓰면 root태그 하위에 바로 태그가 생성된다. (<> </> 가 더 편함)

 

2. 실무에서는 constructor() {} 안에 this.state를 넣는 것보다 바로 state객체를 선언해서 사용하는 방식을 많이 쓴다.

// constructor(props) {
// 	super(props);
// 	this.state = {
// 		first: Math.ceil(Math.random() * 9),
// 		second: Math.ceil(Math.random() * 9),
// 		value: '',
// 		result: ``,
// 	};
// }
class GuGuDan extends React.Component {
    state = { //실무에선 constructor보다 바로 state를 쓰는 방식을 자주쓴다.
        first: Math.ceil(Math.random() * 9),
        second: Math.ceil(Math.random() * 9),
        value: '',
        result: ``,
    };
    ..
    ..
    ..
}

 

 

1-9. 함수형 setState

깃 링크: https://github.com/Harimad/zeroReact/commit/421cc2298c4d1ed842e646945365b66774646627

 


setState를 쓸때 예전 state값으로 새로운 state 값을 만들때는 
콜백함수를 리턴해주는 형식을 쓰는게 좋다!

왜? 예전state값을 인자로 넘길 수 있기 때문이다.

prevState는 바꾸기전의 상태값이 들어있다.

Q. PrevState 는 어디서 온건가요?
-> 그냥 주어지는 겁니다. 함수의 매개변수는 그냥 주어지는 거라서 그 자리가 무엇인지는 외우셔야 합니다.
-> PrevState: 지금 현재 state입니다
this.setState((prevState) => {
	return {
		result: '정답: ' + prevState.value,
		first: Math.ceil(Math.random() * 9),
		second: Math.ceil(Math.random() * 9),
		value: '',
	}
}

 

★어떨때 유용하게 쓰냐면, Counter를 올리는 state를 활용할때 좋다. (이전값을 써야하기 때문이다)

//좋은 방식
this.setState((prevState) => {  //prevState는 이전 값이 담겨있다.
	return {
		value: prevState.value + 1;
	}
})

//안좋은 방식
this.setState({
	value: this.state.value + 1,
});
this.setState({
	value: this.state.value + 1,
});
this.setState({
	value: this.state.value + 1,
});
//했을때 새로운 value가 기존 value + 3이 아니라 value + 1 이 될 수 있다.
//setState는 비동기이기 때문이다.

 

 

1-10. ref

깃 링크: https://github.com/Harimad/zeroReact/commit/93b4c4d7b77c6343e6c456d1e4e601fd74d3ecf2


Q. 1. input에 focus효과를 주려면 어떻게 해야할까?

input ref의 콜백함수 인자 c는 일단 아무 단어나 다 됩니다. c는 input dom 객체를 가리킵니다.

단어 제출하고 나서 다시 input 창에 포커스를 주기 위함입니다.

input의 ref 매커니즘


2. setState를 바꿀때 마다 render() 함수가 재실행 된다. 
너무 많이 랜더링되면 성능이 저하될 수 있다.

 

2-1 Q. render() 함수에서 콜백함수를 밖으로 빼는 이유??
render() 함수안에 콜백함수를 그대로 적어주면
setState를 바꿀때마다 render함수가 재실행되면서
콜백함수도 새로 생기기 때문에 성능저하가 올 수 있다.

 

3. Q. this.setState()가 실행되면  렌더링이 된다고 하셨는데,

this.setState()가 실행만 되어도 렌더링이 되는건가요?

아니면 this.setState()가 실행되고 this.state = {}에서의 객체가 변경되어야 실행되는건가요?

만약 후자가 맞다면this.state={}에서의 객체가 변경안되면 렌더링은 안되는건가요?

-> setState만 해도 리렌더링이 됩니다.

setState를 하면 어쩔 수 없이 객체가 바뀌게 되어있습니다.

이 현상을 해결한게 함수 컴포넌트의 useState입니다.