vscode 확장 패키지 추가
1. - Auto Import - ES6, TS, JSX, TSX
2. - Reactjs code snippets
3. - ESLint
4. - Prettier - Code formatter
Visual Studio Code 폴더/파일 아이콘 변경하기
리액트 프로젝트 생성
npx create-react-app 경로
예) npx create-react-app E:\react-app2
1. 오리엔테이션
1. 함수에서 라이프 사이클 구현하기 - clean up
useEffect 실행시점 :
(1) App() 함수가 최초실행 될때(마운트 될 때) App 그림이 최초 그려질때
(2) 상태 변수가 변경될 때( dependencyList 에 등록되어 있어야 함)
(3) 의존리스트 관리를 할 수 있다.
import { useEffect, useState } from "react"; import "./App.css"; //map, filter, concat, spread, slice function App() { const [data, setData] = useState(0); const [search, setSearch] = useState(0); const download = () => { //다운로드 받고 (통신) let downloadData = 5; //가정 setData(downloadData); } //useEffect 실행시점 : //(1) App() 함수가 최초실행 될때(마운트 될 때) App 그림이 최초 그려질때 //(2) 상태 변수가 변경될 때( dependencyList 에 등록되어 있어야 함) //(3) 의존리스트 관리를 할 수 있다. useEffect(() => { console.log("userEffect 실행됨"); download(); }, [search]); return ( <div> <button onClick={() => { setSearch(2); }}>검색</button> <h1>데이터 : {data}</h1> <button onClick={() => { setData(data + 1) }}>더하기</button> </div> ); } export default App;
2. 수업목표
side effect를 알아야 react hook개념이 이해가 되고 그 hook 안에 useState와 useEffect가 속해있는 것이다.
01 Side Effect란?
React 컴포넌트가 화면에 1차로 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 흔히 Side Effect라고 한다.
예를들어 데이터를 가져오려고 외부 API를 호출할 때, 일단 화면에 렌더링할 수 있는 것은 1차로 먼저 렌더링하고 실제 데이터는 비동기로 가져오는 것이 권장된다. 왜 먼저 렌더링하냐면 연동된 API가 응답이 늦거나 없을 때 데미지(답답함)을 최소화 시켜 사용자 경험 측면에서 유리하기 때문이다.
→ 한마디로 요구되어지는 이펙트 이외에 다른 이펙트가 발생하는 현상이라고 생각하면 된다.
→ Hook은 이 side effect 를 수행하는역할을 한다. side effect 를 줄여 그냥 effect 라고 한다. 그래서 훅의 이름은 useEffect 가 된다.
02 React Hooks 란?
Hooks 는 리액트 v16.8 에 새로 도입된 기능이다. 함수형태의 컴포넌트에서 사용되는 몇가지 기술을 Hook이라고 부른다.
함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능 등을 제공한다.
그렇다면 왜 굳이 잘 쓰고 있던 클래스형 컴포넌트를 함수형 컴포넌트로 바꿔야할까?
react를 배우는 데에 있어서 클래스는 큰 진입장벽이었다. 코드의 재사용성과 코드 구성을 어렵게 만들고, this의 사용이나 이벤트 핸들러의 등록 등 기본적인 JS 문법 사항을 알아야 다룰 수 있기 때문이다. 또한 클래스는 잘 축소되지 않고, reloading을 깨지기 쉽고 신뢰하기 어렵게 만든다. 따라서 react의 최신 기술들이 클래스형 컴포넌트에 효과적으로 적용되지 않았다.
클래스의 문법이 어렵다.
축소가 어렵다.
reloading의 신뢰성이 떨어진다.
최신 기술의 적용이 효과적이지 않다.
→ 이러한 클래스의 단점들을 함수형 컴포넌트로 커버할 수 있다. 하지만 클래스 컴포넌트의 장점인 state 사용이나 life cycle을 직접 다루는 등의 기능을 사용하지 못한다. 이를 해결하기 위해 Hook이 등장했다.
Hook 의 기능
- Hook은 함수형 컴포넌트가 클래스형 컴포넌트의 기능을 사용할 수 있도록 해주는 기능이다.
- useState와 useEffect를 사용하여 특징적으로 state와 lifecycle과 같은 기능을 사용 가능하게 해준다.
State Hook - useState
Effect Hook - useEffect
출처: https://devbirdfeet.tistory.com/52 [새발개발자:티스토리]
app .js
import { Component } from 'react'; import './App.css'; function App() { return ( <div className="container"> <h1>Hello World</h1> <FuncComp></FuncComp> <ClassComp></ClassComp> </div> ); } function FuncComp() { return ( <div className="container"> <h2>function style component</h2> </div> ); } class ClassComp extends Component { render() { return ( <div className='container'> <h2>class style component</h2> </div> ) } } export default App;
3. 클래스에서 state 사용법
app.js
import { Component } from 'react'; import './App.css'; function App() { return ( <div className="container"> <h1>Hello World</h1> <FuncComp initNumber={2}></FuncComp> <ClassComp initNumber={2}></ClassComp> </div> ); } function FuncComp(props) { return ( <div className="container"> <h2>function style component</h2> <p>Number : {props.initNumber}</p> </div> ); } class ClassComp extends Component { state = { number: this.props.initNumber } render() { return ( <div className='container'> <h2>class style component</h2> <p>Number : {this.state.number}</p> <input type="button" value="random" onClick={function () { this.setState({ number: Math.random() }) }.bind(this)} /> </div > ) } } export default App;
4. 함수에서 state 사용법 hook
App.js
import { Component, useEffect, useState } from 'react'; import './App.css'; function App() { return ( <div className="container"> <h1>Hello World</h1> <FuncComp initNumber={2}></FuncComp> <ClassComp initNumber={2}></ClassComp> </div> ); } function FuncComp(props) { // let numberState = useState(props.initNumber); // let number = numberState[0]; // let setNumber = numberState[1]; // let dateState = useState((new Date()).toString()); // let _date = dateState[0]; // let setDate = dateState[1]; const [number, setNumber] = useState([]); useEffect(() => { setNumber(props.initNumber) }, []); const [_date, setDate] = useState((new Date()).toString()); return ( <div className="container"> <h2>function style component</h2> <p>Number : {number}</p> <p>Date : {_date}</p> <input type="button" value="random" onClick={function () { setNumber(Math.random()); }} /> <input type="button" value="date" onClick={function () { setDate((new Date()).toString()); }} /> </div> ); } class ClassComp extends Component { state = { number: this.props.initNumber, date: (new Date()).toString() } render() { return ( <div className='container'> <h2>class style component</h2> <p>Number : {this.state.number}</p> <p>Date : {this.state.date}</p> <input type="button" value="random" onClick={function () { this.setState({ number: Math.random() }) }.bind(this)} /> <input type="button" value="date" onClick={function () { this.setState({ date: (new Date()).toString() }) }.bind(this)} /> </div > ) } } export default App;
5. 클래스에서 라이프 사이클 구현하기
let classStyle = "color:red"; class ClassComp extends Component { state = { number: this.props.initNumber, date: (new Date()).toString() } //componentWillMount 주로 연결했던 이벤트 리스너를 제거하는 등의 여러 가지 정리 활동 //컴포넌트가 소멸된 시점에(DOM에서 삭제된 후) 실행되는 메소드다. //컴포넌트 내부에서 타이머나 비동기 API를 사용하고 있을 때, 이를 제거하기에 유용하다 componentWillUnmount() { console.log("%cclass => componentWillUnmount", classStyle); } //componentDidMount() 는 컴포넌트가 마운트된 직후, 즉 트리에 삽입된 직후에 호출 //컴포넌트가 만들어지고 render가 호출된 이후에 호출되는 메소드다. // AJAX나 타이머를 생성하는 코드를 작성하는 부분이다. componentDidMount() { console.log("%cclass => componentDidMount", classStyle); } //컴포넌트 업데이트 직전에 호출되는 메소드다. //props 또는 state가 변경되었을 때, 재랜더링을 여부를 return 값으로 결정한다. shouldComponentUpdate(nextProps, nextState) { console.log("shouldComponentUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState)); return true; } //shouldComponentUpdate가 불린 이후에 컴포넌트 업데이트 직전에서 호출되는 메소드다. //새로운 props 또는 state가 반영되기 직전 새로운 값들을 받는다. //이 메서드 안에서 this.setState()를 사용하면 무한 루프가 일어나게 되므로 사용하면 안된다. componentWillUpdate(nextProps, nextState) { console.log("componentWillUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState)); } render() { console.log('%cclass => render ', classStyle); return ( <div className='container'> <h2>class style component</h2> <p>Number : {this.state.number}</p> <p>Date : {this.state.date}</p> <input type="button" value="random" onClick={function () { this.setState({ number: Math.random() }) }.bind(this)} /> <input type="button" value="date" onClick={function () { this.setState({ date: (new Date()).toString() }) }.bind(this)} /> </div > ) } }
6. 클래스에서 라이프 사이클 구현하기
let funStyle = "color:#03a9f4"; let funcId = 0; console.log('%cfunc => render ' + (++funcId), funStyle);
7. 함수에서 라이프 사이클 구현하기 - useEffect
//useEffect 복수개 사용가능 const [number, setNumber] = useState([]); const [_date, setDate] = useState((new Date()).toString()); console.log('%cfunc => render ' + (++funcId), funStyle); useEffect(() => { console.log('%cfunc => useEffect A ' + (++funcId), funStyle); setNumber(props.initNumber) }, []); useEffect(() => { console.log('%cfunc => useEffect B ' + (++funcId), funStyle); document.title = number + " : " + _date; }, []);
8. 함수에서 라이프 사이클 구현하기 - clean up
리액트 문서 참조 : Using the Effect Hook
useEffect 에서 clean up 처리 방법
useEffect(() => { console.log('%cfunc => useEffect B ' + (++funcId), funStyle); document.title = number + " : " + _date; return function () { console.log('%cfunc => useEffect B (componentDidMount & componentDidUpdate) ' + (++funcId), funStyle); } });
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다. return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다. return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
9. 함수에서 라이프 사이클 구현하기 - skipping effect
App.js
import { Component, useEffect, useState } from 'react'; import './App.css'; function App() { return ( <div className="container"> <h1>Hello World</h1> <FuncComp initNumber={2}></FuncComp> <ClassComp initNumber={2}></ClassComp> </div> ); } let funStyle = "color:#03a9f4"; let funcId = 0; function FuncComp(props) { // let numberState = useState(props.initNumber); // let number = numberState[0]; // let setNumber = numberState[1]; // let dateState = useState((new Date()).toString()); // let _date = dateState[0]; // let setDate = dateState[1]; //useEffect 복수개 사용가능 const [number, setNumber] = useState(props.initNumber); const [_date, setDate] = useState((new Date()).toString()); //console.log('%cfunc => render ' + (++funcId), funStyle); useEffect(() => { console.log('%cfunc => useEffect A number' + (++funcId), funStyle); //setNumber(props.initNumber); }, [number]); useEffect(() => { console.log('%cfunc => useEffect B _date' + (++funcId), funStyle); document.title = _date; return function () { //클린작업시 사용 console.log('%cfunc => useEffect B _date 클린작업 (componentDidMount & componentDidUpdate) ' + (++funcId), funStyle); } }, [_date]); return ( <div className="container"> <h2>function style component</h2> <p>Number : {number}</p> <p>Date : {_date}</p> <input type="button" value="random" onClick={function () { setNumber(Math.random()); }} /> <input type="button" value="date" onClick={function () { setDate((new Date()).toString()); }} /> </div> ); } let classStyle = "color:red"; class ClassComp extends Component { state = { number: this.props.initNumber, date: (new Date()).toString() } //componentWillMount 주로 연결했던 이벤트 리스너를 제거하는 등의 여러 가지 정리 활동 //컴포넌트가 소멸된 시점에(DOM에서 삭제된 후) 실행되는 메소드다. //컴포넌트 내부에서 타이머나 비동기 API를 사용하고 있을 때, 이를 제거하기에 유용하다 componentWillUnmount() { console.log("%cclass => componentWillUnmount", classStyle); } //componentDidMount() 는 컴포넌트가 마운트된 직후, 즉 트리에 삽입된 직후에 호출 //컴포넌트가 만들어지고 render가 호출된 이후에 호출되는 메소드다. // AJAX나 타이머를 생성하는 코드를 작성하는 부분이다. componentDidMount() { console.log("%cclass => componentDidMount", classStyle); } //컴포넌트 업데이트 직전에 호출되는 메소드다. //props 또는 state가 변경되었을 때, 재랜더링을 여부를 return 값으로 결정한다. shouldComponentUpdate(nextProps, nextState) { console.log("shouldComponentUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState)); return true; } //shouldComponentUpdate가 불린 이후에 컴포넌트 업데이트 직전에서 호출되는 메소드다. //새로운 props 또는 state가 반영되기 직전 새로운 값들을 받는다. //이 메서드 안에서 this.setState()를 사용하면 무한 루프가 일어나게 되므로 사용하면 안된다. // componentWillUpdate(nextProps, nextState) { // console.log("componentWillUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState)); // } render() { console.log('%cclass => render ', classStyle); return ( <div className='container'> <h2>class style component</h2> <p>Number : {this.state.number}</p> <p>Date : {this.state.date}</p> <input type="button" value="random" onClick={function () { this.setState({ number: Math.random() }) }.bind(this)} /> <input type="button" value="date" onClick={function () { this.setState({ date: (new Date()).toString() }) }.bind(this)} /> </div > ) } } export default App;
10. 총정리 겸 수업을 마치며
function App() { const [funcShow, setFuncShow] = useState(true); const [classShow, setClassShow] = useState(true); return ( <div className="container"> <h1>Hello World</h1> <input type="button" value="show all" onClick={function () { setClassShow(true); setFuncShow(true); }} /> <input type="button" value="remove func" onClick={function () { setFuncShow(false); }} /> <input type="button" value="remove class" onClick={function () { setClassShow(false); }} /> {funcShow ? <FuncComp initNumber={2}></FuncComp> : null} {classShow ? <ClassComp initNumber={2}></ClassComp> : null} </div> ); }
다음과 같이 사라질때 클래스에서는 componentWillUnmount 가 호출되고
함수에서는 useEffect 내의 리턴함수가 호출되는 것을 알 수 있다.
return function () { //클린작업시 사용 console.log('%cfunc => useEffect B _date 클린작업 (componentDidMount & componentDidUpdate) ' + (++funcId), funStyle); }
출력 :
class => render
App.js:63 func => useEffect B _date 클린작업 (componentDidMount & componentDidUpdate) 3
App.js:104 class => componentWillUnmount
소스 : https://github.com/braverokmc79/LifeCoding
댓글 ( 4)
댓글 남기기