본문 바로가기

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

[2024 여름방학 React.js 스터디] 이가인 #4주차

반응형

10. useRef 로 특정 DOM 선택

  • ref, useRef 의미
    • ref는 React에서 DOM 요소나 클래스 인스턴스를 참조할 수 있게 해주는 속성이며 주로 직접 DOM 접근이 필요한 경우 사용
    • useRef는 함수형 컴포넌트에서 ref를 사용할 때 사용하는 React Hook.
      • useRef는 렌더링 간에 값을 유지할 수 있도록 함
      • ref와 함께 DOM 요소나 값을 참조하는 데 사용됨
  • react에서 ref를 사용하는이유
    1. DOM 접근: React의 가상 DOM은 실제 DOM을 추상화하기 때문에, 직접 DOM을 조작해야 할 때 ref를 사용함. (ex 포커스를 설정하거나, 스크롤 위치를 조절할 때 )
    2. 자식 컴포넌트의 접근: 클래스 기반 컴포넌트에서 자식 컴포넌트의 메서드나 인스턴스에 접근해야 할 때 ref를 사용함. 함수형 컴포넌트에서는 useRef를 통해 접근할 수 있음
    3. 비동기 작업 처리: 특정 DOM 요소에서 비동기 작업(예: AJAX 호출, 타이머 등)을 실행할 때 ref가 필요할 수 있다.

useRef

  • javascript에서 특정 Dom을 선택하는 역할 ex) getElementById, querySelector
  • 특정 DOM에 접근할 때 사용한다.
  • 외부 라이브러리 사용할때 유용하다.
  • 원하는 위치에 ref={} 의 형태로 작성하면 된다.
  • 포커스를 잡으려면 nameInput.current.focus() 형태로 작성하면 된다.

DOM이란 예를 들어, HTML 문서에서 <div>, <input>, <button> 같은 태그 각각이 DOM 요소임.

React에서는 직접 DOM 요소를 조작하지 않고 상태(state)와 props를 통해 UI를 업데이트하는 것을 권장함. 그러나 특정 상황에서는 직접 DOM 요소에 접근해야 할 때가 있는데 이때 ref를 사용하여 DOM 요소를 참조한다!

 

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

function InputSample() {
  // 상태 변수 inputs를 초기화, setInputs는 inputs을 제어하는 setter함수,
  // inputs 객체에서 name과 nickname을 빈 문자열로 설정
  const [inputs, setInputs] = useState({
    name: "",
    nickname: ""
  });

  // nameInput에 ref를 설정하여 DOM 요소에 접근할 수 있게 함
  const nameInput = useRef();

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

  // input 값이 변경될 때 호출되는 함수
  const onChange = (e) => {
    // e.target에서 name과 value 값을 추출
    const { value, name } = e.target;
    // setInputs 함수를 통해 inputs 상태를 업데이트
    setInputs({
      ...inputs, // 기존 inputs 객체를 복사한 뒤
      [name]: value // name 키를 가진 값을 새로운 value로 설정
    });
  };

  // 초기화 버튼이 클릭될 때 호출되는 함수
  const onReset = () => {
    // inputs 상태를 초기값으로 설정
    setInputs({
      name: "",
      nickname: ""
    });
    // nameInput에 포커스를 설정
    nameInput.current.focus();
  };

  return (
    <>
      <input
        name="name" // input 요소의 name 속성을 'name'으로 설정
        placeholder="이름" // input 요소의 placeholder를 '이름'으로 설정
        onChange={onChange} // 값이 변경될 때 onChange 함수 호출
        value={name} // input 요소의 값을 상태 변수 name으로 설정
        ref={nameInput} // ref를 설정하여 nameInput에 접근할 수 있게 함
      />
      <input
        name="nickname" // input 요소의 name 속성을 'nickname'으로 설정
        placeholder="닉네임" // input 요소의 placeholder를 '닉네임'으로 설정
        onChange={onChange} // 값이 변경될 때 onChange 함수 호출
        value={nickname} // input 요소의 값을 상태 변수 nickname으로 설정
      />
      <button onClick={onReset}>초기화</button>
      {"  "}
      {/*버튼 클릭 시 onReset 함수 호출*/}
      <div>
        <b>값: </b>
        {name} ({nickname}) {/*현재 name과 nickname 값을 화면에 출력*/}
      </div>
    </>
  );
}

export default InputSample;

 

Q. 왜 nickname에서는 ref설정 안하는지?? 그리고 ref를 왜, 어떤 기준으로 설정하는지 정확하게는 모르겠음.

 

A. name에서 ref의 속성 중 focus 설정을 해놓았음. (nameInput.current.focus(); )

 

이때 focus 설정이란 웹 페이지나 애플리케이션에서 특정 입력 요소(예: 텍스트 입력 필드, 버튼 등)를 활성화하여 사용자가 바로 입력할 수 있도록 하는 것을 의미함.

 

>>focus 설정의 예시<<

  • 로그인 폼 예시:
    • 웹사이트에 로그인 폼이 있음
    • 사용자가 페이지를 로드하면, 커서가 자동으로 "사용자 이름" 입력 필드에 위치함
    • 사용자는 키보드를 사용하여 바로 사용자 이름을 입력할 수 있음
  • 검색 창 예시:
    • 웹사이트에 검색 창이 있음
    • 페이지가 로드되면, 검색 입력 필드가 선택됨
    • 사용자는 마우스를 사용하지 않고도 키보드로 검색어를 입력할 수 있음
예를 들면 우리가 초기 상태에서 네이버를 열면 자동으로 검색 창으로 커서가 위치해서 바로 검색을 할 수 있게 하는 것과 같다!

 

 

 

 

 


11장 배열 랜더링

리액트에서 배열을 렌더링 한번에 map할때는  key 라는 props 를 설정해야함!

 

key 값은 각 원소들마다 가지고 있는 고유값으로 설정을 해야 함. 지금의 경우엔 id 가 고유 값임

 

 

Q. 여기 user가 너무많은데 각 user이 각각 어떤 것이고 무슨 역할을 하는지 잘 모르겠다.

return (
    <div>
      {users.map(user => ( //users의 배열에서 순회하며(map) 객체(3개)를 user(변수)라고 선언.
        <User user={user} key={user.id} />   //User컴포의 props인 각 user 객체를 User 컴포넌트로 렌더링하고 
        컴포에서 고유한 key 속성을 설정
      ))}
    </div>
  );

A.

과정

  • map 함수는 배열의 첫 번째 요소 { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }를 user 변수에 할당하고 콜백을 실행한 후, 두 번째 요소, 세 번째 요소로 넘어감
  • user 변수는 users 배열의 각 요소를 순차적으로 가리킴

결론

  • users.map(user => (...))의 각 user는 users 배열의 서로 다른 요소를 가리킴
  • user 변수는 User 컴포넌트에 props로 전달되어 해당 사용자의 정보를 렌더링하는 역할을 함
  • key prop은 각 컴포넌트를 고유하게 식별하여 React가 효율적으로 컴포넌트를 업데이트할 수 있도록 도와줍니다. (컴포에서 실행되지 않아도 key props로 인해 효율적 업데이트 가능!!)

 

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

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

 

 

 

 

 

 


12장 useRef 객체 유지

useRef는 React 훅으로, 다음과 같은 경우에 사용:

  1. DOM 요소에 접근: 특정 DOM 요소에 직접 접근하고 조작해야 할 때 사용됨
  2. Mutable 객체 유지: 컴포넌트가 다시 렌더링될 때도 유지되는 변경 가능한 객체를 만들기 위해 사용됨 (useRef는 current 속성을 가지며, 이 속성은 변경해도 컴포넌트가 다시 렌더링되지 않음)
  • const nextId = useRef(4);:
    • nextId는 새로운 사용자의 ID를 추적
    • useRef(4)를 사용하여 초기값을 4로 설정합니다. 이 값은 컴포넌트가 다시 렌더링되어도 유지
  • const onCreate = () => { nextId.current += 1; };:
    • onCreate 함수는 새로운 사용자가 생성될 때 호출됨
    • nextId.current 값을 1 증가시켜 다음 사용자에게 새로운 고유 ID를 부여할 준비
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"
    }
  ];

  // 다음에 생성될 사용자의 ID를 추적하는 ref를 초기값 4로 생성
  const nextId = useRef(4);

  // 새로운 사용자를 생성할 때 호출되는 함수
  const onCreate = () => {
    // nextId.current 값을 1 증가시켜 다음 사용자 ID 준비
    nextId.current += 1;
  };

  // **UserList 컴포넌트에 users 배열을 prop으로 전달**
  return <UserList users={users} />;
}

export default App;

 

—> 결론적으로 이와 같이 useRef를 사용하면 컴포넌트가 다시 렌더링되더라도 nextId 값을 유지할 수 있으며, 이를 통해 고유한 ID를 부여할 수 있습니다

 

 

 

 

 

 


13장 항목에 배열 추가

 

CreateUser.js : App컴포의 props를 넘겨받아서 리턴값만 있음

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;

 

 

UserList.js : users배열들을 순회하며 순차적으로 map을 쓰고 받은 User와 UseList함수로 기록되는 값을 가짐.

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 : 전반적인(거의 모두의) 상태와 동작 관리

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

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 (
    <>
      <CreateUser />
      <UserList users={users} />
    </>
  );
}

export default App;

—> 버튼을 눌렀을 때 : input값들 초기화 안되며 동시에 밑에 배열이 추가가 안되는 상태임.

 

따라서 

 

1. input 값들을 제어해줄 로직 필요(onChange)

const [inputs, setInputs] = useState({
    username: "",
    email: "",
  });
  const { username, email } = inputs;
  
  
  const onChange = e => {
  // e.target에서 name과 value를 추출
  const { name, value } = e.target;
  
  // 기존의 inputs 객체를 복사한 뒤, 
  // 특정 name 키를 가진 값을 변경된 value로 설정
  setInputs({
    ...inputs, // 기존의 inputs 객체를 복사
    [name]: value // name 키를 가진 값을 value로 설정
  });
};

 

 

2. 배열 추가, 초기화 되는 로직 필요 (onCreate)

const onCreate = () => {
    const user = {
      id: nextId.current, // nextId.current 값을 id로 사용
      username, // 현재 입력된 username 값
      email, // 현재 입력된 email 값
    };

    // 새로운 사용자 객체(user)를 기존 사용자 목록(users)에 추가
    setUsers(users.concat(user));

    // 입력 필드를 초기화 (username과 email을 빈 문자열로 설정)
    setInputs({
      username: "",
      email: "",
    });

    // nextId.current 값을 1 증가시켜 다음 사용자 ID 준비
    nextId.current += 1;
  };

 배열에 항목 추가

  1. setUsers(users.concat(user)); concat (배열 내장 함수) 사용
  2. setUsers([...users, user]); spread 사용

 

 

 


14장 배열에 항목 제거

UserList.js : 각 User 컴포넌트를 보여줄 때, 삭제 버튼을 렌더링

import React from 'react';

function User({ user, onRemove }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button> 
      // 삭제 버튼이 클릭 될 때는 user.id 값을 앞으로 props 로 받아올 onRemove 함수의 파라미터로 넣어서 호출해주어야 함
      // onRemove는 "id 가 __인 객체를 삭제해라"인 역할
    </div>
  );
}

function UserList({ users, onRemove }) {
  return (
    <div>
      {users.map(user => (
        <User user={user} key={user.id} onRemove={onRemove} />
        //User컴포에게 전달
      ))}
    </div>
  );
}

export default UserList;

 

 

App.js : onRemove 함수를 구현

기존 App.js에서 onRemove 함수 생성만 하면 됨

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

배열에 항목 제거

  • filter (배열 내장 함수) 사용.
    • 이 함수는 배열에서 특정 조건이 만족하는 원소들만 추출하여 새로운 배열을 만들어줌

 

 

 

 


15장 배열 항목 수정

계정명을 클릭했을때 색상이 초록색으로 바뀌고, 다시 누르면 검정색으로 바뀌도록 구현
  1. App 컴포
    • users 배열 안의 객체 안에 active 라는 속성을 추가
  2. UserList컴포
    • active 값에 따라 폰트의 색상을 바꿔주도록 구현
    • cursor 필드를 설정하여 마우스를 올렸을때 커서가 손가락 모양으로 변하도록 구현
  3. App컴포에서 onToggle 함수 구현
    • map 사용(배열의 불변성을 유지하면서 배열을 업데이트)
  4. UserList 컴포
    • onToggle를 받아와서 User 에게 전달
    • onRemove 를 구현했었던것처럼 onToggle 에 id 를 넣어서 호출

 

App.js (기존 App에서 추가된 것만)

 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 onToggle = (id) => {
    setUsers(   // setUsers 함수를 호출하여 users 상태를 업데이트
      users.map((user) =>
        user.id === id ? { ...user, active: !user.active } : user)
      // 만약 현재 순회 중인 user 객체의 id가 매개변수로 전달받은 id와 같으면
      // user 객체를 복사한 뒤 active 속성을 반전시킨 새로운 객체로 대체
      // id가 일치하지 않는 경우 기존 user 객체를 그대로 반환
    );
  };
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
    </>
  );

 

 

UserList.js

import React from "react";

function User({ user, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: "pointer",
          color: user.active ? "green" : "black",
        }}
        onClick={() => onToggle(user.id)}   
      > //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;

반응형