멋사 스터디 기록 - 02. Hooks

Kyle·2022년 1월 21일
0

React

목록 보기
2/2

Hooks

1. useState

state => 컴포넌트 내부에서 변경될 수 있는 값(데이터). 직접 변경할 수 없음

import React, { useState } from "react";

const InputPractice = () => {
  const [inputs, setInputs] = useState({
    name: "",
    nickname: "",
  });

  const { name, nickname } = inputs;

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

  const onReset = () => {
    setInputs({
      name: "",
      nickname: "",
    });
  };

  return (
    <div>
      <input name="name" placeholder="이름" onChange={onChange} value={name} />
      <input
        name="nickname"
        placeholder="닉네임"
        onChange={onChange}
        value={nickname}
      />
      <button onClick={onReset}>초기화</button>
      <div>
        <b>값: </b>
        {name} ({nickname})
      </div>
    </div>
  );
};

export default InputPractice;

다음 코드의 onChange에서 [name] : value라고 되어 있는데,

이는 state의 불변성을 위해 JS의 spread문법(기존의 것을 건들이지 않고 새로운 객체를 만들 때 사용)을 이용하여

기존 state를 복사한 다음 새로 추가되는 name이라는 key를 가진 value로 설정하는 것이다.

inputs[name] = value 와 같이 직접적인 수정은 불가

2. useRef

순수 JS를 사용할 때 특정 DOM을 선택하기 위해서는
getElementById, querySelector와 같은 DOM Selector 함수를 사용한다.
리액트에서는 ref라는 것을 사용하며 함수형은 useRef를, 클래스형은 콜백함수React.createRef라는 함수를 사용한다.

다음은 Input 창에서 useRef를 사용하는 예제이다.

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

const InputPractice = () => {
  const [inputs, setInputs] = useState({
    name: "",
    nickname: "",
  });
  const nameInput = useRef();

  const { name, nickname } = inputs;

  const onChange = (e) => {
    const { value, name } = e.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
    // inputs[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>
  );
};

export default InputPractice;

3. 배열

  • App.js
import React, { useRef, useState } from "react";
import CreateUser from "./components/CreateUser";
import UserList from "./components/UserList";

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 = () => {
		/* 로직 추가 필요 */

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

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

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

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;

3-1. 추가

배열을 state 로 선언 → 직접 수정하면 ❌❌❌
JS 배열 함수 중 push , splice 등은 배열을 직접 수정하게 된다!
따라서 전에 배웠던 spread 문법을 사용하거나 concat 함수를 사용하면 된다.

  • App.js - onCreate - spread
const onCreate = () => {
  const user = {
    id: nextId.current,
    username,
    email
  };
  setUsers([...users, user]);

  setInputs({
    username: '',
    email: ''
  });
  nextId.current += 1;
};
  • App.js - onCreate - concat
const onCreate = () => {
  const user = {
    id: nextId.current,
    username,
    email
  };
  setUsers(users.concat(user));

  setInputs({
    username: '',
    email: ''
  });
  nextId.current += 1;
};

3-2. 삭제

  • UserList.js → 각각 아이템에 삭제버튼 추가
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} />
      ))}
    </div>
  );
}

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

3-3. 수정

  • App.js → users state 에 active key 추가
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
  }
]);
  • App.js - onToggle
const onToggle = id => {
  setUsers(
    users.map(user =>
      user.id === id ? { ...user, active: !user.active } : user
    )
  );
};
  • 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;

4. useEffect

컴포넌트는 대부분 아래 3가지 경우에 업데이트를 한다.

  1. props 가 바뀔 때
  2. state 가 바뀔 때
  3. 부모 컴포넌트가 리렌더링 될 때

클래스 컴포넌트에는 이럴 때 조건에 맞게 컴포넌트를 처리하고 싶을 때 사용하는 Life Cycle 메소드가 있다. (componentDidMount , componentDidUpdate , componentWillUnmount 등)

함수형 컴포넌트에는 따로 Life Cycle 메소드가 없고 useEffect hook 으로 같은 기능 가능.

useEffect(() => {
	// 함수 내용
}, [deps])

useEffect 를 사용 할 때에는 첫번째 파라미터에는 함수,

두번째 파라미터에는 의존값이 들어있는 배열 (deps)을 넣는다.

만약에 deps 가 빈 배열이면, 컴포넌트가 처음 나타날때에만 useEffect 에 등록한 함수가 호출.

useEffect 에서는 함수를 반환 할 수 있는데 이를 cleanup 함수라고 부른다. 

cleanup 함수는 useEffect 에 대한 뒷정리를 하는 역할을 하며

deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다.

5. useMemo

내부 발생 연산을 최적화시킬 수 있는 hook.

6. useCallback

App.js 의 onCreateonRemoveonToggle 함수는 컴포넌트가 리렌더링 될 때 마다 새로 만들어진다.
컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 최적화가 필요.
함수 안에서 사용하는 상태 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 한다!

profile
불편함을 고민하는 프론트엔드 개발자, 박민철입니다.

0개의 댓글