카테고리 없음

[포스코x코딩온] 풀스택 부트캠프 11주차 정리 -2 React(map, filter, ref, lifeCycle, Hooks)

김선지 2024. 1. 7. 13:24

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를 넣어줘야한다.