-
[React] 개발자 지망생 스터디 - 25일차스터디/KAKAOCLOUDSCHOOL 2022. 12. 7. 10:02
React_hooks
1. 컴포넌트 반복
- 동일한 모양의 컴포넌트를 여러 개 배치
- 배열 형태의 데이터를 출력할 때 유용하게 사용
1) 배열.map() - 변환
- 배열의 데이터를 순회하면서 매개변수로 받은 함수를 요소 단위로 수행한 후 그 결과를 모아서 다시 배열로 리턴하는 함수
- map에 설정하는 매개변수
#첫번째는 callback 함수(필수) - 매개변수는 3개까지 될 수 있고 반드시 하나의 데이터를 리턴해야함
첫번째 매개변수는 순회하는 각 요소
두번째 매개변수는 인덱스
세번째 매개변수는 배열 그 자체
#두번째는 callback 함수 내부에서 사용할 this 참조(선택)
let ar = [10, 20, 30]; // 위의 배열에 5를 더한 배열을 생성 for(let i=0; i<ar.length;i++){ ar[i] = ar[i] + 5; } // react에서는 이런 구조는 성립되지 않음 - props나 state는 값을 수정할 수 없기 때문 let ar = [10, 20, 30]; let result = []; // 위의 배열에 5를 더한 배열을 생성 let size = ar.length; for(let i=0; i<size; i++){ result.push(ar[i] + 5); } let ar = [10, 20, 30]; let result = ar.map(e => return e + 5);
2) 배열을 이용한 컴포넌트
# React 프로젝트 생성
> yarn create react-app react_hooks
# src 디렉토리에 Iteration.jsx 파일을 만들고 반복문을 이용해 Component를 생성하는 코드를 작성
🗂react_hooks -> 📁 src -> (CREATE) 📄 Iteration.jsx
import React from 'react'; const Iteration = () => { const names = ['JavaScript', 'Java', 'Python', 'C#', 'C++', 'Rust']; // 배열을 순회하면서 <li> 태그로 감싸기 const nameList = names.map(name => <li>{name}</li>); return (<ul>{nameList}</ul>); } export default Iteration;
# App.js 파일을 수정해서 Iteration 컴포넌트 출력🗂react_hooks -> 📁 src -> 📄 App.js
import Iteration from "./Iteration"; function App() { return ( <div> <Iteration/> </div> ); } export default App;
Iteration 컴포넌트 출력 결과 (yarn start)
> 실행을 하고 확인을 하면 출력은 제대로 되는데 콘솔 창에는 에러가 있음
> React에서는 Component 배열을 랜더링 할 때 어떤 원소에 변동이 있었는지 확인하기 위해서 key 값을 설정해야 함
> key를 설정하지 않으면 변동 사항을 확인 하기 위해서 배열 전체를 순회해야 하지만 key를 설정하면 더욱 빠르게 변동 사항을 확인 할 수 있음
> 배열에서는 index를 키로 설정하면 무난함.Console 결과
# Iteration.jsx 파일 수정
📁 src -> 📄 Iteration.jsx
> const nameList = names.map((name, index) => <li key={index}>{name}</li>);
import React from 'react'; const Iteration = () => { const names = ['JavaScript', 'Java', 'Python', 'C#', 'C++', 'Rust']; // 배열을 순회하면서 <li> 태그로 감싸기 const nameList = names.map((name, index) => <li key={index}>{name}</li>); return (<ul>{nameList}</ul>); } export default Iteration;
콘솔 창에 에러가 사라진 것을 확인해 볼 수 있음 3) 유동적인 데이터 적용
- state 는 변경이 되면 자동으로 component 에 적용
☑︎ props : 상위 컴포넌트가 하위 컴포넌트에게 데이터를 넘겨주고자 할 때 사용
☑︎ state : 컴포넌트 내에서 생성해서 수정과 삭제가 가능한 개체로 변경이 되면 컴포넌트를 리랜더링
☑︎ ref : 컴포넌트를 조작하고자 할 때 생성
☑︎ filter는 매개변수가 1개이고 boolean을 리턴하는 함수를 매개변수로 대입하는데 함수의 결과가 true인 데이터만 모아서 다시 배열로 리턴하는 함수
# Iteration.jsx 파일을 수정
🗂react_hooks -> 📁 src -> 📄 Iteration.jsx
import React, {Component} from 'react'; class Iteration extends Component { //내용이 변경되면 Component를 리랜더링하는 state를 생성 state = { names:['JavaScript'], name:'' } //Input에 입력하면 name state 의 값을 변경하는 //이벤트 처리 함수 handleChange = (e) =>{ this.setState({ name:e.target.value }) } //name 의 값을 names에 추가하는 함수 - 버튼을 누르면 동작 //push 대신에 배열을 복제해서 연결하는 concat 함수 이용 handleInsert = (e) =>{ this.setState({ names:this.state.names.concat(this.state.name), name:'' }) } //데이터 삭제 함수 //index를 매개변수로 받아서 삭제 handleRemove = (index) => { //확인 대화상자를 출력해서 아니오를 누르면 리턴 let result = window.confirm("정말로 삭제"); if(result === false){ return; } const {names} = this.state; //const names = this.state.names; //slice: 매개변수 2개 받아서 //배열을 잘라내서 복제해서 리턴하는 함수 //매개변수는 시작 위치와 마지막 위치를 대입 /* this.setState({ names:[names.slice(0, index), names.slice(index+1, names.length)] }) */ //넘어온 인덱스와 배열의 인덱스가 다른 것 만 추출 this.setState({ names:names.filter((item, e) => e !== index ) }) } render(){ const nameList = this.state.names.map( (name, index) => <li key={index} onDoubleClick={(e) => this.handleRemove(index)}>{name} <button onClick={(e) => this.handleRemove(index)}>삭제</button></li>); return (<div> <input onChange={this.handleChange} value={this.state.name}/> <button onClick={this.handleInsert}>추가</button> <ul>{nameList}</ul> </div>) } } export default Iteration;
2. Life Cycle
- 인스턴스가 생성되고 소멸될 때 까지의 과정
- 생성은 생성자(Constructor)가 담당을 하고 소멸은 소멸자(Destructor)가 담당함
- IoC(Inversion of Control - 제어의 역전, 클래스는 개발자가 생성하지만 인스턴스의 생성과 소멸은 개발자가 하지 않고 다른 프레임워크나 컨테이너 등이 하는 형태)가 적용된 경우에는 특별한 경우가 아니면 생성자를 이용해서 인스턴스를 생성하지 않음
- IoC가 적용되면 일반적으로 생성자를 직접 호출하지 않기 떄문에 수명 주기 관련 메서드를 이용해서 생성 과 소멸될 때 작업을 수행
1) react의 Component의 수명 주기
(1) Mount(컴포넌트가 메모리 할당을 받아서 출력)
(2) Update(컴포넌트 정보를 업데이트 하는 것으로 리랜더링)
(3) Unmount(컴포넌트가 화면에서 제거)2) Mount 될 때 호출되는 메서드
☑︎ constructor : 생성자 - 가장 먼저 호출, state 초기화를 수행
☑︎ getDerivedStateFromProps : props 에 있는 값을 state 에 동기화 할 때 호출
☑︎ render : 랜더링 할 때 호출되는 메서드로 this.props와 this.state를 이용해서 props와 state 접근이 가능
☑︎ componenetDidMount : 화면에 보여지고 난 후 호출되는 메서드, 비동기(네트워크 사용이나 타이머 작용) 작업 수행3) update 할 때 호출되는 메서드
☑︎ getDerivedStateFromProps : props 에 있는 값을 state 에 동기화 할 때 호출
☑︎ shouldComponentUpdate : 리랜더링을 결정하는 메서드로 여기서 false를 리턴하면 리랜더링 되지 않음
☑︎ render
☑︎ getSnapshotBeforeUpdate : 변경된 내용을 DOM에 적용하기 직전에 호출되는 메서드
☑︎ componentDidUpdate : 업데이트 되고 나면 호출되는 메서드4) Unmount 될 때 호출되는 메서드
☑︎ componentWillUnmount : 사라지기 전에 호출되는 메서드로 memory leak(타이머)이 발생할 수 있는 객체의 제거 작업을 수행
5) 라이프 사이클 이용 시 주의할 점
- react의 개발 모드가 React.StrictMode 로 설정되어 있으면 개발 환경에서는 Mount를 2번씩 함
3. 에러를 화면에 출력
1) 에러를 발생시키기 위해서 Iteration.jsx 파일에서 없는 프로퍼티를 호출
{this.state.errorexample.value}
2) state 관련된 에러가 발생하면 호출되는 메서드를 재정의해서 에러가 발생했다는 사일을 출력해주는 컴포넌트를 생성 -ErrorBoundary.jsx
🗂react_hooks -> 📁 src -> (CREATE) 📄 ErrorBoundary.jsx
import React, {Component} from 'react'; class ErrorBoundary extends Component{ state = { error:false } //컴포넌트에서 예외가 발생하면 호출되는 메서드 componentDidCatch(error, info){ this.setState({ error:true }) console.log({error, info}); } render(){ //error 가 true이면 출력 if(this.state.error){ return <div>에러가 발생했습니다.</div>; } //error 가 false 이면 하위 컴포넌트 출력 else{ return this.props.children; } } } export default ErrorBoundary;
3) App.js 수정
🗂react_hooks -> 📁 src -> 📄 App.js
import ErrorBoundary from "./ErrorBoundary"; import Iteration from "./Iteration"; function App() { return ( <div> <ErrorBoundary> <Iteration/> </ErrorBoundary> </div> ); } export default App;
4. Hooks
- React 16.8 버전에 추가
- Class Component의 기능을 Function Component에서 사용할 수 있도록 해주는 기능
5. useState
> state를 함수 컴포넌트 안에서 사용할 수 있도록 해주는 hook
> useState의 매개변수는 state의 초기값이 되고 리턴하는 데이터는 state와 state의 값을 변경할 수 있는 setter 함수의 배열
# 컴포넌트에서의 state 사용 - App.js 수정
🗂react_hooks -> 📁 src -> 📄 App.js
import React, {Component, useState} from 'react'; //클래스 컴포넌트 class ClassState extends Component{ /* //생성자를 만들지 않고 이렇게 state를 초기화해도 됩니다. state = { count:0 } */ constructor(props){ super(props); this.state = { count:0 } } render(){ return( <div> <p>클릭을 {this.state.count} 번 수행</p> <button onClick={ (e) => this.setState( {count: this.state.count + 1})}> +1 </button> </div> ); } } //함수형 컴포넌트에서 state 사용 //함수형 컴포넌트는 인스턴스가 아니므로 this 사용 불가 const FunctionState = () => { const [count, setCount] = useState(0); return( <div> <p>클릭을 {count} 번 수행</p> <button onClick={ (e) => setCount(count + 1)}> +1 </button> </div> ) } function App() { return ( <div> <ClassState/> <FunctionState /> </div> ); } export default App;
6. useRef
> ref : 컴포넌트를 조작하기 위해서 붙이는 일종의 id와 같은 변수
> useRef 로 만들어진 변수는 값이 변경된다고 해서 컴포넌트가 리랜더링 되지는 않음
> useRef(초기값) 으로 변수를 생성하고 컴포넌트나 DOM에 설정할 때는 ref 속성에 생성된 변수를 대입해주면 됨
✔️ 특정 Input 에 포커스 설정하기
# InputSample.jsx 파일을 생성하고 컴포넌트 생성 - state 와 ref 도 사용
🗂react_hooks -> 📁 src -> (CREATE) 📄 InputSample.jsximport React, {useState, useRef} from 'react'; const InputSample = () => { //2개의 속성을 가진 state 생성 const [inputs, setInputs] = useState({ name:'', nickname:'' }); //state를 편리하게 사용하기 위해서 비 구조화 할당 const {name, nickname} = inputs; //react 에서 다른 컴포넌트 나 DOM을 참조할 수 있는 변수를 생성 const nameInput = useRef(); const onChange = (e) => { setInputs({ [e.target.name]:e.target.value }) } const onReset = (e) => { setInputs({ name:'', nickname:'' }) //이름 입력 란으로 포커스 옮기기 nameInput.current.focus(); } return( <div> <input name="name" value={name} onChange={onChange} ref={nameInput}/> <input name="nickname" value={nickname} onChange={onChange}/> <button onClick={onReset}>초기화</button> </div> ) } export default InputSample;
# App.js 에서 InputSample 출력하는 코드 추가
🗂react_hooks -> 📁 src -> 📄 App.jsimport React, {Component, useState} from 'react'; import InputSample from './InputSample'; //클래스 컴포넌트 class ClassState extends Component{ /* //생성자를 만들지 않고 이렇게 state를 초기화해도 됩니다. state = { count:0 } */ constructor(props){ super(props); this.state = { count:0 } } render(){ return( <div> <p>클릭을 {this.state.count} 번 수행</p> <button onClick={ (e) => this.setState( {count: this.state.count + 1})}> +1 </button> </div> ); } } //함수형 컴포넌트에서 state 사용 //함수형 컴포넌트는 인스턴스가 아니므로 this 사용 불가 const FunctionState = () => { const [count, setCount] = useState(0); return( <div> <p>클릭을 {count} 번 수행</p> <button onClick={ (e) => setCount(count + 1)}> +1 </button> </div> ) } function App() { return ( <div> <ClassState/> <FunctionState /> <InputSample /> </div> ); } export default App;
7. useEffect
- state 가 변경된 후 수행할 side effect를 설정하는 Hook
- Class 의 LifeCycle 중에서 componentDidMount 와 componentDidUpdate, ComponentWillUnmount가 합쳐진 형태
1) Class 의 수명주기 메서드 사용
# App.js 수정
🗂react_hooks -> 📁 src -> 📄 App.js
import React from 'react'; class ClassEffect extends React.Component{ //생성자 constructor(props){ super(props); console.log("생성자 - 가장 먼저 호출되는 메서드"); this.state = { count:0 }; } //Component 가 Mount 된 후 호출되는 메서드 componentDidMount(){ console.log("마운트 된 후 호출되는 메서드"); document.title = `You clicked ${this.state.count} times`; } //Component 가 Update 된 후 호출되는 메서드 componentDidUpdate(){ console.log("업데이트 된 후 호출되는 메서드"); document.title = `You clicked ${this.state.count} times`; } render(){ return( <div> <p>You clicked {this.state.count} times</p> <button onClick={ (e) => this.setState({count:this.state.count+1})}> +1 </button> </div> ) } } const App = () => { return( <div> <ClassEffect /> </div> ) } export default App;
> 실행하고 검사 창을 열어서 console을 확인해보면 생성자 와 마운트가 2번 되는 것을 확인할 수 있는데
이는 개발 환경에서 strict mode 가 적용되면 그렇게 됨2) useEffect를 이용해서 함수형 컴포넌트에 위와 유사한 형태 구현
import React, {useState, useEffect} from 'react'; const ClassEffect = () => { const [count, setCount] = useState(0); useEffect(()=>{ console.log("마운트 와 업데이트가 끝나면 호출"); document.title = `You clicked ${count} times`; }) return( <div> <p>You clicked {count} times</p> <button onClick={ (e) => setCount(count + 1)}> +1 </button> </div> ) } const App = () => { return( <div> <ClassEffect /> </div> ) } export default App;
3)useEffect 함수
> useEffect(() => {수행할 내용}, deps 배열} 이 원형
deps 배열을 생략하면 Mount 된 경우 와 모든 state 가 변경될 때 마다 호출
deps 배열을 비워두게 되면([ ]) Mount 된 후에만 호출
deps 배열에 state를 설정하게 되면 state 값이 변경될 때도 호출
> 수행할 내용에서 함수를 리턴하면 cleanup 함수가 됨.
cleanup 함수는 Unmount 될 때 호출되는 함수8. 객체 배열을 함수형 컴포넌트로 출력하고 삽입, 삭제, 갱신을 수행하기
1) 기본 데이터 출력
> 하나의 객체(이름 과 이메일을 소유하고 기본키로 id를 소유)를 출력할 컴포넌트 와 객체 배열을 출력할 컴포넌트를 작성
# UserList.jsx
🗂react_hooks -> 📁 src -> 📄 UserList.jsx
import React from 'react'; function User({user}){ return( <div> <b>{user.username}</b> <span>({user.email})</span> </div> ) } function UserList({users}){ return( <div> {users.map( user => <User user={user} key={user.id}/>)} </div> ) } export default UserList;
# App.js 에서 기본 데이터를 생성하고 UserList를 출력
🗂react_hooks -> 📁 src -> 📄 App.js
import React from 'react'; import UserList from './UserList'; const App = () => { const users = [ {id:1, username:'adam', email:'itstudy@kakao.com'}, {id:2, username:'군계', email:'ggangpae1@gmail.com'} ] return( <div> <UserList users={users} /> </div> ) } export default App;
2)데이터 추가 구현
# 추가 화면에 해당하는 컴포넌트 생성 - CreateUser.jsx
🗂react_hooks -> 📁 src -> (CREATE) 📄 CreateUser.jsx
> 기본 데이터는 App.js 파일에 존재
> 실제 데이터 추가는 App.js 파일에 있는 데이터에 추가되어야 함
이런 경우에는 App.js state 나 이벤트 핸들러를 만들고 CreateUser에게 props 형태로 전달을 해서 사용하는 것이 편리함
import React from 'react'; function CreateUser({username, email, onChange, onCreate}){ return( <div> <input name="username" value={username} onChange={onChange} placeholder="이름 입력"/> <input name="email" value={email} onChange={onChange} placeholder="이메일 입력"/> <button onClick={onCreate}>추가</button> </div> ) } export default CreateUser;
import React from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import {useState, useRef} from 'react'; const App = () => { const [inputs, setInputs] = useState({ username:'', email:'' }); const {username, email} = inputs; const onChange = (e) => { //state를 수정할 때는 원본을 복제한 후 수정 setInputs({ ...inputs, [e.target.name]:e.target.value }); } //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록 //state로 생성 const [users, setUsers] = useState([ {id:1, username:'adam', email:'itstudy@kakao.com'}, {id:2, username:'군계', email:'ggangpae1@gmail.com'} ]); //변수를 생성 const nextId = useRef(3); //데이터 삽입 메서드 const onCreate = () => { //하나의 객체 생성 const user = { id:nextId.current, username, email } //users 에 user를 추가 setUsers([...users, user]); //입력 요소 초기화 setInputs({ username:'', email:'' }) //다음 id를 위해서 id를 1증가 nextId.current += 1; } return( <div> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} /> </div> ) } export default App;
3) 삭제 구현
> 데이터를 출력할 때 이메일 뒤에 삭제 버튼을 추가해서 삭제 버튼을 누르면 그 데이터가 삭제되도록 구현 삭제의 경우 대부분 기본키를 매개변수로 받아서 그 기본키에 해당하는 데이터를 삭제
# UserList.jsx 수정
🗂react_hooks -> 📁 src -> 📄 UserList.jsx
import React from 'react'; function User({user, onRemove}){ return( <div> <b>{user.username}</b> <span>({user.email})</span> <button onClick={(e) => onRemove(user.id)}>삭제</button> </div> ) } function UserList({users, onRemove}){ return( <div> {users.map( user => <User user={user} key={user.id} onRemove={onRemove}/>)} </div> ) } export default UserList;
# App.js 파일에 삭제하는 함수를 만들고 UserList에게 넘겨주기
🗂react_hooks -> 📁 src -> 📄 App.js
import React from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import {useState, useRef} from 'react'; const App = () => { const [inputs, setInputs] = useState({ username:'', email:'' }); const {username, email} = inputs; const onChange = (e) => { //state를 수정할 때는 원본을 복제한 후 수정 setInputs({ ...inputs, [e.target.name]:e.target.value }); } //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록 //state로 생성 const [users, setUsers] = useState([ {id:1, username:'adam', email:'itstudy@kakao.com'}, {id:2, username:'군계', email:'ggangpae1@gmail.com'} ]); //변수를 생성 const nextId = useRef(3); //데이터 삽입 메서드 const onCreate = () => { //하나의 객체 생성 const user = { id:nextId.current, username, email } //users 에 user를 추가 setUsers([...users, user]); //입력 요소 초기화 setInputs({ username:'', email:'' }) //다음 id를 위해서 id를 1증가 nextId.current += 1; } //삭제하는 함수 //users state 에서 id가 id 인 데이터 삭제 //id 가 일치하지 않는 데이터만 삭제 //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서 //수정합니다. const onRemove = id => setUsers(users.filter(user => user.id !== id)); return( <div> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove}/> </div> ) } export default App;
4) 배열의 데이터를 수정
- 계정 이름을 클릭하면 active 속성 값을 toggle(반전) 시켜서 글자 색상을 변경하도록 하기
# UserList.jsx 수정
🗂react_hooks -> 📁 src -> 📄 UserList.jsx
import React from 'react'; function User({user, onRemove, onToggle}){ return( <div> <b onClick={(e)=>onToggle(user.id)} style={{ cursor:'pointer', color:user.active?'green':'black' }}>{user.username}</b> <span>({user.email})</span> <button onClick={(e) => onRemove(user.id)}>삭제</button> </div> ) } function UserList({users, onRemove, onToggle}){ return( <div> {users.map( user => <User user={user} key={user.id} onRemove={onRemove} onToggle={onToggle}/>)} </div> ) } export default UserList;
# App.js 수정
🗂react_hooks -> 📁 src -> 📄 App.js
import React from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import {useState, useRef} from 'react'; const App = () => { const [inputs, setInputs] = useState({ username:'', email:'' }); const {username, email} = inputs; const onChange = (e) => { //state를 수정할 때는 원본을 복제한 후 수정 setInputs({ ...inputs, [e.target.name]:e.target.value }); } //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록 //state로 생성 const [users, setUsers] = useState([ {id:1, username:'adam', email:'itstudy@kakao.com', active:false}, {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true} ]); //변수를 생성 const nextId = useRef(3); //데이터 삽입 메서드 const onCreate = () => { //하나의 객체 생성 const user = { id:nextId.current, username, email } //users 에 user를 추가 setUsers([...users, user]); //입력 요소 초기화 setInputs({ username:'', email:'' }) //다음 id를 위해서 id를 1증가 nextId.current += 1; } //삭제하는 함수 //users state 에서 id가 id 인 데이터 삭제 //id 가 일치하지 않는 데이터만 삭제 //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서 //수정합니다. const onRemove = id => setUsers(users.filter(user => user.id !== id)); //수정하는 메서드 //id에 해당하는 데이터의 active 속성의 값을 반전 const onToggle = id => setUsers(users.map(user => user.id === id ? {...user, active:!user.active} : user)) return( <div> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> </div> ) } export default App;
5) useEffect 활용
# UserList.jsx 파일의 User 컴포넌트에 useEffect 작성
🗂react_hooks -> 📁 src -> 📄 UserLisx.jsx
import React, {useEffect} from 'react'; function User({user, onRemove, onToggle}){ //마운트 될 때 그리고 state 가 변경될 때 모두 호출 useEffect(() => { console.log('컴포넌트가 화면에 나타남'); //삽입 과 수정 시 호출 //이 데이터의 id가 존재하면 수정이고 //id가 존재하지 않으면 삽입 console.log(user) //함수를 리턴하면 컴포넌트가 사라질 때 호출 return () => { //데이터를 삭제한 경우 호출 console.log('컴포넌트가 사라짐') console.log(user); } }, [user]); return( <div> <b onClick={(e)=>onToggle(user.id)} style={{ cursor:'pointer', color:user.active?'green':'black' }}>{user.username}</b> <span>({user.email})</span> <button onClick={(e) => onRemove(user.id)}>삭제</button> </div> ) } function UserList({users, onRemove, onToggle}){ return( <div> {users.map( user => <User user={user} key={user.id} onRemove={onRemove} onToggle={onToggle}/>)} </div> ) } export default UserList;
9. useMemo
- 연산된 값을 재사용하는 Hook
- 성능 최적화를 위해서 사용
- 매개변수로 연산을 수행하는 함수 와 배열을 대입받는데 배열에 변화가 생긴 경우에만 연산을 수행하는 함수를 수행하고 그렇지 않은 경우는 함수를 호출해도 결과만 다시 제공함
1) App.js를 수정해서 현재 active 가 true 인 user 수를 출력하기
- 실행하면 입력 내용을 수정해도 유저 수를 다시 계산합니다.
import React from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import {useState, useRef} from 'react'; //active 가 true 인 데이터의 개수 const countActiveUser = (users) => { console.log("사용자 수를 세기"); return users.filter(user => user.active).length; } const App = () => { const [inputs, setInputs] = useState({ username:'', email:'' }); const {username, email} = inputs; const onChange = (e) => { //state를 수정할 때는 원본을 복제한 후 수정 setInputs({ ...inputs, [e.target.name]:e.target.value }); } //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록 //state로 생성 const [users, setUsers] = useState([ {id:1, username:'adam', email:'itstudy@kakao.com', active:false}, {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true}, {id:3, username:'카리나', email:'karina@sm.com', active:true} ]); //변수를 생성 const nextId = useRef(4); //데이터 삽입 메서드 const onCreate = () => { //하나의 객체 생성 const user = { id:nextId.current, username, email } //users 에 user를 추가 setUsers([...users, user]); //입력 요소 초기화 setInputs({ username:'', email:'' }) //다음 id를 위해서 id를 1증가 nextId.current += 1; } //삭제하는 함수 //users state 에서 id가 id 인 데이터 삭제 //id 가 일치하지 않는 데이터만 삭제 //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서 //수정합니다. const onRemove = id => setUsers(users.filter(user => user.id !== id)); //수정하는 메서드 //id에 해당하는 데이터의 active 속성의 값을 반전 const onToggle = id => setUsers(users.map(user => user.id === id ? {...user, active:!user.active} : user)) //활성화된 user 개수를 세는 함수 호출 const count = countActiveUser(users); return( <div> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성화된 유저 수: {count}</div> </div> ) } export default App;
2) useMemo를 이용해서 해결
import React from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import {useState, useRef, useMemo} from 'react'; //active 가 true 인 데이터의 개수 const countActiveUser = (users) => { console.log("사용자 수를 세기"); return users.filter(user => user.active).length; } const App = () => { const [inputs, setInputs] = useState({ username:'', email:'' }); const {username, email} = inputs; const onChange = (e) => { //state를 수정할 때는 원본을 복제한 후 수정 setInputs({ ...inputs, [e.target.name]:e.target.value }); } //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록 //state로 생성 const [users, setUsers] = useState([ {id:1, username:'adam', email:'itstudy@kakao.com', active:false}, {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true}, {id:3, username:'카리나', email:'karina@sm.com', active:true} ]); //변수를 생성 const nextId = useRef(4); //데이터 삽입 메서드 const onCreate = () => { //하나의 객체 생성 const user = { id:nextId.current, username, email } //users 에 user를 추가 setUsers([...users, user]); //입력 요소 초기화 setInputs({ username:'', email:'' }) //다음 id를 위해서 id를 1증가 nextId.current += 1; } //삭제하는 함수 //users state 에서 id가 id 인 데이터 삭제 //id 가 일치하지 않는 데이터만 삭제 //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서 //수정합니다. const onRemove = id => setUsers(users.filter(user => user.id !== id)); //수정하는 메서드 //id에 해당하는 데이터의 active 속성의 값을 반전 const onToggle = id => setUsers(users.map(user => user.id === id ? {...user, active:!user.active} : user)) //활성화된 user 개수를 세는 함수 호출 //users 에 변화가 생긴 경우만 함수를 호출하고 //그 이외의 경우는 결과를 복사하도록 수정 const count = useMemo(()=>countActiveUser(users), [users]); return( <div> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성화된 유저 수: {count}</div> </div> ) } export default App;
3)새로운 컴포넌트를 추가해서 useMemo 동작을 확인
> 하나의 숫자 데이터를 추가하면 추가될 때 마다 평균을 구해서 출력하는 컴포넌트를 생성
# Average.jsx 생성
🗂react_hooks -> 📁 src -> 📄 Average.jsx
import React, {useState} from 'react'; //배열의 평균을 구해서 리턴해주는 함수 const getAverage = (numbers) => { console.log("평균 계산"); if(numbers.length === 0) return 0; //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴 //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고 //두번째 매개변수는 연산을 시작할 때의 초기값입니다. //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정 //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데 //첫번째는 누적값이고 두번째는 배열의 요소 //세번째는 배열의 인덱스이고 네번째는 배열 //[10, 20, 130, 240] //10+20=30, 30+130=160, 160+240=400 400리턴 const sum = numbers.reduce((a, b) => a + b); return sum / numbers.length; } const Average = () => { //숫자 배열 const [list, setList] = useState([]); //입력받은 내용 const [number, setNumber] = useState(''); //input에 내용을 수정할 때 호출될 메서드 const onChange = e => {setNumber(e.target.value)}; //추가를 눌렀을 때 호출될 메서드 const onInsert = e => { const nextList = list.concat(parseInt(number)); setList(nextList); setNumber(''); } return( <div> <input value={number} onChange={onChange}/> <button onClick={onInsert}>추가</button> <ul> {list.map((value, index) => ( <li key={index}>{value}</li> ))} </ul> <div> <b>평균:</b>{getAverage(list)} </div> </div> ) } export default Average;
> App.js 파일에 배치하고 숫자 데이터를 입력하면서 콘솔을 확인 - 입력 도중에 평균을 계산하는 함수를 호출
# Average.jsx 파일에 useMemo를 import 하고 getAverage 호출하는 부분을 useMemo를 이용하도록 수정
> 다시 실행을 하면 입력 도중에는 함수를 호출하지 않고 추가를 눌러서 list에 변화가 생긴 경우만 함수를 호출함import React, {useState, useMemo} from 'react'; //배열의 평균을 구해서 리턴해주는 함수 const getAverage = (numbers) => { console.log("평균 계산"); if(numbers.length === 0) return 0; //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴 //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고 //두번째 매개변수는 연산을 시작할 때의 초기값입니다. //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정 //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데 //첫번째는 누적값이고 두번째는 배열의 요소 //세번째는 배열의 인덱스이고 네번째는 배열 //[10, 20, 130, 240] //10+20=30, 30+130=160, 160+240=400 400리턴 const sum = numbers.reduce((a, b) => a + b); return sum / numbers.length; } const Average = () => { //숫자 배열 const [list, setList] = useState([]); //useMemo를 이용해서 getAverage를 호출 //list에 변화가 생긴 경우만 메서드를 호출하고 //그 이외의 경우는 결과를 재사용합니다. const avg = useMemo(() => getAverage(list), [list]) //입력받은 내용 const [number, setNumber] = useState(''); //input에 내용을 수정할 때 호출될 메서드 const onChange = e => {setNumber(e.target.value)}; //추가를 눌렀을 때 호출될 메서드 const onInsert = e => { const nextList = list.concat(parseInt(number)); setList(nextList); setNumber(''); } return( <div> <input value={number} onChange={onChange}/> <button onClick={onInsert}>추가</button> <ul> {list.map((value, index) => ( <li key={index}>{value}</li> ))} </ul> <div> <b>평균:</b>{avg} </div> </div> ) } export default Average;
'스터디 > KAKAOCLOUDSCHOOL' 카테고리의 다른 글
[React] 개발자 지망생 스터디 - 27일차 (0) 2022.12.08 [React] 개발자 지망생 스터디 - 26일차 (0) 2022.12.07 [React] 개발자 지망생 스터디 - 24일차 (0) 2022.12.06 [Node, React] 개발자 지망생 스터디 - 23일차 (0) 2022.12.05 [Node] 개발자 지망생 스터디 - 22일차 (1) 2022.12.01