React 기본 2

WebStudy / / 2021. 2. 13. 23:56

특정 DOM 선택하기

리액트에서는 ref를 사용

  • 함수용 컴포넌트에서
    • useRef 사용
  • 클래스형 컴포넌트에서
    • React.creasteRef() 또는 callback 함수를 사용

커서를 name input란으로 옮기기

input DOM을 선택하여 커서를 옮기자.

  • const nameInput = useRef();

    • useRef 메소드를 사용한다.
  • <input name = "name" onChange = {onChange} value = {inputs.name} ref = {nameInput}/>

    • 이 input란으로 DOM 선택을 한다.
  • nameInput.current.focus();

    • reset 시 현재 선택한 DOM으로 이동한다.
  • 전체 코드

      import React,{useState, useRef} from "react";
    
      function InputSample(){
          const [inputs, setText] = useState({
              name: '',
              nickname: '',
          });
          const nameInput = useRef();
          const {name, nickname} = inputs;
          const onChange = (e) =>{
              const {name, value} = e.target;
              setText({
                  ...inputs,//불변성을 지킨다.
                  [name]: value,
              });
          }
          const onReset = (e) => {
              setText({
                  name: '',
                  nickname: '',
              });
              nameInput.current.focus();
          }
          return(
              <div>
                  <input 
                  name = "name" 
                  onChange = {onChange} 
                  value = {inputs.name}
                  ref = {nameInput}
                  />
                  <input 
                  name = "nickname" 
                  onChange = {onChange} 
                  value = {inputs.nickname}
                  />
                  <button onClick = {onReset}>초기화</button>
                  <div>
                      <b>값: </b>
                      {inputs.name}
                      <br/>
                      <b>값: </b>
                      {inputs.nickname}
                  </div>
              </div>
          );
      }
      export default InputSample;

useRef는 언제 쓰는가?

  • useRef로 관리되는 값은 바뀌어도 컴포넌트가 리렌더링 되지 않는다.
  • 위에서의 경우 말고 또한 어떤 변수를 기억하고 싶을 때 사용하기도 한다.
    • 그 변수의 값이 바뀐다고 하더라도 그 컴포넌트는 리렌더링 되지 않는다.
  • 언제 쓰일 수 있는가?
    • setTimeout
    • setInerval의 id
    • 외부 라이브러리를 사용하여 생성된 인스턴스
    • Scroll 위치 etc...

배열 렌더링하기

  • 직접 가져와서 쓰기

      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: 'MyTest',
                  email: 'MyTest@example.com'
              }
    
          ];
    
          return (
          <div>
    
              <User user = {users[0]}/>
              <User user = {users[1]}/>
              <User user = {users[2]}/>
    
          </div>
          )
      }
      export default UserList;
  • map 함수 사용

      import React from 'react';
    
      function User({user}){
          return(
              <div>
                  <b>{user.index}<br/></b><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: 'MyTest',
                  email: 'MyTest@example.com'
              }
    
          ];
    
          return (
          <div>
              {
                  users.map(
                      (user, index) => (<User user={user} key={user.id}/>)
                  )
              }
          </div>
          )
      }
      export default UserList;
    • map은 key와 value로 이루어져 있다.
    • 따라서 key를 설정해주는 것을 권장하는데, 할 것이 없다고 index로 하는 것은 매우 비효율적이다.

배열 추가하기

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

      import React, {useRef, useState} from 'react';
      import CreateUser from './CreateUser';
      import UserList from './UserList';
    
      function App() {
    
      const [inputs, setInputs] = useState({
          username: '',
          email: ''
      });
    
      const {username, email} = inputs;
      const onChange = (e) =>{
          const {name, value} = e.target;
          console.log(name);
          console.log(value);
          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: 'MyTest',
              email: 'MyTest@example.com'
          }
    
      ]);
    
      const nextId = useRef(4);
    
      const onCreate = ()=>{
      const user = {
          id: nextId.current,
          username,
          email,
      };
      // 여기는 배열을 새로 생성하여 복사하는 방법
      //setUsers([...users, user]);
    
      // 여기는 concat 함수 사용
      setUsers(users.concat(user));
    
      //push 와 같은 연산으로는 업뎃이 안됨
    
      setInputs({
          username: '',
          email: ''
      });
      console.log(nextId.current);
      nextId.current += 1;
      }
    
      return (
          <>
          <CreateUser 
              username={username}
              email = {email}
              onChange = {onChange}
              onCreate = {onCreate}
          />
          <UserList users = {users}/>
          </>
      );
      }
    
      export default App;
  • 요소를 추가하는데 두 가지 방법이 있다.

    • 배열을 새로 생성하여 복사하는 방법
      • setUsers([...users, user]);
      • users라는 기존 배열을 복사하고, user이라는 값을 추가하여 배열을 생성한다.
      • 그 후 setUsers를 통해 배열을 갱신한다.
    • concat 함수 사용
      • concat: 두 개의 배열을 합칠 때 사용
      • 이를 이용하여 기존 배열에 user이라는 값을 배열삼아 결합한다.

배열 항목 제거하기

  • 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, index) => (
                    <User
                    user={user} 
                    key={user.id} 
                    onRemove = {onRemove}
                    />)
            )
        }
    </div>
    )
}
export default UserList;
```
- button에 `onClick = {()=>onRemove(user.id)}`으로 해야한다.
- `onClick = {onRemove(user.id)}`는 렌더링 할 때 메소드가 호출되어 먼저 삭제가 되는 상황이 발생한다.
- 따라서, 함수를 생성해주고 해당 함수 안에 onRemove를 호출한다.
  • App.js
    • 위 코드에서 다음 메소드를 삽입한다.
        const onRemove = id => {
        setUsers(users.filter(user => user.id !== id));
        };
    • 그 후 UserList에 onRemove를 넣어준다.
    • filter: 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환한다.
      • 따라서, 입력받은 id와 다른 모든 값을 통과시켜 배열로 반환해주고
      • 같은 id는 해당 배열에 속하지 않고 배열이 업데이트 된다.

배열 항목 수정하기

  • map을 통해 해당 요소를 찾아 그 요소의 값을 수정한다.

  • username의 색상을 바꾸는 코드를 구성해보자.

  • App.js 에서 다음의 코드를 추가한다.

    • 각 배열의 요소 active를 추가한다.
      const onToggle = id => {
      setUsers(users.map(
        user => user.id === id ?
        {...user, active: !user.active} : user
        ));
      }
    • map을 통해 입력된 id와 같은 id를 찾는다.
    • 그 후 user의 저장된 데이터를 복사하고 active 요소만 반전시킨다.
    • 그 외의 값들은 이전 데이터를 그대로 넘겨준다.
  • UserLIst.js

      import React from 'react';
    
      function User({user, onRemove, onToggle}){
          const {username, email, id, active} = user;
          const style = {
              color: active ? 'red' : 'yellow',
              cursor: `pointer`
          };
          return(
              <div>
                  <b
                      style = {style}
                      onClick = {() => onToggle(id)}
                  >
                      {username}
                  </b>
                  <span>{email}</span>
                  <button onClick = {()=>onRemove(id)}>삭제</button>
              </div>
          );
      }
    
      function UserList({users, onRemove, onToggle}){
          return (
          <div>
              {
                  users.map(
                      (user, index) => (
                          <User
                          user={user} 
                          key={user.id} 
                          onRemove = {onRemove}
                          onToggle = {onToggle}
                          />)
                  )
              }
          </div>
          )
      }
      export default UserList;

    useEffect

  • 화면이 나타날 때 혹은 사라질 때 특정 작업을 수행함

  • 컴포넌트의 props나 상태가 바뀌어서 업데이트가 되기 전 또는 후에도 특정 작업을 수행함

  • 즉, 리렌더링 될 때마다 작업을 수행함

  • example -> UserList.js

      function User({user, onRemove, onToggle}){
          const {username, email, id, active} = user;
    
          useEffect(()=>{
              console.log('컴포넌트가 화면에 나타남');
              return () => {
                  console.log('컴포넌트가 화면에서 사라짐');
              }
          }, []);
    
          const style = {
              color: active ? 'red' : 'yellow',
              cursor: `pointer`
          };
          return(
              <div>
                  <b
                      style = {style}
                      onClick = {() => onToggle(id)}
                  >
                      {username}
                  </b>
                  <span>{email}</span>
                  <button onClick = {()=>onRemove(id)}>삭제</button>
              </div>
          );
      }
  • []안에 데이터를 넣으면 해당 데이터가 변경 감지가 되었을 때 내용을 실행한다.

    • 만일 [] 없이 한다면, 부모 컴포넌트의 변경 감지를 하게 됨
  • useEffect 마운트 되면서 작업하는 내용

    • props로 받은 값을 컴포넌트의 state로 설정할 수 있음
    • 외부 API 요청을 할 수 있음
    • 라이브러리를 사용할 경우에도 내부에서 사용 가능
    • setInterval, setTimeout 또한 가능
  • useEffect 언마운트 되면서 작업하는 내용

    • clearInterval, clearTimeout 여기서 작업함
    • 라이브러리 인스턴스 제거

useMemo

이전에 연산된 값을 재사용 함
주로 성능을 최적화 하는 곳에서 사용함

  • App.js에 다음과 같은 함수를 추가한다.
      function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
      }
    • 이는 users의 active상태가 true인 값들만 찾아서 길이를 리턴한다.
  • 그 후 count 변수를 통해 해당 값을 출력해보자.
    • const count = countActiveUsers(users);
    • 이렇게 해서 출력을 해보면 제대로 원하는 값이 나온다.
    • 하지만 input란에 입력할 때마다 호출이 된다.
      • 이는 가장 루트 컴포넌트가 변화를 감지했기 때문인데, 우리는 단지 등록을 통해 원하는 경우에 해당 메소드를 호출하고 싶다.
      • 이를 위해 useMemo를 이용한다.
    • const count = useMemo(() => countActiveUsers(users), [users]);
      • 이로써 등록을 통해 users가 변화가 생겼을 때 해당 함수를 호출한다.

useCallback

이전에 만들었던 함수를 재사용
위 모든 함수들은 컴포넌트가 리렌더링 될 때마다 새로 만들어진다.
하지만 같은 작업을 반복해야 하는 상황인데 리렌더링 될 때마다 새로 만들어지는 것은 비효율 적이다.
물론 메모리 및 리소스를 많이 차지하지는 않으나 계속 생성되는 작업은 불필요하다.
따라서, useCallback을 통해 한번 만들어 두고 계속 사용할 수 있도록 하자.

  • App.js
      const onChange = useCallback( (e) =>{
          const {name, value} = e.target;
          console.log(name);
          console.log(value);
          setInputs({
          ...inputs,
          [name]: value 
          });
      }, [inputs]);
    • 위 모든 함수를 이렇게 useCallback으로 묶는다.

React.memo

컴포넌트에서 리렌더링이 불필요 할 때 이전에 썻던 결과를 재사용 할 수 있도록 함
리렌더링 성능 최적화

  • 항상 리렌더링이 불필요한 요소에 React.memo를 감싸준다.
    • 예를 들어 User 컴포넌트는 한번 만들고 다른 값이 바뀔 때 이 값까지 리렌더링이 될 필요는 없다.
      따라서, User컴포넌트는 React.memo를 감싸줄 필요가 있다.

useReducer

컴포넌트 상태를 업데이트

useState: 설정하고 싶은 다음 상태를 직접 지정해주는 방식
useReducer: Action이라는 객체를 기반으로 상태를 업데이트
Action 객체: 업데이트 할 때 참조하는 객체

  • useReducer는 상태 업데이트 로직을 컴포넌트 밖으로 분리가 가능하다.

  • 다른 파일에 작성 후 불러와 사용이 가능하다.

  • reducer: 현재상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 업데이트 하는 함수

      function reducer(state, action){
          return nextState;
      }
    • reducer가 반환하는 상태는 곧 컴포넌트가 지닐 새로운 상태이다.
    • action은 업데이트를 위한 정보를 가지고 있다.
      • 주로 type 값을 지닌 객체 형태로 사용한다.
  • useReducer 사용

    • const [state, dispatch] = useReducer(reducer, initialState);
    • state: 컴포넌트에서 사용할 수 있는 상태
    • dispatch: 액션을 발생시키는 함수
  • Counter.js

      import React, {useReducer} from 'react';
    
      function reducer(state, action){
          switch(action.type){
              case 'INCREMENT':
                  return state + 1;
              case 'DECREMENT':
                  return state - 1;
              default:
                  throw new Error('Unhandled action');
    
          }
      }
    
      function Counter(){
          const [number, dispatch] = useReducer(reducer, 0);
    
          const Increase = () => {
              dispatch({
                  type: 'INCREMENT'
              });
          }
          const Decrease = () => {
              dispatch({
                  type: 'DECREMENT'
              });
          };
          return (
              <div>
                  <h1>{number}</h1>
                  <button onClick={Increase}>+1</button>
                  <button onClick={Decrease}>-1</button>
              </div>
          )
      }
    
      export default Counter;

useReducer를 이용하여 App.js수정

import React, {useRef, useState, useMemo, useCallback, useReducer} from 'react';
import CreateUser from './CreateUser';
import UserList from './UserList';


function countActiveUsers(users){
  console.log('활성 사용자 수를 세는 중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  inputs:{
  username: '',
  email: '',
  active: true,
  },
  users: [
      {
        id: 1,
        username: 'velopert',
        email: 'public.velopert@gmail.com',
        active: true
    },
    {
        id: 2,
        username: 'tester',
        email: 'tester@example.com',
        active: true
    },
    {
        id: 3,
        username: 'MyTest',
        email: 'MyTest@example.com',
        active: true
    }
  ]
}

function reducer(state, action){
  switch(action.type){
    case 'CHANGE_INPUT':
      return{
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value,
        }
      }
    case 'CREATE_USER':
      return{
        inputs: initialState.inputs,
        users: state.users.concat(action.user)
      }
    case 'TOGGLE_USER':
      return{
        ...state,
        users: state.users.map(
          user => user.id === action.id ? 
          {...user, active: !user.active} : user
        )
      }
    case 'REMOVE_USER':
      return{
        ...state,
        users: state.users.filter(
          user => user.id !== action.id
        )
      }

    default:
      throw new Error('Unhandled Action');
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);
  const {users} = state;
  const {username, email} = state.inputs;

  const onChange = useCallback( (e) =>{
    const {name, value} = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value,
    })

  }, []);


const onCreate =  useCallback( ()=>{
  dispatch({
    type: 'CREATE_USER',
    user: {
      id: nextId.current,
      username,
      email,
      active: true,
    }
  })
}, [username, email]);

const onRemove = useCallback( id => {
  dispatch({
    type: 'REMOVE_USER',
    id
  })
}, [])

const onToggle = useCallback(id => {
    dispatch({
    type: 'TOGGLE_USER',
    id
  })

}, []);

const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser 
        username={username}
        email = {email}
        onChange = {onChange}
        onCreate = {onCreate}
      />
      <UserList users = {users} onRemove = {onRemove} onToggle = {onToggle}/>
      <div>활성 사용자 수: {count}</div>
    </>
  );
}

export default App;

커스텀 Hook 만들기

useInput 만들기

  • uesInput.js

      import {useState, useCallback} from React;
    
      function useInput(initialForm){
          const [form, setForm] = useState(initialForm);
          const onChange = useCallback(e => {
              const {name, value} = e.target;
              setForm(form => ({ ...form, [name]: value}));
          }, []);
          const reset = useCallback(() =>{
              setForm(initialForm)
          }, []);
    
          return [form, onChange, reset];
      }
    
      export default useInput;

'WebStudy' 카테고리의 다른 글

React API  (0) 2021.02.18
React 기본 3  (0) 2021.02.15
React 기본 1  (0) 2021.02.12
Web Server 및 WAS  (0) 2021.02.06
Browser  (0) 2021.02.06
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기