본문 바로가기

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

[2024 여름방학 React.js 스터디] 김민서 #4주차

반응형
useRef

 

리액트에서 직접 DOM을 선택해야 할 때 ref를 사용한다

  • 함수형 컴포넌트에서 ref 사용 - useRef라는 Hook 함수(함수형 컴포넌트가 클래스형 컴포넌트의 기능을 사용할 수 있게 해줌)를 사용
  • 클래스형 컴포넌트에서 - 콜백 함수 또는 React.createRef 함수 사용
import React, { useState, useRef } from 'react';
const InputSample = () => {
  const [inputs, setInputs] = useState({
    name: '',
    nickname: ''
  });
  const nameInput = useRef(); // Ref 객체 만들기

  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 = () => { // DOM API 호출
    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;

 

 

 

배열 렌더링하기

 

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

이 배열을 컴포넌트로 렌더링하기

 

* 배열의 인덱스를 하나하나 조회하면서 렌더링하면 동적인 배열을 렌더링하지 못한다

=> map() 사용 - 배열안에 있는 각 원소를 변환하여 새로운 배열을 만들어준다

 

* 리액트에서 배열을 렌더링 할 때에는 key 라는 props를 설정해야한다 ( 각 원소들마다 가지고 있는 고유값)

import React from 'react';

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

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>
      {users.map(user => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}
export default UserList;

 

 

 

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

 

useRef Hook은 DOM 선택 외에도 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리하는 용도가 있다

 

* 리액트 컴포넌트에서의 상태 - 상태를 바꾸는 함수 호출 -> 렌더링 이후로 업데이트 된 상태 조회

* useRef로 관리하고 있는 변수 - 설정 후 바로 조회 가능, 밑의 값을 관리 할 수 있다

  • setTimeout, setInterval 을 통해 만들어진 id
  • 외부 라이브러리를 사용해 생성된 인스턴스
  • scroll 위치
//App.js
import React, { useRef } from 'react';
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'
    }
  ];

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

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

export default App;
//UserList.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

 

 

배열에 항목 추가하기

 

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

 

배열에 변화를 줄 때에는 불변성을 지켜야한다 - push, splice, sort 사용 불가

불변성을 지키면서 배열에 새 항목 추가하기?

=> spread 연산자 사용 or concat 사용

// App.js spread 연산자 사용
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'
    },
    {
      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;
// App.js concat 사용
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'
    },
    {
      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;

 

 

배열에 항목 제거하기

 

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

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

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

export default UserList;
// 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'
    },
    {
      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;

불변성을 지키면서 특정 원소를 배열에서 제거하기 위해 filter 함수(배열에서 특정 조건이 만족하는 원소들만 추출해 새로운 배열을 만들어줌) 사용 

 

 

배열에 항목 수정하기

 

// 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));
  };
  const onToggle = id => {
    // 배열의 불변성을 유지하면서 업데이트 하기 위해 map 사용
    setUsers(
      users.map(user =>
        user.id === id ? { ...user, active: !user.active } : user //id 값 비교 후 다르면 그대로, 같으면 active 반전
      )
    );
  };
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} onToggle={onToggle} />
    </>
  );
}

export default App;
// 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;

 

 

useEffect로 마운트/언마운트/업데이트시 할 작업 설정하기

 

1. 마운트 / 언마운트

import React, { useEffect } from 'react';

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

const 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에서 함수를 반환 하는 것을 cleanup함수라 부른다 (deps가 비어있을 때 컴포넌트가 사라지면 cleanup 함수 호출)

마운트 시에 하는 작업

  • props로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 API 요청
  • 라이브러리 사용
  • setInterval을 통한 반복작업 또는 setTimeout을 통한 작업 예약

 

언마운트 시에 하는 작업

  • setInterval, setTimeout을 사용해 등록한 작업들 clear 하기
  • 라이브러리 인스턴스 제거

 

2. deps에 특정 값 넣기

import React, { useEffect } from 'react';

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

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

export default UserList;

* useEffect 안에서 사용하는 상태나 props가 이쓰면 deps에 넣어주어야 한다

 

3. deps 파라미터 생략하기

리렌더링 될 때마다 호출

import React, { useEffect } from 'react';

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

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

export default UserList;

* 리액트 컴포넌트는 기본적으로 부모컴포넌트가 리렌더링되면 자식 컴포넌트 또한 리렌더링 된다 (바뀐 내용이 없어도)

=> Virtual DOM에는 모든걸 다 렌더링, 실제 DOM에 변화가 반영되는 건 바뀐 내용이 있는 컴포넌트만

반응형