10. 리액트로 배열 다루기

dana·2021년 12월 22일
2

React.js

목록 보기
12/20
post-thumbnail

본 내용은 벨로퍼트와 함께하는 모던리액트 중 1장 11-15 까지의 내용을 다루고 있습니다.

배열 렌더링 하기

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

다음과 같은 배열이 있을 때, 배열을 뽑아내기 위해 사용하는 가장 기본적인 방법은 배열의 key 값을 활용해 뽑아오는 것이다.

users[0].id //1
users[1].email //'tester@example.com'

이렇게 뽑은 데이터는 jsx로 화면에 렌더링 하기 위해 일일히 html을 작성해주어야 하는 단점이 있다. 그래서 이를 보완하기 위해 다음과 같은 방법을 사용할 수 있다.

중복되는 코드는 컴포넌트화하기

return (
    <div>
      <div>
        <b>{users[0].username}</b> <span>({users[0].email})</span>
      </div>
      <div>
        <b>{users[1].username}</b> <span>({users[1].email})</span>
      </div>
      <div>
        <b>{users[2].username}</b> <span>({users[1].email})</span>
      </div>
    </div>
  )

위 코드를 보면 안에 값만 바뀌고, 데이터를 둘러싼 HTML구조는 반복되는 걸 볼 수 있다. 저 반복되는 코드를 컴포넌트화 시키면 훨씬 깔끔한 코드를 짤 수 있게된다.

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}
return (
    <div>
      <User user={users[0]} />
      <User user={users[1]} />
      <User user={users[2]} />
    </div>
  );

초 깔끔✨ 해진 코드를 볼 수 있다.

map을 사용해 출력하기

깔끔해진 코드가 보기 좋지만, 이 코드에도 단점이 있다. 만약 User의 데이터가 5000000000개라면?? 그런 경우 50000000000개를 전부 작성할 수 없으니 js에서 사용하던 map을 사용해 간편하게 출력할 수 있다.

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

foreach는 안되나요?
네 안됩니다.❌ return 값이 없기때문에 foreach문 안에서 백날 돌려봤자 되돌려받는 값이 없어 출력되는게 없음.

이렇게 map을 돌리면 컴파일은 성공하지만 개발자도구를 열었을 때 오류메세지가 나타나는 걸 확인할 수 있다.

그 이유는 바로 map을 사용할 때 key props를 넣어주지 않았기 때문

key 값을 넣어주는 이유

key props는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하기 위해 필요하다. 값으로는 데이터가 가지고 있는 고유값을 넣어주면 된다. 보통 데이터에 들어있는 id값을 사용하는데, 만약 id 값이 없다면 map의 두번째 파라미터인 index 값을 사용하기도 한다

근데 index 사용을 권장하지 않는다고 함.
why❓ 값이 추가 및 삭제되는 경우 데이터의 인덱스 값이 변화되어 재렌더링 되는 부분과 아닌 부분간의 차이가 발생할 수 있다.
참고

그럼 이 key 값은 어디에 넣느냐!
쉽게 생각해서 map의 리턴 컴포넌트에만 작성해주면 된다.

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

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

<User users={user} key={user.id} />에 키값을 적었지만 User component 내부에서는 key를 사용하지 않아도 되는 것을 확인할 수 있다.

그렇다면 user component에서 id값을 사용하고 싶은 경우는 어떻게 key값을 가져오죠?
User에서 users.key를 해도 값을 출력하지 않는다.

그래서 id 값을 사용하고 싶다면 별도의 이름을 가진 props에 id값을 전달해주면 사용할수 있다.

map 함수를 컴포넌트화

map이 중첩되어 여러번 사용되는 경우, 가독성을 위해 map함수를 변수에 저장해 사용하거나 컴포넌트화해서 사용하는 방법이 권장된다.

배열에 새 항목 추가하기

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

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    city: "",
  });

  const { username, city } = inputs;

  const onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

  const [users, setUsers] = useState([
    {
      id: 1,
      username: "kim",
      city: "hongkong",
    },
    {
      id: 2,
      username: "min",
      city: "singapore",
    },
    {
      id: 3,
      username: "ju",
      city: "seoul",
    },
  ]);

  const nextId = useRef(4);

  const onCreate = () => {
    nextId.current += 1;
    const user = {
      id: nextId.current,
      username,
      city,
    };
    setUsers(users.concat(user));

    setInputs({
      username: "",
      city: "",
    });
    nextId.current += 1;
  };

  return (
    <>
      <CreateUser
        username={username}
        city={city}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} />
    </>
  );
}
export default App;

코드를 뜯어보면 input의 값이 바뀔 때마다 onChange 메소드가 실행된다.

const onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

setInputs에서 왜 스프레드시트를 사용할까? 하는 의문을 갖고 있었는데,
단계별로 짚어보니

  1. input은 key가 username과 city로 이루어진 객체를 갖고 있다.
  2. 객체는 immutable 속성으로 input의 값을 변경하는 대신, 새로운 객체를 할당해 주어야한다.
  3. setInputs를 다음과 같이 작성하는 경우, 값이 업데이트 될 때마다 해당 값만 들어있는 값이 저장되게 된다.
    const onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
     [name]: value,
      // username 이 바뀌는 경우, city데이터 삭제
      // city가 바뀌는 경우, username 데이터 삭제
    });
  };

따라서 기존 값에서 업데이트 시키고 싶다면 스프래드 문법을 이용해 기본 값을 넣어준 다음, 새로운 데이터로 업데이트 해주는 방법을 사용해야한다.

setInputs({
  	...inputs
	[name]: value,
});

//city 값이 'newyork'으로 입력시,
setInputs({
  	// ...inputs
  	username : "",
  	city : "",
  	// [name]: value
	city : "newyork",
  	// city : "" -> city : "newyork" 갱신
});  

이와 비슷하게 유저 객체에 값을 추가할 때도
setUsers(users.concat(user))를 사용하거나 setUsers([...users,user]) 을 사용해 값을 추가해주어야한다.

배열에서 항목 삭제하기

불변성을 유지하면서 삭제하기 위해선 filter사용하기!

실행화면

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

function App() {
  const [inputs, setInputs] = useState({
    username: "",
    city: "",
  });

  const { username, city } = inputs;

  const onChange = (e) => {
    const { name, value } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

  const [users, setUsers] = useState([
    {
      id: 1,
      username: "kim",
      city: "hongkong",
    },
    {
      id: 2,
      username: "min",
      city: "singapore",
    },
    {
      id: 3,
      username: "ju",
      city: "seoul",
    },
  ]);

  const nextId = useRef(4);

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

    setInputs({
      username: "",
      city: "",
    });
    nextId.current += 1;
  };

  const onRemove = (id) => {
    setUsers(users.filter((user) => user.id !== id));
  };

  return (
    <>
      <CreateUser
        username={username}
        city={city}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onRemove={onRemove} />
    </>
  );
}
export default App;

onRemove 메소드의 흐름을 살펴보면

App.js

const onRemove = (id) => {
    setUsers(users.filter((user) => user.id !== id));
  };

return (
    <>
      <CreateUser
        username={username}
        city={city}
        onChange={onChange}
        onCreate={onCreate}
      />
    	/*userlist로 onRemove를 내려줌*/
      <UserList users={users} onRemove={onRemove} />
    </>
  );

UserList.js

import React from "react";

function User({ users, onRemove }) {
  return (
    <div>
      <b>ID : {users.id} </b>
      <br />
      <b>{users.username}</b>
      <span>({users.city})</span>
      /*각 user의 id값을 받아서 onRemove 함수에 argument를 전달해준다.*/
      <button onClick={() => onRemove(users.id)}>삭제</button>
    </div>
  );
}

function UserList({ users, onRemove }) {
  return (
    <div>
      {users.map((user) => (
        /*user에도 onRemove를 내려줌*/
        <User users={user} key={user.id} onRemove={onRemove} />
      ))}
    </div>
  );
}
export default UserList;

이렇게 onRemove()함수가 실행되면 User state가 변경되면서 변경된 User data로 re-rendering하게 된다.

onClick{() => onRemove(user.id)}
onClick{onRemove(user.id)}
두개의 차이점은 onClick{onRemove(user.id)} 은 렌더링과 동시에 함수가 실행되어버린다. 그래서 보통 파라미터가 없는 함수라면 ()를 제외한 함수명만 전달한다. 하지만 argument를 전달해야하는 경우, 콜백함수를 이용해 선언과 동시에 실행되지 않으면서, argument가 전달되도록 해준다.

배열 항목을 수정하기

클릭한 id 값이 바뀌도록 하는 onToggle 메소드 생성
실행순서는 삭제 기능과 비슷하다.

실행화면

UserList.js

function User({ users, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: "pointer",
          color: users.active ? "green" : "black",
        }}
        onClick={() => onToggle(users.id)}
      >
        ID : {users.id}{" "}
      </b>
      <br />
      <b>{users.username}</b>
      <span>({users.city})</span>
      <button onClick={() => onRemove(users.id)}>삭제</button>
    </div>
  );
}

jsx 안에 스타일 속성 넣는 법

style={{
          cursor: "pointer",
          color: users.active ? "green" : "black",
        }}

중괄호 안에 스타일 속성을 담은 객체를 넣어 선언할 수 있다. (== 중괄호가 두개~)
프롭스를 사용할 때 따로 괄호처리를 하지 않아도 넣을 수 있다는 점이 흥미롭다,, 🤓

profile
PRE-FE에서 PRO-FE로🚀🪐!

0개의 댓글