본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024 여름방학 React.js 스터디] 이정욱 #4주차 react 하기 힘들어요

반응형

10. useRef 로 특정 DOM 선택하기

특정 DOM 을 선택하기 위해서 DOM selector를 사용할 수도 있지만, ex) getElementById

리액트를 사용하는 프로젝트에서도 가끔 DOM 을 직접 선택해야 하는데
그때 사용하는 것이 UseRef() 이다.

 

함수형 컴포넌트에서 ref를 사용하려면 useRef 라는 Hook 함수를 사용해준다.

 

사용하는 법은

 

1.react 라이브러리에서 useRef를 불러온다

import React, { useState, useRef } from 'react';

 

2. useRef 객체를 생성해준다.

const nameInput = useRef();

 

3. 생성한 객체를 선택하고 싶은 DOM 에서 ref 값으로 지정한다.

<input
        name="name"
        ref={nameInput}
/>

 

4. 선택한 DOM을 참조하고 싶을 때 해당 객체의 .current 값은 해당 객체를 가리킨다.

  const onReset = () => {
    setInputs({
      name: '',
      nickname: ''
    });
    nameInput.current.focus();
  };

11. 배열 렌더링하기

만약 아래와 같은 배열이 있다면, 컴포넌트로 어떻게 렌더링 해야할까?

const users = [
  {
    id: 1,
    username: 'velopert',
    email: 'public.velopert@gmail.com'
  },
  {
    id: 2,
    username: 'tester',
    email: 'tester@example.com'
  },
  {
    id: 3,
    username: 'liz',
    email: 'liz@example.com'
  }
];

 

우선 기본적으로 그대로 코드를 작성하는 방법이 있다

 

UserList.js

import React from 'react';

const UserList = () => {
    const users = [
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com'
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com'
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com'
        }
    ];
    return (
        <div>
            <div>
                <b>{users[0].username}</b> <span>({users[0].email})</span>
            </div>
            <div>
                <b>{users[1].username}</b> <span>({users[1].email})</span>
            </div>
            <div>
                <b>{users[2].username}</b> <span>({users[1].email})</span>
            </div>
        </div>
    );
}

export default UserList;

 

이렇게 세 개의 User를 직접 입력하는 방법으로도 아래와 같이 화면을 띄울 수 있다.

그러나 비슷한 내용이 반복되는 데 이를 일일이 수작업 하는건 비효율적이다.

그러므로 User라는 컴포넌트를 생성해 주고 이를 사용한다

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}

위에서 만든 컴포넌트를 아래에서와 같이 사용해준다

  return (
    <div>
      <User user={users[0]} />
      <User user={users[1]} />
      <User user={users[2]} />
    </div>
  );

그래서 다음과 같이 나타낼 수 있지만 이렇게 하면 인덱스도 수작업으로 작성되기 때문에
만약에 동적인 배열을 나타내려면(user가 3명이 아니고 4명, 5명, 100명으로 늘어나면) 인덱스를
하나하나 조회하는 것이 아닌 배열의 내장함수 map() 을 써준다.

  return (
    <div>
      {users.map(user => (
        <User user={user} />
      ))}
    </div>
  );
}

다음이 map() 함수를 써준 모습인데 이렇게 랜더링을 하면 내용을 잘 뜨지만 warning 이라고 뜬다

과연 문제는 뭘까?

 

key를 지정해주지 않았기 때문인데 warning은 error는 아니기 때문에 불러올 수는 있지만, 비효율적인 문제점이 있을 때
이를 나타내기 위해서 알려준다.

 

즉 key를 지정해주지 않으면 배열의 index 값을 key로 사용하여 불러오게 되는데 이렇게 하면 배열이 업데이트 될 때
효율적으로 랜더링 되지 못한다.

 

왜 효율적이지 않을까??

만약 key 가 없다면 배열에 새로운 값을 삽입하게 되면 기존 값들의 사이에 삽입 되는 것이 아니라 실제로는

삽입될 순서에 맞게 모든 배열을 업데이트 해준다

여기 이미지를 보면 b와 c 사이에 z를 삽입하면 실제로는
1. 기존의 c가 z로 바뀌고 c부터 한 칸씩 밀려나는 순서로 업데이트 된다.

 

그러나 key 값이 있다면 그 순서에 맞게 원하는 곳에 내용의 삽입, 삭제가 가능하다

12. useRef 로 컴포넌트 안의 변수 만들기

우리는 useRef를 통해 컴포넌트에서 특정 DOM 을 선택했었다!
그러나 useRef에는 해당 기능 말고도 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리가 가능하다.
이 변수를 통해 다음과 같은 값이 관리가 가능하다

  • setTimeout, setInterval 을 통해서 만들어진 id
  • 외부 라이브러리를 사용하여 생성된 인스턴스
  • scroll 위치

 

userRef를 사용하여 변수를 관리하려면 해야하는 작업이 있는데

우선 컴포넌트 내부에서 사용할 변수를 App에서 선언하고 해당 컴포넌트에 props로 전달해준다.

App.js

import UserList from "./UserList";

const App = () => {
    const users = [
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com'
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com'
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com'
        }
    ];
    return <UserList users={users} />;
}

export default App;

그러면 UsersList에서 props로 users를 받은 뒤 해당 props를 통해 랜더링해준다.

 

UsersList.js

import React from 'react';

const User = ({ user }) => {
    return (
        <div>
            <b>{user.username}</b> <span>({user.email})</span>
        </div>
    );
}

const UserList = ({ users }) => {
    return (
        <div>
            {users.map(user => (
                <User user={user} key={user.id} />
            ))}
        </div>
    );
}

export default UserList;

이제 App.js 에서 useRef() 를 사용하여 nextId라는 변수를 만들어준다

 

App.js

  const nextId = useRef(4);
  const onCreate = () => {
    // 나중에 구현 할 배열에 항목 추가하는 로직
    // ...

    nextId.current += 1;
  };

 

여기서 useRef의 파라미터로 들어가는 4는 뭘까?? 이는 10장에서 사용했던 .current 값의 초기 값이 된다.
즉 nextId.current 의 초기값이 4가 되는 것이다~

 

13. 배열에 항목 추가하기

이제 배열에 새로운 항목을 추가하는 법을 알아보자

우선 input 두개와 button 하나로 이루어진 CreateUser.js 라는 컴포넌트를 만들어준다

 

CreateUser.js

import React from 'react';

const CreateUser = ({ username, email, onChange, onCreate }) => {
    return (
        <div>
            <input
                name="username"
                placeholder="계정명"
                onChange={onChange}
                value={username}
            />
            <input
                name="email"
                placeholder="이메일"
                onChange={onChange}
                value={email}
            />
            <button onClick={onCreate}>등록</button>
        </div>
    );
}

export default CreateUser;

 

그리고 해당 컴포넌트를 App.js 에서 추가하여 렌더링해준다.

  return (
    <>
      <CreateUser />
      <UserList users={users} />
    </>
  );

다음과 같이 잘 뜨는 모습

이를 통해 우리가 만들고 싶은 것은 계정명이메일을 작성하면 새로운 User가 조회되는 것!

그렇다면 실시간으로 작성되는 값을 어떻게 User에 추가할 수 있을 까?

그걸 위해 우리는 UseState를 사용한다

  const [inputs, setInputs] = useState({
    username: '',
    email: ''
  });
  const { username, email } = inputs;
  const onChange = e => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value
    });
  };

기억을 되짚어 보자면 useState를 통해 관리할 State 를 생성해주고 

  const [users, setUsers] = useState([
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com'
    }
  ]);

그리고 해당 계정명과 이메일을 통해 새로운 '유저'를 만들어야 하기 때문에 이제 users도 state로써 관리되어야 한다.

  const onCreate = () => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers([...users, user]);

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  };

setUsers와 setInputs를 통해 user 배열에 값을 추가해준다

      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />

그리고 onChange와 onCreate를 통해 이벤트가 발생 시 새로운 유저가 작성되도록 한다

 

새로운 유저를 만드는 방법으로 concat이라는 메소드도 사용이 가능한데

setUsers(users.concat(user));

차이점은 기존의 배열을 수정하는 것이 아닌, 새로운 원소가 추가된 새로운 배열을 만들어준다.

 

14. 배열에 항목 제거하기

그렇다면 배열에서 항목을 제거하려면??
일단 제거할 항목을 선택하기 위해 User컴포넌트를 보여줄 때, 옆에 삭제 버튼도 렌더링 해준다.

UserList.js

import React from 'react';

const User = ({ user }) => {
    return (
        <div>
            <b>{user.username}</b> <span>({user.email})</span>
            <button onClick={() => onRemove(user.id)}>삭제</button>
        </div>
    );
}

const UserList = ({ users }) => {
    return (
        <div>
            {users.map(user => (
                <User user={user} key={user.id} onRemove={onRemove}/>
            ))}
        </div>
    );
}

export default UserList;

 

App.js

  const onRemove = id => {
    // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
    // = user.id 가 id 인 것을 제거함
    setUsers(users.filter(user => user.id !== id));
  };

위와 같은 함수를 만들어 준 뒤에

  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} />
    </>
  );

이를 UserList에 props로 넘겨준다

삭제가 잘 실행되는 모습

15. 배열 항목 수정하기

이제 배열 항목을 수정하는 법을 알아보자!

우선 특정 배열 항목을 수정을 위해 선택했다는 표시로 계정명을 클릭하면 초록색으로 바뀌고 다시 누르면 검정색으로 바뀌도록 해준다.

 

이를 위해 users에 active라는 속성을 만들어주고, 계정명에 커서가 올라가면 손가락 모양으로 커서가 바뀌도록 한다

 

App.js

import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

const App = () => {
    const [inputs, setInputs] = useState({
        username: '',
        email: ''
    });
    const { username, email } = inputs;
    const onChange = e => {
        const { name, value } = e.target;
        setInputs({
            ...inputs,
            [name]: value
        });
    };
    const [users, setUsers] = useState([
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com',
            active: true
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com',
            active: false
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com',
            active: false
        }
    ]);

    const nextId = useRef(4);
    const onCreate = () => {
        const user = {
            id: nextId.current,
            username,
            email
        };
        setUsers(users.concat(user));

        setInputs({
            username: '',
            email: ''
        });
        nextId.current += 1;
    };

    const onRemove = id => {
        // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
        // = user.id 가 id 인 것을 제거함
        setUsers(users.filter(user => user.id !== id));
    };

    return (
        <>
            <CreateUser
                username={username}
                email={email}
                onChange={onChange}
                onCreate={onCreate}
            />
            <UserList users={users} onRemove={onRemove} />
        </>
    );
}

export default App;


그리고 이제 누르면 초록색 혹은 검정색으로 바뀌도록 onToggle 이라는 함수를 구현해본다.

  const onToggle = id => {
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user
      )
    );
  };

동일하게 모든 user에 동적으로 적용하기 위해 map을 써준다
위의 로직은 모든 user 배열의 값들을 돌아보며 선택한 계정명의 user.id와 일치하면 값을 바꾸어준다.

 

그리고 UserList.js에서 적용하기 위해 props를 받아와 onclick 시 onToggle 이 실행되도록 한다.

UserList.js

import React from 'react';

const User = ({ user, onRemove, onToggle }) => {
    return (
        <div>
            <b
                style={{
                    cursor: 'pointer',
                    color: user.active ? 'green' : 'black'
                }}
                onClick={() => onToggle(user.id)}
            >
                {user.username}
            </b>
            &nbsp;
            <span>({user.email})</span>
            <button onClick={() => onRemove(user.id)}>삭제</button>
        </div>
    );
}

const UserList = ({ users, onRemove, onToggle }) => {
    return (
        <div>
            {users.map(user => (
                <User
                    user={user}
                    key={user.id}
                    onRemove={onRemove}
                    onToggle={onToggle}
                />
            ))}
        </div>
    );
}

export default UserList;

짜잔 잘된다... 헤헤

16. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기

이번에는 useEffect 라는 Hook을 사용하여 컴포넌트가 마운트 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 그리고 업데이트 될 때 (특정 props가 바뀔 때) 에 따라서 특정 작업을 처리해본다.

1) 마운트/언마운트

useEffect를 사용할 때 첫번째 파라미터로는 함수, 두번째 파라미터는 의존값이 들어있는 배열 (deps) 를 넣는다.
만약 deps 배열을 비우게 되면 컴포넌트가 처음 나타날때에만 useEffect에 등록한 함수가 호출된다.

 

UsersList.js

const User = ({ user, onRemove, onToggle }) => {
    useEffect(() => {
        console.log('컴포넌트가 화면에 나타남');
        return () => {
            console.log('컴포넌트가 화면에서 사라짐');
        };
    }, []);

이렇게하면 컴포넌트가 마운트 될 때 useEffect에 등록한 함수
() => {
        console.log('컴포넌트가 화면에 나타남');
        return () => {
            console.log('컴포넌트가 화면에서 사라짐');
        };
    } 

 

를 호출하고 deps가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup함수가 호출된다.         
return () => {
            console.log('컴포넌트가 화면에서 사라짐');
        };

deps 에 특정 값 넣기

그렇다면 deps를 비우지 않고 특정 값을 넣는다면?

-> 컴포넌트가 처음 마운트 될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 된다.

그리고 deps 안에 특정 값이 있다면 언마운트시에도 호출이되고, 값이 바뀌기 직전에도 호출이 된다.

 

UserList.js

  useEffect(() => {
    console.log('user 값이 설정됨');
    console.log(user);
    return () => {
      console.log('user 가 바뀌기 전..');
      console.log(user);
    };
  }, [user]);

이렇게 해주면 deps에 넣어준 user는 state이므로 해단 state가 최신인지 확인할 수 있다.

 

deps 파라미터를 생략하기

deps 파라미터를 생략한다면 컴포넌트가 리렌더링 될 때마다 호출이 된다.

  useEffect(() => {
    console.log(user);
  });

다음과 같이 생략한다면 어떻게 될까?

 

우리는 User 말고 State가 하나 더 있다.

바로 Input 이다. 그러므로 input 창에 입력을 할 때마다 state가 변화되어 리렌더링 하므로 게속 호출이 된다..

반응형