본문 바로가기

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

[2024 React.js 스터디] 이서영 #5주차 리액트 입문 (10장~16장)

반응형

10. useRef로 특정 DOM 선택

- 리액트 사용시 DOM을직접 선택해야 하는 상황 발생

ex) 특정 엘리먼트의 크기 가져오기, 포커스 설정,  외부 라이브러리 사용 ...등등

- ref 사용

: 함수형 컴포넌트에서 ref 사용시 useRef( Hook 함수 ) 사용

: 클래스형 컴포넌트 -> 콜백 함수 / React.createRef 함수 사용

 

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

 

InputSample.js

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>
  );
}
// onReset 함수에서 input에 포커스를 하는 focus() DOM API 호출
export default InputSample;

 

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

: Ref 객체의 .current 값 -> 원하는 DOM 가르킴

 

11. 배열 렌더링하기

(1) 그대로 코드 작성 (비효율, 가장 기본적)

(2) 배열의 내장함수 map() 사용

map() 

- 배열안에 있는 각 원소를 변환 -> 새로운 배열 생성

- 리액트에서 동적인 배열 렌더링해야할 때 사용 ( 일반 데이터 배열 -> 리액트 엘리먼트로 이루어진 배열로 변환 )

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;

 

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

bc) 리액트에서 배열을 렌더링 할 때는 key라는 props 설정

- key 값은 각 원소들마다 가지고 있는 고유값으로 설정 ( 지금 경우엔 id가 고유 값 )

 

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} key={user.id} />
      ))}
    </div>
  );
}

export default UserList;

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

 

if) 배열 렌더링 시 key 설정 x -> index값을 key로 사용하게 됨 -> 경고메세지가 뜸( 고유 원소에 key가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있기 때문 )

 

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

ex) 키가 없읕 때

const array = ['a', 'b', 'c', 'd']; //임의의 배열 가정

 

다음과 같이 렌더링

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

 

위 배열의  b와 c 사이에 z를 삽입

: 리렌더링 할 때 새 div 태그 삽입 x, 기존의 c -> z, d -> c, 맨 마지막에 d가 새로 삽입

: a를 제거, a -> b, b->z, z->c, c->d, 맨 마지막 d 제거

 

ex) 키가 있을 때

[
  {
    id: 0,
    text: 'a'
  },
  {
    id: 1,
    text: 'b'
  },
  {
    id: 2,
    text: 'c'
  },
  {
    id: 3,
    text: 'd'
  }
];
array.map(item => <div key={item.id}>{item.text}</div>);

수정되지 않는 기존의 값은 그대로 두고, 원하는 곳에 내용 삽입 or 삭제

: 따라서 배열을 렌더링 할 때에는 고유한 key값이 있는 것이 중요

 

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

useRef

- 특정 DOM 선택 할 때 사용

- 컴포넌트 안에서 조회 및 수정 할 수 있는 변수 관리 할 때 사용

 

useRef로 관리하는 변수

- 값이 바뀐다고 컴포넌트 리렌더링 x

- 설정 후 바로 조회 가능

- 다음 값 관리 가능

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

UseList 컴포넌트 내부에서 배열을 직접 선언해서 사용

-> 이 배열을 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()를 사용 -> nextld 변수 생성

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 값 수정 / 조회 시 .current값 조회

 

13. 배열에 항목 추가하기

 

(1) input 두개, button 하나로 이루어진 CreateUser.js 컴포넌트를 src디렉터리에 생성

 

CreateUser.js

import React from 'react';

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

 

(2) App에서 UserList위에 렌더링

(3) CreateUser 컴포넌트에게 필요한 props를 App에서 준비

-> 코드 작성 시 input에 값 입력 -> 등록버튼 눌렀을 때 input 값들 초기화 되는지 확인

(4) users, useState 사용, 컴포넌트의 상태로서 관리

 

변화 

배열에 변화를 줄 때 

-> 불변성 지켜야함 ( 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 함수 사용

: 기존의 배열 수정 x, 새로운 원소가 추가된 새로운 배열 생성

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.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. 배열에 항목 제거

 

(1) 삭제 버튼 렌더링

User 컴포넌트의 삭제버튼 클릭 될 때

-> user.id 값을 앞으로 props로 받아올 onRemove 함수의 파라미터로 넣어서 호출

import React from 'react';

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

function UserList({ users, onRemove }) {
  return (
    <div>
      {users.map(user => (
        <User user={user} key={user.id} onRemove={onRemove} />
      ))} // onRemove "id가 _인 객체 삭제해라"라는 역할, UserList에서도 전달 받고 그대로 User 컴포넌트에 전달
    </div>
  );
}

export default UserList;

 

(2) onRemove 함수 구현

-> 불변성 지키면서 업데이트( 추가할 때처럼 )

-> filter 배열 내장 함수 사용

: 배열에서 특정 조건이 만족하는 원소들만 추출, 새로운 배열 생성

// App 컴포넌트에서 onRemove 구현, UserList 에게 전달
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.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;

 

 

 

15. 배열 항목 수정

(1) App컴포넌트의 users 배열 안의 객체 안, active 라는 속성 추가

import React from 'react';

function User({ user, onRemove }) {
  return (
    <div>
      <b
        style={{
          cursor: 'pointer', // cursor 필드 설정, 마우스 올렸을 때 커서모양 -> 손가락 변경
          color: user.active ? 'green' : 'black' //active 값에 따라 폰트의 색상 바꿔주도록
        }}
      >
        {user.username}
      </b>

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

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

export default UserList;

 

(2) onToggle 함수 구현

- 배열의 불변성을 유지하면서 배열을 업데이트 할 때 map 함수 사용 가능

- id값 비교

-> 다르면 : 그대로 두기

-> 같으면 : active 값 반전시키도록 구현

 

- onToggle함수 생성, UserList 컴포넌트에게 전달

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

-> Hook을 사용하여 컴포넌트가 처음 나타났을 때(마운트), 사라질 때(언마운트), 업데이트 될 때(특정 props가 바뀔 때) 

 

(1) 마운트 / 언마운트 관리

- useEffect를 사용 할 때

  • 첫 번째 파라미터에 함수 넣기
  • 두 번째 파라미터에 의존값이 들어있는 배열 (deps) 넣기

- useEffect에서 함수 반환 가능 : cleanup 함수

cleanup 함수

: useEffect에 대한 뒷정리, deps가 비어있는 경우, 컴포넌트가 사라질 때 cleanup 함수 호출 됨

 

마운트

- 마운트 시에 하는 작업

  • props로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청 (REST API 등)
  • 라이브러리 사용 (D3, Video.js 등)
  • setInterval을 통한 반복작업, 작업예약

언마운트

- 언마운트 시에 하는 작업

  • setInterval, setTimeout 사용하여 등록한 작업들 clear하기 ( clearInterval, clearTimeout )
  • 라이브러리 인스턴스 제거

(2) deps에 특정 값 넣기

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log('user 값이 설정됨');
    console.log(user);
    return () => {
      console.log('user 가 바뀌기 전..');
      console.log(user);
    };
  }, [user]);
  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;

deps에 특정 값 넣을 시

-> 컴포넌트가 처음 마운트 될 때 호출

-> 지정한 값이 바뀔 때에도 호출

 

deps 안에 특정 값이 있을 시

-> 언마운트 시에도 호출

-> 값이 바뀌기 직전에 호출

 

(3) deps 파라미터 생략

: 컴포넌트가 리렌더링 될 때마다 호출

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {
  useEffect(() => {
    console.log(user);
  });
  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;

 

cf) 리액트 컴포넌트 : 부모 컴포넌트 리렌더링 -> 자식 컴포넌트 리렌더링

반응형