글 작성자: bbangson
반응형

배열에 항목 제거하기

배열에 항목을 제거할 때에는 어떻게 해야 하는지 알아보겠습니다. 

 

먼저, UserList에서 각 User 컴포넌트를 보여줄 때, 삭제 버튼을 렌더링 하겠습니다. 

 

UserList.js

import React from "react";

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

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

export default UserList;

 

결과 화면 

결과 화면

 

User 컴포넌트의 삭제 버튼이 클릭될 때는 user.id 값을 Props로 받아올 onRemove() 함수의 파라미터로 넣어서 호출해줘야 합니다. 

 

여기서 onClick() 함수는 함수형 호출을 활용하여 onRemove() 함수를 작동합니다. 

onRemove()"id가 ~인 객체를 삭제해라"라는 역할을 가지고 있습니다. 

 

이 onRemove 함수는 UserList 컴포넌트에도 전달받습니다. 이를 그대로 User 컴포넌트에 전달해줍니다. 

 

참고로 onClick={onRemove(id)} 이런 식으로 코드를 작성하면 절대 안 됩니다. 이렇게 하면 컴포넌트가 렌더링 될 때 바로 모든 객체들이 삭제될 것입니다. 렌더링 됨과 동시에 onRemove() 함수를 실행하기 때문입니다. 

 

그래서 보통 onClick = {onRemove}으로 지정해서 ()를 제외하는 방법으로 함수가 즉시 실행되지 않게 하고, 클릭했을 때 실행이 되도록 해줍니다. 

 

하지만 예제와 같이 onRemove의 경우는 해당 함수가 실행될 때 파라미터로 id값을 받아와야 합니다. 

 

이런 문제들을 해결하기 위해서 onClick에 콜백 함수를 넣어주고, 해당 함수가 실행될 때 id를 건네주어 실행시키는 방법으로 처리하는 것입니다.  

 

onRemove 함수는 App.js에서 구현해줍니다. 

 

배열에 있는 항목을 제거할 때에는, 추가할 때와 마찬가지로 불변성을 지키면서 업데이트를 해야 합니다.

 

불변성을 지키면서 특정 원소를 배열에서 제거하기 위해서는 자바스크립트 내장 함수인 filter 배열 내장 함수를 사용하는 것이 가장 편합니다.

이 함수는 배열에서 특정 조건이 만족하는 원소들만 추출하여 새로운 배열을 만들어줍니다.

 

App.js

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와 id가 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
    // user.id === id인 원소만 제거함. 즉, true인 원소들만 반환한다.
    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;

 

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;

삭제되는 것을 확인하실 수 있을 겁니다.

 

 

 

배열에 항목 수정하기

배열 항목을 수정하는 방법을 알아보겠습니다. 

 

User 컴포넌트에서 계정명을 클릭했을 때, 색상이 초록색으로 바뀌고 다시 누르면 검은색으로 바뀌도록 구현하겠습니다.

 

 

App.js

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

  // active 값 반전
  const onToggle = (id) => {
    setUsers(
      users.map((user) =>
        user.id === id
          ? // user 객체를 update할 때에도 불변성을 지켜줘야 하기 때문에 spread연산자 사용.
          { ...user, active: !user.active }
          : // 일치하지 않으면 기존의 값 그대로 사용
            user
      )
    );
  };

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

export default App;

우선, App 컴포넌트의 users 배열 객체 안에 'active'라는 속성을 추가합니다. 그리고 이 속성을 활용하여 폰트의 색상을 바꿔주고 추가적으로 cursor 필드를 설정하여 마우스를 올렸을 때 커서가 손가락 모양으로 변하도록 하겠습니다.

 

또한, App.js에서 onToggle()이라는 함수를 구현하여 배열의 불변성을 유지하면서 배열을 업데이트할 때에도 map 함수를 사용할 수 있습니다. 

 

수정할 때는 기존에 있는 값 자체를 수정하는 것이 아니라 새로운 객체를 만들어줘서

기존에 있는 원소를 가져오고(Spread함수 사용.) 변경될 원소만 바꿔서 수정해주는 것입니다. 

 

id 값을 비교해서 id가 다르다면 그대로 두고, 같다면 active 값을 반전시키도록 구현하겠습니다. 

 

onToggle 함수를 만들어서 UserList 컴포넌트에게 전달합니다.

 

 

 

UserList.js

import React from "react";

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

UserList 컴포넌트에서는 onToggle을 받아와서 User에게 전달해주고 onRemove를 구현했던 것처럼 onToggle에 id를 넣어서 호출해줍니다. 

 

참고로 &nbsp는 공백입니다. 

 

결과 화면

 

결과 화면

 

※ 정리 

배열에서 항목을 생성할 때는 Spread 연산자Concat을 사용합니다. 

제거할 때는 filter 함수, 특정 값만 업데이트(수정)할 때는 map 함수를 사용하면 됩니다. 

 

 

 

 

useEffect

useEffect 함수는 리액트 컴포넌트가 mount(처음 렌더링 될 때), unmount(삭제 혹은 사라질 때), update(수정될 때)될 때 특정 작업을 처리하게 해주는 Hook입니다. 

 

기존 클래스형 컴포넌트에서 사용할 수 있었던 생명주기 메소드를 함수형 컴포넌트에서도 사용할 수 있게 된 것입니다. 

클래스형 컴포넌트의 componentDidMountcomponentDidUpdate를 합친 형태로 봐도 무방합니다. 

 

useEffect()를 사용할 때 기본 형태는 useEffect( function, deps)입니다. 

- function : 수행하고자 하는 작업입니다. 

- deps(dependence) : 배열 형태로, 배열 안에는 검사하고자 하는 특정 배열 혹은 빈 배열입니다.

 

먼저, 마운트 / 언마운트를 관리해보겠습니다. 

 

UserList.js

import React, { useEffect } from 'react';

function User({ user, onRemove, onToggle }) {

  // useEffect()
  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)을 넣습니다. 

 

만약에 deps 배열빈 배열로 넣게 된다면, 컴포넌트가 처음 렌더링 될 때에만 useEffect에 등록한 함수가 호출됩니다. 

 

그리고 useEffect에서는 함수를 반환할 수 있습니다. 이를 clean-up 함수라고 부릅니다. clean-up 함수는 useEffect에 대한 뒷정리를 해준다고 생각하면 됩니다. 즉, deps가 비어있는 경우에는 컴포넌트가 사라질 때 clean-up 함수가 호출됩니다. 

 

결과 화면

 

결과 화면

주로, 마운트 시에 하는 작업들은 다음과 같습니다. 

  - props로 받은 값을 컴포넌트의 state로 설정 

  - 외부 API 요청 (REST API 등)

  - 라이브러리 사용 (D3, Video.js 등)

  - setInterval을 통한 반복 작업 혹은 setTimeout을 통한 작업 예약 

 

언마운트 시에 하는 작업들은 다음과 같습니다. 

  - setInterval, setTimeout을 사용하여 등록한 작업을 clear (clearInterval, clearTimeout)

  - 라이브러리 인스턴스 제거 

 

 

이번에는 deps에 특정 배열을 넣어보도록 하겠습니다.

deps에 특정 값을 넣게 된다면, 컴포넌트가 처음 마운트 될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 됩니다. 

 

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

 

deps에 특정 값 넣기

UserList.js

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;

 

결과 화면

 

useEffect 안에서 사용하는 상태나 Props가 있다면 useEffect의 deps에 넣어주어야 합니다. 이것이 규칙입니다. 

 

만약 useEffect 안에서 사용하는 상태나 props를 deps에 넣지 않으면 useEffect에 등록한 함수가 실행될 때 최신 props 혹은 상태를 가리키지 않게 됩니다. 

 

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

 

 

deps 파라미터 생략하기

UserList.js

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;

 

결과 콘솔 화면 

결과 콘솔 화면

리액트 컴포넌트는 기본적으로 부모 컴포넌트가 리 렌더링되면 바뀐 내용이 없어도 자식 컴포넌트도 같이 리렌더링 됩니다. 

 

실제 DOM에 변화가 반영되는 것은 바뀐 내용이 있는 컴포넌트만 해당됩니다.

하지만 Virtual DOM에는 모든 내용을 다 렌더링하고 있습니다.

 

물론, 내용이 바뀌지 않아도 Virtual DOM에 렌더링 한다고 해서 성능이 느려지는 것은 아니지만 

엄청난 수의 내용을 렌더링 해야 되는 경우에는 느려질 가능성이 조금 있습니다. 

이러한 경우에는 Virtual DOM에서 렌더링 하는 것 조차도 아껴줘야 합니다.

이것을 '컴포넌트 리렌더링 성능 최적화'라 부르긴 하지만, 이것에 대해서는 훗날 더욱 언급하도록 하겠습니다.

 

 

 

 

 

강의 링크

 

프론트엔드 개발 올인원 패키지 with React Online. | 패스트캠퍼스 (fastcampus.co.kr)

 

프론트엔드 개발 올인원 패키지 with React Online. | 패스트캠퍼스

프론트엔드 개발 러닝패스, 이 강의 패키지 하나로 끝낼 수 있습니다. 총 90시간 분량의 평생 소장 온라인 강의로 프론트엔드 개발자가 되세요.

www.fastcampus.co.kr

 

반응형