React(Hook) +4

LEE EUI JOO·2023년 2월 9일
0

Web Programming

목록 보기
13/17

Hook (훅)

개념

  • 클래스 컴포넌트를 사용하다가 함수형 컴포넌트로 변경이 될 때 사용하기 위한 개념
  • 함수형 컴포넌트에서 상태 유지와 수명 주기 관련 메서드를 사용하기 위해서 도입된 개념
  • Hook 은 함수형태로 존재하고 'use' 라는 접두어로 시작 - 우리는 use라는 단어를 만들지 않는것이 중요함

필요한 이유

  • 상태 유지 문제

    • 함수는 일시적으로 메모리를 할당받아서 사용하다가 수행이 종료되면 소멸

    • 인스턴스는 정리 하지 않으면 메모리에 계속 존재하게 됨

    • 이전에 수행한 결과를 다음 수행에 사용하고자 하는 경우

    • HTTP 와 HTTPS 도 연결을 계속 유지하지 않는다.

    • 세션(서버에 저장)과 쿠키라는 개념을 이용해서 상태를 유지

    • 로컬 스토리지 나 Web SQL, indexedDB, Web Socket 등의 개념도 사

    • 클래스 컴포넌트는 수명 주기 관련 메서드가 존재


종류

  • 데이터 관리 - 상태 유지

    • useMemo

    • useCallback

    • useState

    • useReducer

  • 수명 주기 관련

    • useEffect - 비동기

    • useLayoutEffect - 동기

  • 메서드 호출

    • useRef
    • useImperativeHandle - TypeScript
  • 데이터 공유

    • useContext - 저장의 개념

모양

  • 매개변수 1개 (데이터) : useState useRef useImperativeHandle useContext

  • 매개변수 2개 (콜백함수, 의존성 목록 - deps) : useMemo useCallback useReducer useEffect useLayoutEffect


useState - 데이터라고 생각

  • props : 상위 컴포넌트에서 하위 컴포넌트에게 전달하는 데이터, 함수형 컴포넌트의 매개변이나, 수정이 불가하다
<컴포넌트이름 이름= .../> 사용은 prpos.이름
  • state : 컴포넌트 내부에 생성하여 사용한느 수정할 수 있는 데이터
  • children : 여는 태그 와 닫는 태그 사이의 내용으로 props.children 으로 접근
<컴포넌트>childrn</컴포넌트>
  • props 와 state 는 변경이 되면 컴포넌트가 rerendering 된다.

  • 생성

    • const [state이름,setter이름] = useState(초기값);

접근자 메서드

  • 속성을 가져오고 수정하기 위한 메서드

    • getter : 속성의 값을 가져오는 메서드

      • get속성이름() 의 형태로 만드는데 속성 이름의 첫글자는 대문자

      • 자료형이 bool 인 경우는 get 대신에 is 사용

      • 최근의 언어에서는 get 대신에 속성 이름으로 바로 접근할 수 있게 만들어 준다

    • setter : 속성의 값을 수정하는 메서드

      • set속성이름(데이터)의 형태로 만드는데 속성 이름의 첫글자는 대문자

클래스형 컴포넌트에서의 state 사용

  • 새 프로젝트 생성 : hooks
  • 컴포넌트 디렉토리를 생성해 ClassComponent.jsx 파일 생성 및 수정
import React, {Component} from 'react';

// 버튼을 누르면 숫자를 1씩 증가시켜서 출력하느 컴포넌트
class ClassComponent extends Component{

    constructor(props){
        super(props);
        this.state = {
            count:0
        }
    }

    render(){

        return(

            <>            
            
                <p>{this.state.count} 번 클릭</p>
                <button 
                    onClick = {(e) => {this.setState({count:this.state.count +1 })}}>
                    Button
                </button>
            
            </>
        )
    }
}

export default ClassComponent;
  • App.js 파일 수정
import logo from './logo.svg';
import './App.css';
import ClassComponent from './components/ClssComponent';

function App() {
  return (
    <>
      <ClassComponent/>
    
    </>
    
  );
}

export default App;

  • 클래스 형 컴포넌트는 화면에 출력이 될 때 인스턴스를 생성해서 출력 - 내부에 만든 데이터가 유지가 된다

함수형 컴포넌트에서의 state 사용

  • 함수형 컴포넌트 생성 - FunctionComponent.jsx
import React from "react";

function FunctionComponent(){

    let n = 0;

    function handleClick(){

        n = n + 1;
        console.log(n);
    }

    return(

        <>
            <p>{n}</p>
            <button onClick={handleClick}>Button</button>
        </>
    );
}

export default FunctionComponent;
  • App.js 수정

import './App.css';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';

function App() {
  return (
    <>
      <ClassComponent/>
      <FunctionComponent/>
    
    </>
    
  );
}

export default App;

  • 함수형 컴포넌트는 재출력 되지 않음 (콘솔에는 찍히지만)
  • 강제로 출력하려면 함수 안에 getElementById 사용
import React from "react";

function FunctionComponent(){
  //일반 변수를 만들어서 사용을 하면 값이 변경되더라도 재출력되지 않음

    let n = 0;

    function handleClick(){

        n = n + 1;
        console.log(n);
        //강제로 출력하는 코드 작성
        document.getElementById('display').innerHTML = n;
    }

    return(

        <>
            <p id='display'>{n}</p>
            <button onClick={handleClick}>Button</button>
        </>
    );
}

export default FunctionComponent;

  • setState 사용하기 위해 FunctionComponent.jsx 파일 수정
import React, { useState } from "react";

function FunctionComponent(){

    /*let n = 0;

    function handleClick(){

        n = n + 1;
        console.log(n);
        //강제로 출력하는 코드 작성
        document.getElementById('display').innerHTML = n;*/


    /*let state = {
        n:0
    }

    function handleClick(){
        state = {
            n:state.n + 1
        }
    }*/

    //state 생성
    
    const [count, setCount] = useState(0);

    function handleClick(){

        setCount(count + 1);
    }

    return(

        <>
            <p id='display'>{count}</p>
            <button onClick={handleClick}>Button</button>
        </>
    );
}

export default FunctionComponent;

함수형 컴포넌트에서 여러 개의 state 생성

  • 이름과 별명을 입력받아서 내용이 수정될 때 화면에 출력하는 컴포넌트 생성
  • Info.jsx 생성 하여 수정
import React ,{useState} from "react"; //useState 에 해당하는 에만 여기{}에 넣어 놓고 시작하겠다

const Info = () => {

    const [name, setName] = useState(''); //초기값
    const [lee, setLee] = useState('');

    const onChangeName = (e) => {

        setName(e.target.value);

    }

    const onChangeLee = (e) => {

        setLee(e.target.value);
    }



    return(

        <>
            <div>
                <input name='name' value={name} onChange={onChangeName}/>
                <input name='lee' value={lee} onChange={onChangeLee}/>
            </div>

            <div>
                <b>이름:{name}</b>
            </div>
            <div>
                별명:{lee}
            </div>
    
        </>
    )


}
export default Info;
  • App.js 수정하여 컴포넌트 출력
import './App.css';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

function App() {
  return (
    <>
      <ClassComponent/>
      <FunctionComponent/>
      <Info/>
    
    </>
    
  );
}

export default App;


useEffect

  • 개요

    • state 가 변경된 후 수행할 side effect 효과를 설정하는 hook

    • 이 함수에서는 state를 사용할 수 있음

    • Class Component 의
      componentDidMount(컴포넌트가 출력되고 난 후 호출되는 메서드), componentDidUpdate(컴포넌트가 업데이트 되고 난 후 호출되는 메서드), componentWillUnmount(컴포넌트가 화면에서 제거된 후 호출되는 메서드)를 합친 메서드

  • Class Component 에서의 수명주기 메서드 확인

  • Not Strict Mode 로 실행 - index.js 의 strict 태그 지움

  • 수명 주기 확인을 위한 컴포넌트 작성 ClassEffect.jsx 파일 생성 후 작성

import React from "react";

class ClassEffect extends React.Component{

    state = {

        count: 0
    }

    componentDidMount(){

        console.log('컴포넌트가 화면에 출력된 후 호출되는 메서드');
        // 탭에 출력되는 문자열 설정 - 검색 엔진이 기본적으로 title을 가지고 검색
        document.title = `${this.state.count} 번 클릭`;
        
    }

    componentDidUpdate(){
        console.log('컴포넌트가 업데이트 된 후 호출');
        document.title = `${this.state.count} 번 클릭`;
    }

    render(){

        return(
                <>
                    <p>{this.state.count}</p>
                    <button onClick={(e)=> {this.setState({count:this.state.count + 1})}}>
                        Click
                    </button>
                </>

        )
    }
}
export default ClassEffect;
  • App.js 에 컴포넌트 추가
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

function App() {
  return (
    <>
      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
    
    </>
    
  );
}

export default App;

  • 클릭 수행


  • useEffect

    • useEffect(호출될 콜백 함수, deps)
      • deps 를 생략하면 state 가 변경될 때 마다 호출
      • deps에 [](빈 배열)을 설정하면 맨 처음 한 번만 호출
      • deps에 state를 나열하면 나열된 state가 변경될 때 호출
      • 콜백함수 안에서 함수를 리턴할 수 있는데 이렇게 하면 이 함수는 clean up 함수가 되서 컴포넌트가 되면 화면에서 제거 될 때 호출된다
  • Info.jsx 파일 수정

import React ,{useState,useEffect} from "react"; //useState 에 해당하는 에만 여기{}에 넣어 놓고 시작하겠다

const Info = () => {

    const [name, setName] = useState(''); //초기값
    const [lee, setLee] = useState('');

    useEffect(()=>{

        console.log('콜백함수');
    })

    const onChangeName = (e) => {

        setName(e.target.value);

    }

    const onChangeLee = (e) => {

        setLee(e.target.value);
    }



    return(

        <>
            <div>
                <input name='name' value={name} onChange={onChangeName}/>
                <input name='lee' value={lee} onChange={onChangeLee}/>
            </div>

            <div>
                <b>이름:{name}</b>
            </div>
            <div>
                별명:{lee}
            </div>
    
        </>
    )


}
export default Info;

  • Info.jsx 파일 수정
import React ,{useState,useEffect} from "react"; //useState 에 해당하는 에만 여기{}에 넣어 놓고 시작하겠다

const Info = () => {

    const [name, setName] = useState(''); //초기값
    const [lee, setLee] = useState('');
// 두번째 매개변수를 생성략하면 state가 변경될 때 마다 호출
//두번째 매개변수에 []를 대입하면 Mount 될때만 호출

    useEffect(()=>{

        console.log('콜백함수');
    },[])

    const onChangeName = (e) => {

        setName(e.target.value);

    }

    const onChangeLee = (e) => {

        setLee(e.target.value);
    }



    return(

        <>
            <div>
                <input name='name' value={name} onChange={onChangeName}/>
                <input name='lee' value={lee} onChange={onChangeLee}/>
            </div>

            <div>
                <b>이름:{name}</b>
            </div>
            <div>
                별명:{lee}
            </div>
    
        </>
    )


}
export default Info;
  • 두번째 매개변수에 [ ]를 대입하면 Mount 될때만 호출
  • 두번째 매개변수에 state를 나열하면 나열된 state 값이 변경될 때 호출된다.

import React ,{useState,useEffect} from "react"; //useState 에 해당하는 에만 여기{}에 넣어 놓고 시작하겠다

const Info = () => {

    const [name, setName] = useState(''); //초기값
    const [lee, setLee] = useState('');
// 두번째 매개변수를 생성략하면 state가 변경될 때 마다 호출
//두번째 매개변수에 []를 대입하면 Mount 될때만 호출
//두번째 매개변수에 state를 나열하면 나열된 state 값이 변경될 때 호출된다.

    useEffect(()=>{

        console.log('콜백함수');
    },[name])

    const onChangeName = (e) => {

        setName(e.target.value);

    }

    const onChangeLee = (e) => {

        setLee(e.target.value);
    }



    return(

        <>
            <div>
                <input name='name' value={name} onChange={onChangeName}/>
                <input name='lee' value={lee} onChange={onChangeLee}/>
            </div>

            <div>
                <b>이름:{name}</b>
            </div>
            <div>
                별명:{lee}
            </div>
    
        </>
    )


}
export default Info;

  • App.js 를 수정해서 버튼을 누르면 보여지고 사라지도록 작성
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

import React, {useState} from 'react';

function App() {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
    
    </>
    
  );
}

export default App;

import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

import React, {useState} from 'react';

function App() {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
    
    </>
    
  );
}

export default App;


  • uselayoutEffect

    • 사용방법은 useEffect 와 동일

    • useEffect 는 비동기적으로 수행되고 useLayoutEffect는 동기적으로 수행

    • 공식문서에는 useEffect 사용을 권장


  • ref

    • 개요

      • ref는 변수

      • 데이터를 보관할 수 있고 컴포넌트를 가리키는 것이 가능

      • 상태를 유지해야 하는데 화면을 재출력 할 필요가 없는 경우에 state 대신에 ref를 사용

      • 일반적인 자바스크립트는 DOM 객체를 가져올때 id를 설정한 후 document.getElementById()를 이용 하거나 document.querySelector(선택자)를 이용하는데 리액트 에서는 ref사용을 권장

    • 생성

      • const 변수명 = useRef(초기값)
    • 사용

      • 변수명.current
    • 클래스 형 컴포넌트에서는 변수를 사용하고자 할 때는 일반적인 변수 선언을 이용해서 사용

  • Info.jsx 를 수정해서 버튼을 추가한 후 버튼을 누르면 name 과 lee를 초기화 하고 name에 포커스(state를 사용할 수 없음)를 설정

import React ,{useState, useEffect, useRef} from "react"; //useState 에 해당하는 에만 여기{}에 넣어 놓고 시작하겠다

const Info = () => {

    const [name, setName] = useState(''); //초기값
    const [lee, setLee] = useState('');
// 두번째 매개변수를 생성략하면 state가 변경될 때 마다 호출
//두번째 매개변수에 []를 대입하면 Mount 될때만 호출
//두번째 매개변수에 state를 나열하면 나열된 state 값이 변경될 때 호출된다.

    useEffect(()=>{

        console.log('이 함수는 컴포넌트가 화면에서 사라질 때 호출됩니다');
    },[name])

    const onChangeName = (e) => {

        setName(e.target.value);

    }

    const onChangeLee = (e) => {

        setLee(e.target.value);
    }
    //변수 생성
    const nameInput = useRef();
    //버튼을 클릭했을 때 호출되는 함수

    const handleClick = () => {
        setName('');
        setLee(''); //초기화 완료

        nameInput.current.focus();
    }


    return(

        <>
            <div>
                <input name='name' value={name} onChange={onChangeName}
                ref = {nameInput}/>
                <input name='lee' value={lee} onChange={onChangeLee}/>
                <button onClick={handleClick}>초기화 버튼</button>
            </div>

            <div>
                <b>이름:{name}</b>
            </div>
            <div>
                별명:{lee}
            </div>
    
        </>
    )


}
export default Info;

  • 포커스가 이름으로 들어감


  • useState 와 useRef 를 이용한 CRUD 작업

    • 데이터를 추가할 수 있는 화면 과 목록 화면(여러 컴포넌트가 필요함)을 구성

      • 데이터는 App.js가 소유

      • App.js 와 index.js 는 Entry Point

      • 모든 컴포넌트는 App.js에서 출력이 되어야 한다

      • 여러 컴포넌트에서 사용할 데이터가 필요하다면 App.js에 만드는 것
        * 시작하자마자 필요한 데이터가 있다면 App.js에 만드는 것이 좋다

  • 목록을 출력할 컴포넌트를 생성 - UserList.jsx

import React from "react";

function CreateUser({username, email, onChange, onCreate}) {

    return(
        <div>
            <input placeholder="이름을 입력"
            name='username'
            value={username}
            onChange={onChange}/>


            <input placeholder="이메일을 입력"
            name = 'email'
            value={email}
            onChange={onChange}/>
            <button onClick={onCreate}>등록</button>

        </div>


    )
}

export default CreateUser
  • App.js 파일에서 샘플 데이터를 생성하고 UserList 에 출력
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

import React, {useState} from 'react';
import UserList from './components/UserList';

function App() {
  const [visible, setVisible] = useState(false);
  
  // 기본 데이터 생성
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com'
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com'
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net'

    }


  ])

  return (
    <>

      <UserList users={users} />
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;
  • 실행 한 후 데이터 목록이 출력되는지 확인

  • 데이터 삽입 컴포넌트를 생성 - CreateUser.jsx
    • username 과 email을 입력받도록 만들어주고 입력
mport React from "react";

function CreateUser({username, email, onChange, onCreate}) {

    return(
        <div>
            <input placeholder="이름을 입력"
            name='username'
            value={username}
            onChange={onChange}/>


            <input placeholder="이메일을 입력"
            name = 'email'
            value={email}
            onChange={onChange}/>
            <button onClick={onCreate}>등록</button>

        </div>


    )
}

export default CreateUser
  • 데이터를 추가하기 위한 App.js 파일 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com'
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com'
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net'

    }


  ])

  return (
    <>
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} />
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;

  • 이메일을 기준으로 오름 차순 정렬


삭제 구현

  • UserList.jsx 파일에 데이터에 삭제버튼을 추가하고 이벤트 핸들러를 연결
import React from 'react';
function User({ user, onRemove }) {
  return (
    <>
      <p><b>{user.username}</b> <span>({user.email})</span>
      <button onClick={()=> onRemove(user.id)}>삭제</button>
      </p>
    </>
  );
}

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

export default UserList;
  • App.js 파일에 데이터 삭제 함수를 생성하고
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com'
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com'
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net'

    }


  ])

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = (id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  }

  return (
    <>
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onRemove={onRemove}/>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;

  • 삭제버튼 클릭


  • 배열의 데이터를 수정하기 위해서 App.js 파일에 내용을 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = (id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  }

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = (id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  }

  return (
    <>
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;
  • User의 이름을 클릭하면 active 속성의 값을 반전시켜서 이름의 색상을 변경하기 위해서 App.js 파일의 내용을 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = (id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  }

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = (id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  }

  return (
    <>
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;
  • UserList.jsx 파일을 수정
import React from 'react';
function User({ user, onRemove ,onToggle}) {
  return (
    <>
      <p><b onClick={() => onToggle(user.id)} style={{cursor:'pointer', color:user.active ? 'green':'black'}}>
      {user.username}:</b></p>
      <p><span>({user.email})</span>
          <button onClick={()=> onRemove(user.id)}>삭제</button>
      </p>
    </>
  );
}

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 를 이용한 마운트와 언마운트 시 그리고 업데이트 될 때 작업 수행하기
  • UserList.jsx 수정
import React, {useEffect} from 'react';

function User({ user, onRemove ,onToggle}) {
  useEffect(()=> {
    console.log('컴포넌트가 화면에 나타남');
    return ()=>{
      console.log('컴포넌트가 화면에서 사라짐');
    }
  })
  return (
    <>
      <p><b onClick={() => onToggle(user.id)} style={{cursor:'pointer', color:user.active ? 'green':'black'}}>
      {user.username}:</b></p>
      <p><span>({user.email})</span>
          <button onClick={()=> onRemove(user.id)}>삭제</button>
      </p>
    </>
  );
}

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

export default UserList;


  • deps 에 특정 값 넣기

useMemo 를 사용하지 않고 활성 사용자 수 세기

  • App.js 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

//active 속성이 true 인 데이터의 개수 출력하는 함수

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = (id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  }

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = (id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  }

  //active 가 true 인 데이터 수 세기
  //함수를 호출하면 무조건 함수가 수행
  const count = countActiveUsers(users);

  return (
    <>
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
      <div>활성사용자 수 : {count}</div>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;

  • 실행을 해보면 데이터를 입력하는 도중에도 state가 변경되기 때문에 함수를 계속 호출

  • App.js 파일 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';

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

//active 속성이 true 인 데이터의 개수 출력하는 함수

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange = (e) => {
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }

  //등록 처리
  const nextId = useRef(4);
  const onCreate = () => {
    // 추가할 데이터 생성

    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];

    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  }


  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = (id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  }

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = (id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  }

  //active 가 true 인 데이터 수 세기
  //함수를 호출하면 무조건 함수가 수행
  //useMemo 를 이용해서 함수를 호출하면 두 번째 매개변수의 값이 변경된 경우에만
  //함수를 호출해서 연산을 다시 수행한다.
  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>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      

    
    </>
    
  );
}

export default App;


하나의 숫자를 입력받아서 숫자가 입력될 때 마다 입력한 숫자들의 평균을 계산 해서 출력

  • 숫자를 입력받아서 평균을 출력할 컴포넌트 생성
  • Average.jsx
import React, {useState} from "react";
//배열을 매개변수로 받아서 배열의 평균을 리턴해주는 함수 구현

const getAverage = (numbers) => {
    console.log('평균 게산 중입니다');
    if(numbers.length === 0){
        return 0;
    }
    //배열의 데이터 합계
    //reduce 는 배열을 순회하면서 연산을 한 결과를 누적시켜 하나의 값으로 리턴
    //첫번째 매개변수는 누적되는 데이터이고 두번째는 배열의 데이터

    const sum = numbers.reduce((a,b)=> {return a+b;})
    return sum / numbers.length; //나눠줘야죠!

}

const Average = () => {

    const [list,setList] = useState([]);
    const [number, setNumber] = useState('');

    const onChange = (e) => {setNumber(e.target.value)}

    const onInsert = (e) => {
        //number를 정수로 변환시켜 list 와 결합 -> nextList 에 대입
        const nextList = list.concat(parseInt(number));
        //nexList 를 list로 설정
        setList(nextList);
        setNumber('');
    }
    return (
        <div>
            <input value={number} onChange={onChange}/>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index)=> (
                    <li key={index}>{value}</li>

                    

                ))}
            </ul>
            <div>
                <b>평균:</b>{getAverage(list)}
            </div>

        </div>
    )
}
export default Average;
  • App.js 에 컴포넌트 출력
import Average from './components/Average';

// 리턴 안에
<Average/> //추가

  • 실행 한 후 브라우저의 콘솔을 확인해보면 입력 중간에도 평균을 매번 다시 계산

  • ❓ 평균을 언제 계산할지를 잘 생각해야 한다

  • Average.jsx 파일을 수정해서 데이터에 변화가 발생한 경우에만 평균을 다시계산

import React, {useState, useMemo} from "react";
//배열을 매개변수로 받아서 배열의 평균을 리턴해주는 함수 구현

const getAverage = (numbers) => {
    console.log('평균 게산 중입니다');
    if(numbers.length === 0){
        return 0;
    }
    //배열의 데이터 합계
    //reduce 는 배열을 순회하면서 연산을 한 결과를 누적시켜 하나의 값으로 리턴
    //첫번째 매개변수는 누적되는 데이터이고 두번째는 배열의 데이터

    const sum = numbers.reduce((a,b)=> {return a+b;})
    return sum / numbers.length; //나눠줘야죠!

}

const Average = () => {

    const [list,setList] = useState([]);
    const [number, setNumber] = useState('');

    const onChange = (e) => {setNumber(e.target.value)}

    const onInsert = (e) => {
        //number를 정수로 변환시켜 list 와 결합 -> nextList 에 대입
        const nextList = list.concat(parseInt(number));
        //nexList 를 list로 설정
        setList(nextList);
        setNumber('');
    }
    //list에 변화가 생길 때에만 
    const avg = useMemo(()=> {return getAverage(list)}, [list]);
    return (
        <div>
            <input value={number} onChange={onChange}/>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index)=> (
                    <li key={index}>{value}</li>

                    

                ))}
            </ul>
            <div>
                <b>평균:</b>{avg}
            </div>

        </div>
    )
}
export default Average;


useCallback

  • 개요
    • 컴포넌트 파일 안에 함수를 생성하면 컴포넌트가 리랜더링 될 때 마다 다시 생성되는데 자주 리랜더링이 발생하는 컴포넌트의 경우는 함수를 다시 생성하지 않도록 해줘야 함
    • 특정 함수가 특정 데이터가 변경되는 경우에만 다시 생성하도록 해주는 것이 useCallback 이다.
  • 사용 형식
    • useCallback(함수, [deps]) 형태로 사용하면 함수를 재생성해주고 리턴해준다
    • deps를 생략하면 처음 1번만 생성하고 다음부터는 기존 함수를 사용하게 된다.
  • Average.jsx 파일 수정해서 화면이 리랜더링 될 때 마다 만들어지지 않도록 해준다
import React, {useState, useMemo,useCallback} from "react";
//배열을 매개변수로 받아서 배열의 평균을 리턴해주는 함수 구현

const getAverage = (numbers) => {
    console.log('평균 게산 중입니다');
    if(numbers.length === 0){
        return 0;
    }
    //배열의 데이터 합계
    //reduce 는 배열을 순회하면서 연산을 한 결과를 누적시켜 하나의 값으로 리턴
    //첫번째 매개변수는 누적되는 데이터이고 두번째는 배열의 데이터

    const sum = numbers.reduce((a,b)=> {return a+b;})
    return sum / numbers.length; //나눠줘야죠!

}

const Average = () => {

    const [list,setList] = useState([]);
    const [number, setNumber] = useState('');

//함수가 맨 처음 만들어지고 다시는 새로 생성되지 않도록 설정
    const onChange = useCallback( (e) => {setNumber(e.target.value)},[]);

//list 와 number 라는 state 를 사용하기 때문에
// 처음 한 번만 함수를 생성하라고 하면 경고가 발생함

//list 와 number에 변화가 발생하면 함수를 다시 생성
    const onInsert = useCallback((e) => {
        //number를 정수로 변환시켜 list 와 결합 -> nextList 에 대입
        const nextList = list.concat(parseInt(number));
        //nexList 를 list로 설정
        setList(nextList);
        setNumber('');
    },[list,number])
    //list에 변화가 생길 때에만 
    const avg = useMemo(()=> {return getAverage(list)}, [list]);
    return (
        <div>
            <input value={number} onChange={onChange}/>
            <button onClick={onInsert}>등록</button>
            <ul>
                {list.map((value, index)=> (
                    <li key={index}>{value}</li>

                    

                ))}
            </ul>
            <div>
                <b>평균:</b>{avg}
            </div>

        </div>
    )
}
export default Average;
  • 화면상에는 아무런 변화는 없음

  • App.js 수정
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';
import React, {useState,useRef, useMemo, useCallback} from 'react';
import UserList from './components/UserList';
import CreateUser from './components/CreateUser';
import Average from './components/Average';

//active 속성이 true 인 데이터의 개수 출력하는 함수

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange =  useCallback((e) => { //useCallback 작용
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }, [inputs])

  //등록 처리
  const nextId = useRef(4); //useCallback 사용
  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  const onCreate = useCallback( ()=> {
    // 추가할 데이터 생성
    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];
    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  },[email, username, users])

  
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = useCallback((id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  },[users]);

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = useCallback((id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  },[users]);

  //active 가 true 인 데이터 수 세기
  //함수를 호출하면 무조건 함수가 수행
  //useMemo 를 이용해서 함수를 호출하면 두 번째 매개변수의 값이 변경된 경우에만
  //함수를 호출해서 연산을 다시 수행한다.
  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>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      <Average/>

    
    </>
    
  );
}

export default App;


React.memo - 출력성능의 최적화

  • 개요

    • 컴포넌트의 props 가 변경되지 않았다면 리랜더링을 방지해서 출력 성능을 최적화 해주는 함수
    • 이 함수를 이용해서 컴포넌트를 감싸주면 된다
  • export 부분을 감싸주면 된다.

  • CreateUser.jsx

export default React.memo(CreateUser);

  • UserList.jsx
export default React.memo(UserList);

useReducer - 난이도가 높은 편

  • 개요

    • useState 보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트하고자 할 때 사용하는 Hook

    • useState 를 사용하게 되면 컴포넌트 내부에 state 를 업데이트하는 로직을 작성해야 함

      • 하지만, 로직 작성은 MVC 원칙에 위배
    • 컴포넌트의 역할은 화면 출력인데 컴포넌트 안에 처리 로직이 포함됨

    • reducer 는 상태 업데이트 로직을 컴포넌트 외부 혹은 다른 파일에 작성해서 사용할 수 있도록 해준다

    • reducer 를 만들 때는 현재 상테 그리고 업데이트를 위해서 필요한 정보를 담은 액션을 전달 받아서 새로운 상태를 반환하도록 해야 하며 불변성은 지켜야 한다

  • 형태

function reducer(state, action){

상태 변화
}
  • 사용
const [state,dispatch]= useReducer(reducer, 초기 상태);

## 필요할 때 dispatch({type : 동작을 구분하기 위한 상수})
  • state를 이용한 카운터 컴포넌트를 생성 - Counter.jsx 파일을 생성하고 작성
import React, {useState} from "react";

function Counter(){
    //데이터 생성
    const [number, setNumber] = useState(0);
    const onIncrease = () => {
        setNumber(preNumber => preNumber + 1);
    }

    const onDecrease = () => {

        setNumber(preNumber => preNumber -1 );
    }

    // 이런식으로 로직을 컴포넌트에 넣지 말라는 얘기
    return(
        <>
            <h2>{number}</h2>
            <button onClick={onIncrease}>플러스 1</button>
            <button onClick={onDecrease}>마이너스 1</button>
            
        </>
    );


}
export default React.memo(Counter); //리액트 코드를 작성하면 왠만하면 memo 사용할 것
  • App.js
import './App.css';
import ClassEffect from './components/ClassEffect';
import ClassComponent from './components/ClssComponent';
import FunctionComponent from './components/FunctionComponent';
import Info from './components/Info';
import React, {useState,useRef, useMemo, useCallback} from 'react';
import UserList from './components/UserList';
import CreateUser from './components/CreateUser';
import Average from './components/Average';
import Counter from './components/Counter';

//active 속성이 true 인 데이터의 개수 출력하는 함수

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

function App() {
  const [visible, setVisible] = useState(false);
  // 삽입에 필요한 state 생성

  const [inputs, setInputs] = useState({

      username:'',
      email:''
  });
  
  //입력 상자의 내용이 변경될 때 호출되는 함수
  const {username, email} = inputs;
  const onChange =  useCallback((e) => { //useCallback 작용
    //객체의 특정 속성의 값만 수정해서 리턴하고자 하면
    //...inputs 하게되면 inputs 의 모든 내용을 복제해서 새로운 객체를 생성함
    // name 속성에 해당하는 것만 value 로 수정 - > 굉장히 많이 사용함 불변성 떄문
    //리액트에서는 ...을 많이 사용한다
    //리액트의 props 나 state는 직접 수정이 불가하다
    // 객체나 배열을 복사해서 작업하기 때문에 많이 사용
    const {name,value} = e.target;
    setInputs({
      ...inputs,
      [name]: value

    })
  }, [inputs])

  //등록 처리
  const nextId = useRef(4); //useCallback 사용
  // 기본 데이터 생성
  // 속성 추가
  const [users,setUsers] = useState([
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ])
  const onCreate = useCallback( ()=> {
    // 추가할 데이터 생성
    const user={
      id:nextId.current,
      username,
      email
    }
    //users를 복제한 것과 user를 하나의 배열로 생성
    setUsers([...users, user])
    //state 는 직접 수정이 안되므로 일단 복제
    let temp = [...users];
    //email을 기준으로 오름차순 정렬
    temp.sort((a,b)=>{

      if (a.email > b.email) return 1
      else if (a.email === b.email) return 0
      else return -1
    });
    // 정렬한 결과를 state 에 반영
    setUsers(temp);


    //입력 도구 초기화
    setInputs({

      username:'',
      email:''
    })

    nextId.current = nextId.current + 1;
  },[email, username, users])

  
  //전달된 id에 해당하는 데이터의 active 를 반전시키는 함수

  const onToggle = useCallback((id) =>{
    setUsers(
      users.map((user)=>{return user.id === id ? {...user, active:!user.active}: user})
    )
  },[users]);

  //배열에서 전달된 데이터에 해당하는 User 객체를 삭제하기
  // id 가 아닌것만 골라냄
  const onRemove = useCallback((id) => {
    setUsers(users.filter((user)=>{return user.id !== id}));

  },[users]);

  //active 가 true 인 데이터 수 세기
  //함수를 호출하면 무조건 함수가 수행
  //useMemo 를 이용해서 함수를 호출하면 두 번째 매개변수의 값이 변경된 경우에만
  //함수를 호출해서 연산을 다시 수행한다.
  const count = useMemo(()=>countActiveUsers(users), [users]);

  return (
    <>
      <Counter />
      <CreateUser username={username} email={email}
      onChange={onChange} onCreate={onCreate}/>
      <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
      <div>활성사용자 수 : {count}</div>
      <button onClick={()=> {setVisible(!visible)}}>
        {visible ? '숨기기' : '보이기'}
      </button>
      <hr/>
      {visible && <Info/>}
      <hr/>
      {!visible && <ClassEffect/>}

      <Info/>
      <ClassComponent/>
      <FunctionComponent/>
      <ClassEffect/>
      <Average/>

    
    </>
    
  );
}

export default App;

  • Counter.jsx 에서 state 대신에 reducer 사용
import React, {useReducer} from "react"; //useState 지우고 useReducer 로 대체


function reducer(state, action){
    switch(action.type){
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}


function Counter(){
    //데이터 생성
    //reducer 함수의 state 가 number 로 설정되는 것 과 동일한 효과
    //reducer 함수가 리턴하는 결과가 number 에 설정되서 state 를 수정하는 것과 동일한 효과
    const [number, dispatch] = useReducer(reducer, 0); //방금 만든 함수를 가져오고 다음에 초기값을 가져온다
    const onIncrease = () => {
        dispatch({type:'INCREMENT'});
    }

    const onDecrease = () => {

        dispatch({type:'DECREMENT'});
    }

    // 이런식으로 로직을 컴포넌트에 넣지 말라는 얘기
    return(
        <>
            <h2>{number}</h2>
            <button onClick={onIncrease}>플러스 1</button>
            <button onClick={onDecrease}>마이너스 1</button>
            
        </>
    );


}
export default React.memo(Counter); //리액트 코드를 작성하면 왠만하면 memo 사용할 것

  • App.js 를 수정해서 UserList 에도 reducer 를 적용
import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from '/Users/euijoolee/Desktop/React/hooks/src/components/UserList';
import CreateUser from '/Users/euijoolee/Desktop/React/hooks/src/components/CreateUser';

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

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      id:1,
      username:'leeeuijoo',
      email:'leeeuijoo@naver.com',
      active:true
    },


    {
      id:2,
      username:'leejoo',
      email:'leejoo@gmail.com',
      active:true
    
    },

    {
      id:3,
      username:'euijoo',
      email:'euijoo@hanmail.net',
      active:false

    }


  ]
};

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:
          return state;
      }
    }

    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
          }
        });
        nextId.current += 1;
      }, [username, email]);
    
      const onToggle = useCallback(id => {
        dispatch({
          type: 'TOGGLE_USER',
          id
        });
      }, []);
      const onRemove = useCallback(id => {
        dispatch({
          type: 'REMOVE_USER',
          id
        });
      }, []);
    
      const count = useMemo(() => countActiveUsers(users), [users]);
      return (
        <>
          <CreateUser
            username={username}
            email={email}
            onChange={onChange}
            onCreate={onCreate}
          />
          <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
          <div>활성사용자 수 : {count}</div>
        </>
      );
    }
    
    export default App;


profile
무럭무럭 자라볼까

0개의 댓글