본문 바로가기

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

[2024 React.js 스터디] 김지나 #5주차

반응형

10. useRef로 특정 DOM 선택하기

- 자바스크립트를 사용할 때, 특정 DOM 선택해야 하는 상황에 getElementById, querySelector 같은 DOM Selector 함수를 사용

- 리액트를 사용하는 프로젝트에서 DOM을 직접 선택해야 하는 상황에서는 ref를 사용

- 함수형 컴포넌트에서 ref를 사용할 때에는 useRef라는 Hook 함수 사용

- 클래스형 컴포넌트에서는 콜백 함수 or React.createRef 함수 사용

 

- 초기화 버튼을 클릭했을 때 이름 input에 포커스가 잡히도록 useRef를 사용해서 구현

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

function InputSample() {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });
  const nameInput = useRef();

  const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출

  const onChange = e => {
    const { value, name } = e.target; // e.target에서 name과 value를 추출
    setInputs({
      ...inputs, // 기존의 input 객체를 복사
      [name]: value // name키를 가진 값을 value로 설정
    });
  };

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

  return (
    <div>
      <input
        name="name"
        placeholder="이름"
        onChange={onChange}
        value={name}
        ref={nameInput}
      />
      <input
        name="nickname"
        placeholder="닉네임"
        onChange={onChange}
        value={nickname}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
}

export default InputSample;

= useRef()를 사용하여 Ref 객체 생성 -> 이 객체를 선택하고 싶은 DOM에 ref 값으로 설정

-> Ref 객체의 .current 값은 우리가 원하는 DOM을 가르킴

 

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'
  }
];

- 만약 이 내용을 컴포넌트로 렌더링한다면?

1) 그대로 코드를 작성하는 방법

//UserList.js
import React from 'react';

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

function 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>
      <User user={users[0]} />
      <User user={users[1]} />
      <User user={users[2]} />
    </div>
  );
}

export default UserList;

- 배열의 인덱스를 하나하나 조회해가면서 렌더링하는 방법은 동적인 배열을 렌더링하지 못 함.

- 동적인 배열을 렌더링 해야 할 때에는 자바스크립트 배열의 내장함수 map()을 사용

- map() 함수: 배열 안에 있는 각 원소를 변환하여 새로운 배열 생성

//UserList.js
import React from 'react';

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

function 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>
      {users.map(user => (
        <User user={user} />
      ))}
    </div>
  );
}

export default UserList;

- 하지만 브라우저에서 콘솔을 열어보면 에러가 뜸

=> 리액트에서 배열을 렌더링 할 때에는 key 라는 props 를 설정해야 함. key 값은 각 원소들마다 가지고 있는 고유값으로 설정

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

 

- 만약 배열 안의 원소가 가지고 있는 고유값이 없다면 map() 함수를 사용할 때 설정하는 콜백 함수의 두번째 파라미터 index를 key로 사용

<div>
  {users.map((user, index) => (
    <User user={user} key={index} />
  ))}
</div>

 

- 배열 렌더링 할 때 key 설정을 하지 않게 된다면 기본적으로 배열의 index 값을 key로 사용하게 되고 경고 메시지가 뜸

- 경고 메시지가 뜨는 이유는 각 고유 원소에 key가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있기 때문

 

- key의 존재 유무에 따른 업데이트 방식

- 배열 가정

const array = ['a', 'b', 'c', 'd'];

 

- 렌더링 가정

array.map(item => <div>{item}</div>);

 

- b와 c 사이에 z를 삽입하게 된다면 리렌더링을 하게 될 때 <div>b</div>, <div>c</div> 사이에 새 div 태그가 들어가는 게 아니라

c가 z로 바뀌고, d가 c로 바뀌고, 마지막에 d가 새로 삽입됨

a를 제거하게 된다면, 기존의 a가 b로 바뀌고, b는 z로 바뀌고, z는 c로 바뀌고, c는 d로 바뀌고, 마지막에 d가 제거됨..........

완전 비효율적임... 하지만 key가 있다면 이 작업이 개선됨

key를 사용하면 원하는 곳에 내용을 삽입하거나 삭제할 수 있음

 

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

- useRef Hook은 DOM을 선택하는 용도 외에도, 컴포넌트 안에서 조회 및 수정할 수 있는 변수를 관리하는 용도로도 쓰임

- useRef로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않음. useRef로 관리하고 있는 변수는 설정 후 바로 조회 가능

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

 

- UserList 내부에서 배열을 직접 선언해서 사용하는 대신에, 배열을 App에서 선언하고 UserList에게 props로 전달하기

//App.js
import React from 'react';

import UserList from './UserList';

function 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;
//UserList.js
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에서 useRef()를 사용하여 nextId라는 변수 만들기

//App.js
import React, { useRef } from 'react';
import UserList from './UserList';

function 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'
    }
  ];

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

    nextId.current += 1;
  };
  return <UserList users={users} />;
}

export default App;

- useRef()를 사용할 때 파라미터를 넣어주면 이 값이 .current 값의 기본값이 됨.

- 이 값을 수정, 조회할 때에는 .current 값을 수정, 조회하면 됨.

 

13. 배열에 항목 추가하기

- 불변성 지키기 (push, splice, sort 함수 사용 x)

1) spread 연산자 사용

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

function 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'
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com'
    }
  ]);

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

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  };
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} />
    </>
  );
}

export default App;

 

2) concat 함수 사용

: 기존의 배열을 수정하지 않고 새로운 원소가 추가된 새로운 배열 생성

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

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  };
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} />
    </>
  );
}

export default App;

 

14. 배열에 항목 제거하기

- 삭제 버튼이 클릭될 때는 user.id 값을 앞으로 props로 받아올 onRemove 함수의 파라미터로 넣어서 호출

- onRemove 함수는 UserList에서도 전달받고 이를 그대로 User 컴포넌트에게 전달

- 불변성을 지켜야 함

=> filter 배열 내장 함수 사용: 배열에서 특정 조건이 만족하는 원소들만 추출하여 새로운 배열 생성

  const onRemove = 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;

- onRemove 구현

 

15. 배열에 항목 수정하기

- active 속성 추가, active 값에 따라 폰트의 색상을 바꿔주고, cursor 필드를 설정해서 마우스를 올렸을 때 커서가 손가락 모양으로 변하도록 구현

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

function 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 => {
    setUsers(users.filter(user => user.id !== id));
  };

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

export default App;

- onToggle 함수 구현, id 값을 비교해서 id 가 다르면 그대로 두고 같다면 active 값을 반전

- UserList 컴포넌트에서 onToggle 받아와서 User에게 전달해주고 onToggle에 id 를 넣어서 호출

import React from 'react';

function 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>
  );
}

function 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를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기

 

- 마운트 / 언마운트

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log('컴포넌트가 화면에 나타남');
    return () => {
      console.log('컴포넌트가 화면에서 사라짐');
    };
  }, []);
  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>
  );
}

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

export default UserList;

- useEffect를 사용할 때, 첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열을 넣음

만약 deps 배열을 비우게 된다면, 컴포넌트가 처음 나타날 때에만 useEffect에 등록한 함수가 호출됨

- useEffect에서는 함수를 반환할 수 있는데 이를 cleanup 함수라고 부름

deps가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출됨

-마운트시에 주로 하는 작업들: props로 받은 값을 컴포넌트의 로컬 상태로 설정, 외부 API 요청, 라이브러리 사용, setInterval을 통한 반복작업 또는 setTimeout을 통한 작업 예약

- 언마운트시에 주로 하는 작업들: setInterval, setTimeout을 사용하여 등록한 작업들 clear, 라이브러리 인스턴스 제거

 

- deps에 특정 값 넣기

- deps에 특정 값을 넣게 된다면, 컴포넌트가 처음 마운트 될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출. deps 안에 특정 값이 있다면 언마운트 시에도 호출이 되고 값이 바뀌기 직전에도 호출이 됨
- useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어주어야 함

 

- deps 파라미터를 생략하기

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

(리액트 컴포넌트는 기본적으로 부모컴포넌트가 리렌더링 되면 자식 컴포넌트 또한 리렌더링이 됨)

 

반응형