[포스코x코딩온] 풀스택 부트캠프 11주차 정리 -2 React(map, filter, ref, lifeCycle, Hooks)
map, filter
list 같은 것을 서버에서 가져왔을 때 그 수만큼의 iteration을 통해 이에 대한 개수의 태그를 넣을 때 이용하는 jsx함수이다. js의 map과 비슷하지만 살짝 다르다.
// 함수형 컴포넌트 안에 있다고 가정한다.
// 함수형 컴포넌트의 return이다.
const [users, setUsers] = useState([
{
name: "코디",
email: "codi@gmail.com",
},
{ name: "선지훈", email: "sfffs0@naver.com" },
]);
// filter를 이용해서 새로운 배열을 만들고 이를 user State에 할당.
// 직접적으로 할당하면 안되서 새로운 배열을 만들고 setUser를 이용해야한다.
const remove = (e, value) => {
const removedItems = users.filter((user) => {
return user.name !== value.name;
})
setUsers(removedItems);
}
return (
<ul>
{users.map((value, idx) => {
return (
<li key={value.email} onDoubleClick={ (e) => {
remove(e, value);
}}>
{value.name} : {value.email}
</li>
);
})}
</ul>
)
react의 map() 함수의 경우
일반 JS 배열로 return하는 것과 달리
알아서 일반 JS 배열을 tag의 iteration으로 쓸 수 있게 반환해준다.
**주의사항
map 함수를 쓸때 원시값을 대상으로 리턴하면 상관 없는데
배열.map으로 해서 참조값을 대상으로 include를 사용하게 된다면 주소값을 참조하는 것 같다.
그래서 같은 값을 가지고 있는 객체를 두개 만들어서 두 객체를 대상으로 (객체 1 == 객체 2) 로 해도 주소값으로만 비교해서 false 가 나오는것같다.
이걸 확인해보려고 주소값을 찍어보려고 했지만 js는 주소값에 접근할 권한이 없는 것 같다.
const a = {name: 'jihun'};
const b = {name: 'jihun'};
const c = b;
// 위에꺼는 둘다 false, 왜냐, == 해도 주소값으로 비교하는 듯.
console.log('a == b',a == b);
console.log('a === b', a === b);
// 이건 원시값이라서 둘 다 true
console.log(a.name == a.name);
console.log(a.name === a.name);
// 주소값이 똑같아서 둘 다 true
console.log('b == c', b == c);
console.log('b === c', b === c);
배열로 만약에 includes를 쓰려면 배열을 통째로 하는 것이 아닌 map으로 해서 개별 elem에서 . 접근법을 이용해서 거기에서의 원시값을 비교하거나 some 함수를 이용해야 할 것 같다.
애초에 된다고 하더라도 includes만 쓰면 하나의 true 값만 반환해서 안된다.
그러니까 어차피 filter와 includes를 같이 써야한다.
그러면 filter에서 elem이 나오고 elem에서 하나의 key에 접근을 하면 될 것 같다.
ime 한글 두번입력을 막으려면 이 속성을 하자.... 꼭
if(e.nativeEvent.isComposing) {
return;
}
e.isComposing 아니다.
(key up or key down 이벤트에 추가한다.)
Ref
직접적으로 태그에 접근하거나 지역변수로 이용할 수 있는 방법.
원래는 클래스형 컴포넌트에서만 사용할 수 있었는데 hook이 생겨서 함수형에서도 쓸 수 있다.
useRef method를 이용하자.
1. 태그 접근
import { useRef } from "react";
function RefFunction1() {
const inputRef = useRef();
function handleFocus () {
inputRef.current.focus();
}
return (
<>
<hr />
<p>함수형 컴포넌트</p>
{/* 선택하고 싶은 DOM 요소에 ref prop 설정 */}
<input type="text" ref={inputRef} />
<button onClick={handleFocus}>focus</button>
</> );
}
export default RefFunction1;
2. 지역변수
import { useRef, useState } from "react";
function RefFunction2 () {
const idRef = useRef(1);
const [id, setId] = useState(1);
let a = 1;
function plusIdRef() {
idRef.current++;
a++;
console.log('idREf:', idRef.current);
console.log('a:', a)
}
function plusIdState() {
setId(id+1);
}
return (
<>
// ref 로컬변수는 변수 값만 반영되고 있다가 렌더링 시(state update 등)
// 덤으로 같이 업데이트 된다.
// batch 처리방식이라 그런 것 같다.
<p>(함수형 컴포넌트) ref 로컬변수 사용</p>
<h2>idRef is... {idRef.current}</h2>
<button onClick={plusIdRef}>plus idRef</button>
// 설명은 생략
<p>(함수형 컴포넌트) State 사용</p>
<h2>state is... {id}</h2>
<button onClick={plusIdState}>plus idRef</button>
// 얘는 변수 값은 반영되서 콘솔에 찍히는데 렌더링 해도 값이 반영되지 않는다.
<p>(그냥 변수) 지역변수 a 사용</p>
<h2>{a}</h2>
</>
);
}
export default RefFunction2;
life Cycle
말 그대로 라이프싸이클인데 얘도 클래스형 컴포넌트에서만 사용 가능하다가 훅이 나와서 함수형에서도 사용 가능해졌다.
함수형 컴포넌트로 쓰는 게 편한데 이해하려면 클래스형에서 사용하는 방법을 먼저 보는 게 낫다.
차례로 마운트 될 때 실행되는 함수, 업데이트 될 때 실행되는 함수, 언마운트 될 때 실행되는 함수다.
마운트 되자마자 axios를 한다던가 할 때 유용할 것 같다.
import React, { Component } from 'react';
class LifeCycleClassChild extends Component {
componentDidMount() {
console.log('컴포넌트 마운트!');
}
componentDidUpdate() {
console.log('컴포넌트 업데이트!');
}
componentWillUnmount() {
console.log('컴포넌트 언마운트 예정');
}
render() {
return (
<div>
현재 Number 값은 {this.props.number}
</div>
);
}
}
export default LifeCycleClassChild;
함수형 컴포넌트: useEffect를 이용한다.
import React, { useState, useEffect } from "react";
function LifeCycleFuncChild({ number }) {
const [input, setInput] = useState("");
// Mount 시점에만 실행
useEffect(() => {
console.log("컴포넌트 마운트!!!");
}, []);
// Unmount 시점에 실행
useEffect(() => {
return () => {
console.log("컴포넌트 언마운트!!");
};
}, []);
// Mount or Update 시점에 실행
useEffect(() => {
console.log("컴포넌트 마운트 or 업데이트!!");
});
// input 상태가 update될 떄 실행
useEffect(() => {
console.log("마운트 or 또는 input 상태가 변경됨에 따라 컴포넌트 업데이트!");
}, [input])
return (
<>
자식 컴포넌트
<div>현재 Number 값은 {number} 입니다. </div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
</>
);
}
export default LifeCycleFuncChild;
Hooks
클래스형 컴포넌트의 기능들을 함수형에서도 이용할 수 있게 만든 함수들
use + somethin' 으로 시작하는 이름
usememo
useMemo함수를 이용하면 왼쪽은 함수가 아닌 변수임.
import { useMemo, useState } from "react";
// useMemo : 연산의 결과 값을 기억
function UseMemoEx() {
const [count, setCount] = useState(0);
const [input, setInput] = useState("");
// [before]
// 임의의 큰 연산을 하는 함수(가정)
// 버튼을 누를 떄, input을 입력할 때 둘 다 연산이 이루어짐 (calc 함수 호출)
// input 값이 바뀔 때는 연산 필요 x => useMemo 이용해서 특정 값을 기억하고 그 값이 바뀔 때만 연산되도록 최적화
// function calc() {
// console.log("열심히 계산중");
// for (let i = 0; i <= 3000000000; i++) {}
// return count ** 2;
// };
// [after]
// calc 실행되었을 때 return되는 값이 count와 관련 있기 때문에 의존배열: count
// 의존배열에 count를 넣어주면, count의 값이 바뀔 때만 calc 함수를 싱행
// 아래 calc는 useMemo를 이용한 변수이다. 화살표 함수가 아님
const calc = useMemo(() => {
console.log("열심히 계산중");
for (let i = 0; i <= 30000000; i++) {}
return count ** 2;
}, [count]);
return (
<>
<h1>UserMemo Ex</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
Plus Count
</button>
{/* input 태그에 값 입력시마다 input state값 바뀜 -> 리렌더링 -> 함수 호출 */}
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<p>count : {count}</p>
{/* before */}
{/* <p>calc: {calc()}</p> */}
{/* after */}
<p>calc : {calc}</p>
</>
);
}
export default UseMemoEx;
useCallback, useEffect
useEffect: 라이프 싸이클 함수를 이용할 수 있는 훅
useCallback: 렌더링 시 js 파일이 실행되기 때문에 함수가 계속 재정의 된다. 이를 캐시에 저장해서 의존성 배열 안의 값이 같은 한 재정의하지 않고 캐시에 있는 함수를 실행하는 기능
import { useState, useEffect, useCallback } from "react";
import axios from "axios";
function UseCallbackEx2({ postid }) {
const [post, setPost] = useState({});
// [before]
// const getPost = async () => {
// console.log('data fetching...');
// // 데이터 요청
// const res = await axios.get(`https://jsonplaceholder.typicode.com/posts/${ postid }`);
// setPost(res.data);
// }
// [after]
// useCallback 훅으로 메모이제이션(캐싱) -> 의존성 배열에 있는 postId가 변경되지 않는 한,
// 함수는 다시 생성되지 않음.
const getPost = useCallback(async () => {
console.log('data fetching...');
// 데이터 요청
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts/${ postid }`);
setPost(res.data);
}, [postid]);
// useEffect 의존성 배열에 "함수"
// 컴포넌트가 리렌더링 -> 함수 재생성 (주소값 변경) -> getPost 재호출
// 의존성 배열에 post를 넣으면 함수는 참조값이므로 getPost()를 이용해서 생성된 post의 참조값이 계속 달라져서 useEffect 무한 실행.
// 의존성 배열에 getPost를 넣으면 useCallback으로 postid가 바뀌지 않는 한 함수는 재정의되지 않으므로 한 번만 실행
// 애초에 이런 방식으로 useCallback과 useEffect를 같이 쓰는 듯.
useEffect(() => {
getPost();
}, [getPost]);
// useEffect(() => {
// getPost();
// }); -> 처음 mount 되거나 업데이트가 있을 시 getpost()를 실행
// ** State post를 객체값으로 설정했음에 주의
// 1. 처음에 렌더가 되었을 때 useEffect에 의해 getPost() 함수가 실행되어 state인 post의 값이 들어간다.
// 2. state인 post의 값이 변경되었으므로 렌더링. return에서 post.id가 참이 되어 로딩중이 사라지고 post.title이 마운트된다.
// 3. useCallback에 의해 주소값이 같은 함수를 사용하더라도 getPost()에 의해 setPost(res.data)가 실행되고 state의 post는 객체값이므로 주소값이 달라진다.
// 4. state인 post 값이 변경 되었으므로 렌더가 되고 useEffect에 의해 getPost()를 실행한다.
// 5. state가 변경되었으므로 react는 다시 페이지를 렌더링한다.
// 6. 페이지가 다시 렌더링 == (mount) 되었기 때문에 useEffect는 getPost를 실행한다.
// 7. 3~6번 무한반복
return ( <>
<h1>useCallback Ex2</h1>
{post.id ? post.title : '로딩중...'}
</> );
}
export default UseCallbackEx2;
async await으로 함수를 만들고 그 요청을 통해 얻은 값을 return하는 경우
나중에 그냥 함수를 이용하고 return 값을 변수에 할당하면
실행 함수 앞에 await을 붙여줄 수 없기 때문에 pending 오류가 발생한다. 그래서 return을 이용하지 않고 안에서 처리하거나
.then(function(res) {});를 이용해야 한다.
-----------------------------------------------------------------
useEffect는 동기함수라서 콜백함수에 async를 붙일 수 없다.
그래서 콜백함수 안의 함수에서 따로 async를 넣어줘야한다.