ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 개발자 지망생 스터디 - 25일차
    스터디/KAKAOCLOUDSCHOOL 2022. 12. 7. 10:02

    React_hooks

    1. 컴포넌트 반복

    • 동일한 모양의 컴포넌트를 여러 개 배치
    • 배열 형태의 데이터를 출력할 때 유용하게 사용

    1) 배열.map() - 변환

    • 배열의 데이터를 순회하면서 매개변수로 받은 함수를 요소 단위로 수행한 후 그 결과를 모아서 다시 배열로 리턴하는 함수
    • map에 설정하는 매개변수
    #첫번째는 callback 함수(필수) - 매개변수는 3개까지 될 수 있고 반드시 하나의 데이터를 리턴해야함

    첫번째 매개변수는 순회하는 각 요소
    두번째 매개변수는 인덱스
    세번째 매개변수는 배열 그 자체

    #두번째는 callback 함수 내부에서 사용할 this 참조(선택)
    let ar = [10, 20, 30];
    // 위의 배열에 5를 더한 배열을 생성
    for(let i=0; i<ar.length;i++){
    	ar[i] = ar[i] + 5;
    }
    // react에서는 이런 구조는 성립되지 않음 - props나 state는 값을 수정할 수 없기 때문
    
    let ar = [10, 20, 30];
    let result = [];
    // 위의 배열에 5를 더한 배열을 생성
    let size = ar.length;
    for(let i=0; i<size; i++){
    	result.push(ar[i] + 5);
    }
    
    let ar = [10, 20, 30];
    let result = ar.map(e => return e + 5);

     

    2) 배열을 이용한 컴포넌트

    # React 프로젝트 생성
    > yarn create react-app react_hooks

    # src 디렉토리에 Iteration.jsx 파일을 만들고 반복문을 이용해 Component를 생성하는 코드를 작성

    🗂react_hooks -> 📁 src -> (CREATE) 📄 Iteration.jsx
    import React from 'react';
    
    const Iteration = () => {
    	const names = ['JavaScript', 'Java', 'Python', 'C#', 'C++', 'Rust'];
    	// 배열을 순회하면서 <li> 태그로 감싸기
    	const nameList = names.map(name => <li>{name}</li>);
        
    	return (<ul>{nameList}</ul>);
    }
    export default Iteration;


    # App.js 파일을 수정해서 Iteration 컴포넌트 출력

    🗂react_hooks -> 📁 src -> 📄 App.js

    import Iteration from "./Iteration";
    
    function App() {
      return (
        <div>
          <Iteration/>
        </div>
      );
    }
    
    export default App;
    Iteration 컴포넌트 출력 결과 (yarn start)

    > 실행을 하고 확인을 하면 출력은 제대로 되는데 콘솔 창에는 에러가 있음
    > React에서는 Component 배열을 랜더링 할 때 어떤 원소에 변동이 있었는지 확인하기 위해서 key 값을 설정해야 함
    > key를 설정하지 않으면 변동 사항을 확인 하기 위해서 배열 전체를 순회해야 하지만 key를 설정하면 더욱 빠르게 변동 사항을 확인 할 수 있음
    > 배열에서는 index를 키로 설정하면 무난함.

    Console 결과


    # Iteration.jsx 파일 수정
    📁 src -> 📄 Iteration.jsx
    > const nameList = names.map((name, index) => <li key={index}>{name}</li>);
    import React from 'react';
    
    const Iteration = () => {
    	const names = ['JavaScript', 'Java', 'Python', 'C#', 'C++', 'Rust'];
    	// 배열을 순회하면서 <li> 태그로 감싸기
    	const nameList = names.map((name, index) => <li key={index}>{name}</li>);
        
    	return (<ul>{nameList}</ul>);
    }
    export default Iteration;​
    콘솔 창에 에러가 사라진 것을 확인해 볼 수 있음

    3) 유동적인 데이터 적용

    • state 는 변경이 되면 자동으로 component 에 적용
    ☑︎ props : 상위 컴포넌트가 하위 컴포넌트에게 데이터를 넘겨주고자 할 때 사용
    ☑︎ state : 컴포넌트 내에서 생성해서 수정과 삭제가 가능한 개체로 변경이 되면 컴포넌트를 리랜더링
    ☑︎ ref : 컴포넌트를 조작하고자 할 때 생성
    ☑︎ filter는 매개변수가 1개이고 boolean을 리턴하는 함수를 매개변수로 대입하는데 함수의 결과가 true인 데이터만 모아서 다시 배열로 리턴하는 함수

    # Iteration.jsx 파일을 수정
    🗂react_hooks -> 📁 src -> 📄 Iteration.jsx
    import React, {Component} from 'react';
    
    class Iteration extends Component {
        //내용이 변경되면 Component를 리랜더링하는 state를 생성
        state = {
            names:['JavaScript'],
            name:''
        }
        //Input에 입력하면 name state 의 값을 변경하는
        //이벤트 처리 함수
        handleChange = (e) =>{
            this.setState({
                name:e.target.value
            })
        }
        //name 의 값을 names에 추가하는 함수 - 버튼을 누르면 동작
        //push 대신에 배열을 복제해서 연결하는 concat 함수 이용
        handleInsert = (e) =>{
            this.setState({
                names:this.state.names.concat(this.state.name),
                name:''
            })
        }
    
        //데이터 삭제 함수
        //index를 매개변수로 받아서 삭제
        handleRemove = (index) => {
            //확인 대화상자를 출력해서 아니오를 누르면 리턴
            let result = window.confirm("정말로 삭제");
            if(result === false){
                return;
            }
    
            const {names} = this.state;
            //const names = this.state.names;
    
            //slice: 매개변수 2개 받아서 
            //배열을 잘라내서 복제해서 리턴하는 함수
            //매개변수는 시작 위치와 마지막 위치를 대입
            /*
            this.setState({
                names:[names.slice(0, index),
                names.slice(index+1, names.length)]
            })
            */
    
            //넘어온 인덱스와 배열의 인덱스가 다른 것 만 추출
            this.setState({
                names:names.filter((item, e) => e !== index )
            })
        }
    
       render(){
            const nameList = this.state.names.map(
                (name, index) => <li key={index} 
                onDoubleClick={(e) => this.handleRemove(index)}>{name}
                <button onClick={(e) => this.handleRemove(index)}>삭제</button></li>);
    
            return (<div>
                <input onChange={this.handleChange}
                value={this.state.name}/>
                <button onClick={this.handleInsert}>추가</button>
                <ul>{nameList}</ul>
            </div>)
        }
    }
    
    export default Iteration;

     

    2. Life Cycle

    • 인스턴스가 생성되고 소멸될 때 까지의 과정
    • 생성은 생성자(Constructor)가 담당을 하고 소멸은 소멸자(Destructor)가 담당함
    • IoC(Inversion of Control - 제어의 역전, 클래스는 개발자가 생성하지만 인스턴스의 생성과 소멸은 개발자가 하지 않고 다른 프레임워크나 컨테이너 등이 하는 형태)가 적용된 경우에는 특별한 경우가 아니면 생성자를 이용해서 인스턴스를 생성하지 않음
    • IoC가 적용되면 일반적으로 생성자를 직접 호출하지 않기 떄문에 수명 주기 관련 메서드를 이용해서 생성 과 소멸될 때 작업을 수행

    1) react의 Component의 수명 주기

    (1) Mount(컴포넌트가 메모리 할당을 받아서 출력)
    (2) Update(컴포넌트 정보를 업데이트 하는 것으로 리랜더링)
    (3) Unmount(컴포넌트가 화면에서 제거)

    2) Mount 될 때 호출되는 메서드

    ☑︎ constructor : 생성자 - 가장 먼저 호출, state 초기화를 수행
    ☑︎ getDerivedStateFromProps : props 에 있는 값을 state 에 동기화 할 때 호출
    ☑︎ render : 랜더링 할 때 호출되는 메서드로 this.props와 this.state를 이용해서 props와 state 접근이 가능
    ☑︎ componenetDidMount : 화면에 보여지고 난 후 호출되는 메서드, 비동기(네트워크 사용이나 타이머 작용) 작업 수행

    3) update 할 때 호출되는 메서드

    ☑︎ getDerivedStateFromProps : props 에 있는 값을 state 에 동기화 할 때 호출
    ☑︎ shouldComponentUpdate : 리랜더링을 결정하는 메서드로 여기서 false를 리턴하면 리랜더링 되지 않음
    ☑︎ render
    ☑︎ getSnapshotBeforeUpdate : 변경된 내용을 DOM에 적용하기 직전에 호출되는 메서드
    ☑︎ componentDidUpdate : 업데이트 되고 나면 호출되는 메서드

    4) Unmount 될 때 호출되는 메서드

    ☑︎ componentWillUnmount : 사라지기 전에 호출되는 메서드로 memory leak(타이머)이 발생할 수 있는 객체의 제거 작업을 수행

    5) 라이프 사이클 이용 시 주의할 점

    • react의 개발 모드가 React.StrictMode 로 설정되어 있으면 개발 환경에서는 Mount를 2번씩 함

    3. 에러를 화면에 출력

    1) 에러를 발생시키기 위해서 Iteration.jsx 파일에서 없는 프로퍼티를 호출

    {this.state.errorexample.value}

    2) state 관련된 에러가 발생하면 호출되는 메서드를 재정의해서 에러가 발생했다는 사일을 출력해주는 컴포넌트를 생성 -ErrorBoundary.jsx

    🗂react_hooks -> 📁 src -> (CREATE) 📄 ErrorBoundary.jsx
    import React, {Component} from 'react';
    
    class ErrorBoundary extends Component{
        state = {
            error:false
        }
        //컴포넌트에서 예외가 발생하면 호출되는 메서드
        componentDidCatch(error, info){
            this.setState({
                error:true
            })
            console.log({error, info});
        }
    
        render(){
            //error 가 true이면 출력
            if(this.state.error){
                return <div>에러가 발생했습니다.</div>;
            }
            //error 가 false 이면 하위 컴포넌트 출력 
            else{
               return this.props.children;
            }
        }
    }
    
    export default ErrorBoundary;

    3) App.js 수정

    🗂react_hooks -> 📁 src -> 📄 App.js

    import ErrorBoundary from "./ErrorBoundary";
    import Iteration from "./Iteration";
    
    function App() {
      return (
        <div>
          <ErrorBoundary>
          <Iteration/>
          </ErrorBoundary>
        </div>
      );
    }
    
    export default App;

    4. Hooks

    • React 16.8 버전에 추가
    • Class Component의 기능을 Function Component에서 사용할 수 있도록 해주는 기능

    5. useState

    > state를 함수 컴포넌트 안에서 사용할 수 있도록 해주는 hook
    > useState의 매개변수는 state의 초기값이 되고 리턴하는 데이터는 state와 state의 값을 변경할 수 있는 setter 함수의 배열

    # 컴포넌트에서의 state 사용 - App.js 수정
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React, {Component, useState} from 'react';
    
    //클래스 컴포넌트
    class ClassState extends Component{
      /*
      //생성자를 만들지 않고 이렇게 state를 초기화해도 됩니다.
      state = {
        count:0
      }
      */
    
      constructor(props){
        super(props);
        this.state = {
          count:0
        }
      }
    
      render(){
        return(
          <div>
            <p>클릭을 {this.state.count} 번 수행</p>
            <button onClick={
              (e) => this.setState(
                {count: this.state.count + 1})}>
              +1    
            </button>
          </div>
        );
      }
    }
    
    //함수형 컴포넌트에서 state 사용
    //함수형 컴포넌트는 인스턴스가 아니므로 this 사용 불가
    const FunctionState = () => {
      const [count, setCount] = useState(0);
      return(
        <div>
            <p>클릭을 {count} 번 수행</p>
            <button onClick={
              (e) => setCount(count + 1)}>
              +1    
            </button>
          </div>
      )
    }
    
    function App() {
      return (
        <div>
          <ClassState/>
          <FunctionState />
        </div>
      );
    }
    
    export default App;

    6. useRef

    > ref : 컴포넌트를 조작하기 위해서 붙이는 일종의 id와 같은 변수
    > useRef 로 만들어진 변수는 값이 변경된다고 해서 컴포넌트가 리랜더링 되지는 않음
    > useRef(초기값) 으로 변수를 생성하고 컴포넌트나 DOM에 설정할 때는 ref 속성에 생성된 변수를 대입해주면 됨

    ✔️ 특정 Input 에 포커스 설정하기
    # InputSample.jsx 파일을 생성하고 컴포넌트 생성 - state 와 ref 도 사용

    🗂react_hooks -> 📁 src -> (CREATE) 📄 InputSample.jsx
    import React, {useState, useRef} from 'react';
    
    const InputSample = () => {
        //2개의 속성을 가진 state 생성
        const [inputs, setInputs] = useState({
            name:'',
            nickname:''
        });
    
        //state를 편리하게 사용하기 위해서 비 구조화 할당
        const {name, nickname} = inputs;
    
        //react 에서 다른 컴포넌트 나 DOM을 참조할 수 있는 변수를 생성
        const nameInput = useRef();
    
        const onChange = (e) => {
            setInputs({
                [e.target.name]:e.target.value
            })
        }
    
        const onReset = (e) => {
            setInputs({
                name:'',
                nickname:''
            })
            //이름 입력 란으로 포커스 옮기기
            nameInput.current.focus();
        }
    
        return(
            <div>
                <input name="name" value={name}
                onChange={onChange} ref={nameInput}/>
                <input name="nickname" value={nickname}
                onChange={onChange}/>
                <button onClick={onReset}>초기화</button>
            </div>
        )
    
    }
    
    export default InputSample;

    # App.js 에서 InputSample 출력하는 코드 추가
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React, {Component, useState} from 'react';
    import InputSample from './InputSample';
    
    //클래스 컴포넌트
    class ClassState extends Component{
      /*
      //생성자를 만들지 않고 이렇게 state를 초기화해도 됩니다.
      state = {
        count:0
      }
      */
    
      constructor(props){
        super(props);
        this.state = {
          count:0
        }
      }
    
      render(){
        return(
          <div>
            <p>클릭을 {this.state.count} 번 수행</p>
            <button onClick={
              (e) => this.setState(
                {count: this.state.count + 1})}>
              +1    
            </button>
          </div>
        );
      }
    }
    
    //함수형 컴포넌트에서 state 사용
    //함수형 컴포넌트는 인스턴스가 아니므로 this 사용 불가
    const FunctionState = () => {
      const [count, setCount] = useState(0);
      return(
        <div>
            <p>클릭을 {count} 번 수행</p>
            <button onClick={
              (e) => setCount(count + 1)}>
              +1    
            </button>
          </div>
      )
    }
    
    function App() {
      return (
        <div>
          <ClassState/>
          <FunctionState />
          <InputSample />
        </div>
      );
    }
    
    export default App;

    7. useEffect

    • state 가 변경된 후 수행할 side effect를 설정하는 Hook
    • Class 의 LifeCycle 중에서 componentDidMount 와 componentDidUpdate, ComponentWillUnmount가 합쳐진 형태

    1) Class 의 수명주기 메서드 사용

    # App.js 수정
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React from 'react';
    
    class ClassEffect extends React.Component{
      //생성자
      constructor(props){
        super(props);
        console.log("생성자 - 가장 먼저 호출되는 메서드");
        this.state = {
          count:0
        };
      }
    
      //Component 가 Mount 된 후 호출되는 메서드
      componentDidMount(){
        console.log("마운트 된 후 호출되는 메서드");
        document.title = `You clicked ${this.state.count} times`;
      }
    
      //Component 가 Update 된 후 호출되는 메서드
      componentDidUpdate(){
        console.log("업데이트 된 후 호출되는 메서드");
        document.title = `You clicked ${this.state.count} times`;
      }
    
      render(){
        return(
          <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={
              (e) => this.setState({count:this.state.count+1})}>
                +1
            </button>
          </div>
        )
      }
    }
    
    const App = () => {
      return(
        <div>
          <ClassEffect />
        </div>
      )
    }
    export default App;

    > 실행하고 검사 창을 열어서 console을 확인해보면 생성자 와 마운트가 2번 되는 것을 확인할 수 있는데
       이는 개발 환경에서 strict mode 가 적용되면 그렇게 됨

    2) useEffect를 이용해서 함수형 컴포넌트에 위와 유사한 형태 구현

    import React, {useState, useEffect} from 'react';
    
    const ClassEffect = () => {
      const [count, setCount] = useState(0);
    
      useEffect(()=>{
        console.log("마운트 와 업데이트가 끝나면 호출");
        document.title = `You clicked ${count} times`;
      })
    
      
        return(
          <div>
            <p>You clicked {count} times</p>
            <button onClick={
              (e) => setCount(count + 1)}>
                +1
            </button>
          </div>
        )
    }
    
    const App = () => {
      return(
        <div>
          <ClassEffect />
        </div>
      )
    }
    export default App;

    3)useEffect 함수

    > useEffect(() => {수행할 내용}, deps 배열} 이 원형

    deps 배열을 생략하면 Mount 된 경우 와 모든 state 가 변경될 때 마다 호출
    deps 배열을 비워두게 되면([ ]) Mount 된 후에만 호출
    deps 배열에 state를 설정하게 되면 state 값이 변경될 때도 호출

    > 수행할 내용에서 함수를 리턴하면 cleanup 함수가 됨.
    cleanup 함수는 Unmount 될 때 호출되는 함수

    8. 객체 배열을 함수형 컴포넌트로 출력하고 삽입, 삭제, 갱신을 수행하기

    1) 기본 데이터 출력 

    > 하나의 객체(이름 과 이메일을 소유하고 기본키로 id를 소유)를 출력할 컴포넌트 와 객체 배열을 출력할 컴포넌트를 작성

    # UserList.jsx

    🗂react_hooks -> 📁 src -> 📄 UserList.jsx
    import React from 'react';
    
    function User({user}){
        return(
            <div>
                <b>{user.username}</b>
                <span>({user.email})</span>
            </div>
        )
    }
    
    function UserList({users}){
        return(
            <div>
                {users.map(
                    user => <User user={user} key={user.id}/>)}
            </div>
        )
    }
    
    export default UserList;​

    # App.js 에서 기본 데이터를 생성하고 UserList를 출력
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React from 'react';
    import UserList from './UserList';
    const App = () => {
      const users = [
        {id:1, username:'adam', email:'itstudy@kakao.com'},
        {id:2, username:'군계', email:'ggangpae1@gmail.com'}
      ]
    
      return(
        <div>
          <UserList users={users} />
        </div>
      )
    }
    export default App;

    2)데이터 추가 구현

    # 추가 화면에 해당하는 컴포넌트 생성 - CreateUser.jsx
    🗂react_hooks -> 📁 src -> (CREATE) 📄 CreateUser.jsx
    > 기본 데이터는 App.js 파일에 존재
    > 실제 데이터 추가는 App.js 파일에 있는 데이터에 추가되어야 함
    이런 경우에는 App.js state 나 이벤트 핸들러를 만들고 CreateUser에게 props 형태로 전달을 해서 사용하는 것이 편리함
    import React from 'react';
    
    function CreateUser({username, email, onChange, onCreate}){
        return(
            <div>
                <input name="username" value={username}
                onChange={onChange} placeholder="이름 입력"/>
                <input name="email" value={email}
                onChange={onChange} placeholder="이메일 입력"/>
                <button onClick={onCreate}>추가</button>
            </div>
        )
    }
    
    export default CreateUser;​

     

     

    # App.js 파일을 수정해서 데이터 추가 화면을 출력
    import React from 'react';
    import UserList from './UserList';
    
    import CreateUser from './CreateUser';
    import {useState, useRef} from 'react';
    
    const App = () => {
      const [inputs, setInputs] = useState({
        username:'',
        email:''
      });
      const {username, email} = inputs;
    
      const onChange = (e) => {
        //state를 수정할 때는 원본을 복제한 후 수정
        setInputs({
          ...inputs,
          [e.target.name]:e.target.value
        });
      }
    
      //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록
      //state로 생성
      const [users, setUsers] = useState([
        {id:1, username:'adam', email:'itstudy@kakao.com'},
        {id:2, username:'군계', email:'ggangpae1@gmail.com'}
      ]);
    
      //변수를 생성
      const nextId = useRef(3);
    
      //데이터 삽입 메서드
      const onCreate = () => {
        //하나의 객체 생성
        const user = {
          id:nextId.current,
          username,
          email
        }
        //users 에 user를 추가
        setUsers([...users, user]);
    
        //입력 요소 초기화
        setInputs({
          username:'',
          email:''
        })
        //다음 id를 위해서 id를 1증가
        nextId.current += 1;
      }
    
    
    
      return(
        <div>
          <CreateUser username={username} email={email}
            onChange={onChange} onCreate={onCreate} />
          <UserList users={users} />
        </div>
      )
    }
    export default App;

    3) 삭제 구현

    > 데이터를 출력할 때 이메일 뒤에 삭제 버튼을 추가해서 삭제 버튼을 누르면 그 데이터가 삭제되도록 구현 삭제의 경우 대부분 기본키를 매개변수로 받아서 그 기본키에 해당하는 데이터를 삭제

    # UserList.jsx 수정
    🗂react_hooks -> 📁 src -> 📄 UserList.jsx
    import React from 'react';
    
    function User({user, onRemove}){
        return(
            <div>
                <b>{user.username}</b>
                <span>({user.email})</span>
                <button onClick={(e) => 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;


    # App.js 파일에 삭제하는 함수를 만들고 UserList에게 넘겨주기
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React from 'react';
    import UserList from './UserList';
    
    import CreateUser from './CreateUser';
    import {useState, useRef} from 'react';
    
    const App = () => {
      const [inputs, setInputs] = useState({
        username:'',
        email:''
      });
      const {username, email} = inputs;
    
      const onChange = (e) => {
        //state를 수정할 때는 원본을 복제한 후 수정
        setInputs({
          ...inputs,
          [e.target.name]:e.target.value
        });
      }
    
      //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록
      //state로 생성
      const [users, setUsers] = useState([
        {id:1, username:'adam', email:'itstudy@kakao.com'},
        {id:2, username:'군계', email:'ggangpae1@gmail.com'}
      ]);
    
      //변수를 생성
      const nextId = useRef(3);
    
      //데이터 삽입 메서드
      const onCreate = () => {
        //하나의 객체 생성
        const user = {
          id:nextId.current,
          username,
          email
        }
        //users 에 user를 추가
        setUsers([...users, user]);
    
        //입력 요소 초기화
        setInputs({
          username:'',
          email:''
        })
        //다음 id를 위해서 id를 1증가
        nextId.current += 1;
      }
    
      //삭제하는 함수
      //users state 에서 id가 id 인 데이터 삭제
      //id 가 일치하지 않는 데이터만 삭제
      //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서
      //수정합니다.
      const onRemove = id => 
        setUsers(users.filter(user => user.id !== id));
    
      return(
        <div>
          <CreateUser username={username} email={email}
            onChange={onChange} onCreate={onCreate} />
          <UserList users={users} onRemove={onRemove}/>
        </div>
      )
    }
    export default App;

    4) 배열의 데이터를 수정

    •  계정 이름을 클릭하면 active 속성 값을 toggle(반전) 시켜서 글자 색상을 변경하도록 하기
    # UserList.jsx 수정
    🗂react_hooks -> 📁 src -> 📄 UserList.jsx
    import React from 'react';
    
    function User({user, onRemove, onToggle}){
        return(
            <div>
                <b onClick={(e)=>onToggle(user.id)}
                style={{
                    cursor:'pointer', 
                    color:user.active?'green':'black'
                }}>{user.username}</b>
                <span>({user.email})</span>
                <button onClick={(e) => 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;​


    # App.js 수정
    🗂react_hooks -> 📁 src -> 📄 App.js
    import React from 'react';
    import UserList from './UserList';
    
    import CreateUser from './CreateUser';
    import {useState, useRef} from 'react';
    
    const App = () => {
      const [inputs, setInputs] = useState({
        username:'',
        email:''
      });
      const {username, email} = inputs;
    
      const onChange = (e) => {
        //state를 수정할 때는 원본을 복제한 후 수정
        setInputs({
          ...inputs,
          [e.target.name]:e.target.value
        });
      }
    
      //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록
      //state로 생성
      const [users, setUsers] = useState([
        {id:1, username:'adam', email:'itstudy@kakao.com', active:false},
        {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true}
      ]);
    
      //변수를 생성
      const nextId = useRef(3);
    
      //데이터 삽입 메서드
      const onCreate = () => {
        //하나의 객체 생성
        const user = {
          id:nextId.current,
          username,
          email
        }
        //users 에 user를 추가
        setUsers([...users, user]);
    
        //입력 요소 초기화
        setInputs({
          username:'',
          email:''
        })
        //다음 id를 위해서 id를 1증가
        nextId.current += 1;
      }
    
      //삭제하는 함수
      //users state 에서 id가 id 인 데이터 삭제
      //id 가 일치하지 않는 데이터만 삭제
      //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서
      //수정합니다.
      const onRemove = id => 
        setUsers(users.filter(user => user.id !== id));
    
      //수정하는 메서드
      //id에 해당하는 데이터의 active 속성의 값을 반전
      const onToggle = id => 
        setUsers(users.map(user => user.id === id ? 
            {...user, active:!user.active} : user))
    
      return(
        <div>
          <CreateUser username={username} email={email}
            onChange={onChange} onCreate={onCreate} />
          <UserList users={users} onRemove={onRemove}
          onToggle={onToggle}/>
        </div>
      )
    }
    export default App;

    5) useEffect 활용

    # UserList.jsx 파일의 User 컴포넌트에 useEffect 작성
    🗂react_hooks -> 📁 src -> 📄 UserLisx.jsx
    import React, {useEffect} from 'react';
    
    function User({user, onRemove, onToggle}){
        //마운트 될 때 그리고 state 가 변경될 때 모두 호출
        useEffect(() => {
            console.log('컴포넌트가 화면에 나타남');
            //삽입 과 수정 시 호출
            //이 데이터의 id가 존재하면 수정이고 
            //id가 존재하지 않으면 삽입
            console.log(user)
            //함수를 리턴하면 컴포넌트가 사라질 때 호출
            return () => {
                //데이터를 삭제한 경우 호출
                console.log('컴포넌트가 사라짐')
                console.log(user);
            }
        }, [user]);
    
        return(
            <div>
                <b onClick={(e)=>onToggle(user.id)}
                style={{
                    cursor:'pointer', 
                    color:user.active?'green':'black'
                }}>{user.username}</b>
                <span>({user.email})</span>
                <button onClick={(e) => 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;

    9. useMemo

    • 연산된 값을 재사용하는 Hook
    • 성능 최적화를 위해서 사용
    • 매개변수로 연산을 수행하는 함수 와 배열을 대입받는데 배열에 변화가 생긴 경우에만 연산을 수행하는 함수를 수행하고 그렇지 않은 경우는 함수를 호출해도 결과만 다시 제공함

    1) App.js를 수정해서 현재 active 가 true 인 user 수를 출력하기

    • 실행하면 입력 내용을 수정해도 유저 수를 다시 계산합니다.
    import React from 'react';
    import UserList from './UserList';
    
    import CreateUser from './CreateUser';
    import {useState, useRef} from 'react';
    
    //active 가 true 인 데이터의 개수
    const countActiveUser = (users) => {
        console.log("사용자 수를 세기");
        return users.filter(user => user.active).length;
    }
    
    const App = () => {
      const [inputs, setInputs] = useState({
        username:'',
        email:''
      });
      const {username, email} = inputs;
    
      const onChange = (e) => {
        //state를 수정할 때는 원본을 복제한 후 수정
        setInputs({
          ...inputs,
          [e.target.name]:e.target.value
        });
      }
    
      //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록
      //state로 생성
      const [users, setUsers] = useState([
        {id:1, username:'adam', email:'itstudy@kakao.com', active:false},
        {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true},
        {id:3, username:'카리나', email:'karina@sm.com', active:true}
      ]);
    
     
    
      //변수를 생성
      const nextId = useRef(4);
    
      //데이터 삽입 메서드
      const onCreate = () => {
        //하나의 객체 생성
        const user = {
          id:nextId.current,
          username,
          email
        }
        //users 에 user를 추가
        setUsers([...users, user]);
    
        //입력 요소 초기화
        setInputs({
          username:'',
          email:''
        })
        //다음 id를 위해서 id를 1증가
        nextId.current += 1;
      }
    
      //삭제하는 함수
      //users state 에서 id가 id 인 데이터 삭제
      //id 가 일치하지 않는 데이터만 삭제
      //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서
      //수정합니다.
      const onRemove = id => 
        setUsers(users.filter(user => user.id !== id));
    
      //수정하는 메서드
      //id에 해당하는 데이터의 active 속성의 값을 반전
      const onToggle = id => 
        setUsers(users.map(user => user.id === id ? 
            {...user, active:!user.active} : user))
      
      //활성화된 user 개수를 세는 함수 호출
      const count = countActiveUser(users);
    
      return(
        <div>
          <CreateUser username={username} email={email}
            onChange={onChange} onCreate={onCreate} />
          <UserList users={users} onRemove={onRemove}
          onToggle={onToggle}/>
          <div>활성화된 유저 수: {count}</div>
        </div>
      )
    }
    export default App;

    2) useMemo를 이용해서 해결

    import React from 'react';
    import UserList from './UserList';
    
    import CreateUser from './CreateUser';
    import {useState, useRef, useMemo} from 'react';
    
    //active 가 true 인 데이터의 개수
    const countActiveUser = (users) => {
        console.log("사용자 수를 세기");
        return users.filter(user => user.active).length;
    }
    
    const App = () => {
      const [inputs, setInputs] = useState({
        username:'',
        email:''
      });
      const {username, email} = inputs;
    
      const onChange = (e) => {
        //state를 수정할 때는 원본을 복제한 후 수정
        setInputs({
          ...inputs,
          [e.target.name]:e.target.value
        });
      }
    
      //배열의 데이터를 수정하면 컴포넌트가 리랜더링 될 수 있도록
      //state로 생성
      const [users, setUsers] = useState([
        {id:1, username:'adam', email:'itstudy@kakao.com', active:false},
        {id:2, username:'군계', email:'ggangpae1@gmail.com', active:true},
        {id:3, username:'카리나', email:'karina@sm.com', active:true}
      ]);
    
     
    
      //변수를 생성
      const nextId = useRef(4);
    
      //데이터 삽입 메서드
      const onCreate = () => {
        //하나의 객체 생성
        const user = {
          id:nextId.current,
          username,
          email
        }
        //users 에 user를 추가
        setUsers([...users, user]);
    
        //입력 요소 초기화
        setInputs({
          username:'',
          email:''
        })
        //다음 id를 위해서 id를 1증가
        nextId.current += 1;
      }
    
      //삭제하는 함수
      //users state 에서 id가 id 인 데이터 삭제
      //id 가 일치하지 않는 데이터만 삭제
      //실제로는 id가 일치하지 않는 데이터만 가지고 배열을 만들어서
      //수정합니다.
      const onRemove = id => 
        setUsers(users.filter(user => user.id !== id));
    
      //수정하는 메서드
      //id에 해당하는 데이터의 active 속성의 값을 반전
      const onToggle = id => 
        setUsers(users.map(user => user.id === id ? 
            {...user, active:!user.active} : user))
      
      //활성화된 user 개수를 세는 함수 호출
      //users 에 변화가 생긴 경우만 함수를 호출하고
      //그 이외의 경우는 결과를 복사하도록 수정
      const count = useMemo(()=>countActiveUser(users), [users]);
    
      return(
        <div>
          <CreateUser username={username} email={email}
            onChange={onChange} onCreate={onCreate} />
          <UserList users={users} onRemove={onRemove}
          onToggle={onToggle}/>
          <div>활성화된 유저 수: {count}</div>
        </div>
      )
    }
    export default App;

    3)새로운 컴포넌트를 추가해서 useMemo 동작을 확인

    > 하나의 숫자 데이터를 추가하면 추가될 때 마다 평균을 구해서 출력하는 컴포넌트를 생성 

    # Average.jsx 생성

    🗂react_hooks -> 📁 src -> 📄 Average.jsx
    import React, {useState} from 'react';
    
    //배열의 평균을 구해서 리턴해주는 함수
    const getAverage = (numbers) => {
        console.log("평균 계산");
        if(numbers.length === 0)
            return 0;
        //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴
        //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고
        //두번째 매개변수는 연산을 시작할 때의 초기값입니다.
        //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정
        //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데
        //첫번째는 누적값이고 두번째는 배열의 요소
        //세번째는 배열의 인덱스이고 네번째는 배열
    
        //[10, 20, 130, 240]
        //10+20=30, 30+130=160, 160+240=400 400리턴
        const sum = numbers.reduce((a, b) => a + b);
        return sum / numbers.length;
    }
    
    const Average = () => {
        //숫자 배열
        const [list, setList] = useState([]);
        //입력받은 내용
        const [number, setNumber] = useState('');
        //input에 내용을 수정할 때 호출될 메서드
        const onChange = e => {setNumber(e.target.value)};
        //추가를 눌렀을 때 호출될 메서드
        const onInsert = e => {
            const nextList = list.concat(parseInt(number));
            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 파일에 배치하고 숫자 데이터를 입력하면서 콘솔을 확인 - 입력 도중에 평균을 계산하는 함수를 호출

    # Average.jsx 파일에 useMemo를 import 하고 getAverage 호출하는 부분을 useMemo를 이용하도록 수정
    import React, {useState, useMemo} from 'react';
    
    //배열의 평균을 구해서 리턴해주는 함수
    const getAverage = (numbers) => {
        console.log("평균 계산");
        if(numbers.length === 0)
            return 0;
        //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴
        //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고
        //두번째 매개변수는 연산을 시작할 때의 초기값입니다.
        //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정
        //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데
        //첫번째는 누적값이고 두번째는 배열의 요소
        //세번째는 배열의 인덱스이고 네번째는 배열
    
        //[10, 20, 130, 240]
        //10+20=30, 30+130=160, 160+240=400 400리턴
        const sum = numbers.reduce((a, b) => a + b);
        return sum / numbers.length;
    }
    
    
    const Average = () => {
        
        //숫자 배열
        const [list, setList] = useState([]);
        //useMemo를 이용해서 getAverage를 호출
        //list에 변화가 생긴 경우만 메서드를 호출하고
        //그 이외의 경우는 결과를 재사용합니다.
        const avg = useMemo(() => getAverage(list), [list])
    
        //입력받은 내용
        const [number, setNumber] = useState('');
        //input에 내용을 수정할 때 호출될 메서드
        const onChange = e => {setNumber(e.target.value)};
        //추가를 눌렀을 때 호출될 메서드
        const onInsert = e => {
            const nextList = list.concat(parseInt(number));
            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>{avg}
                </div>
            </div>
        )
    }
    
    export default Average;
    > 다시 실행을 하면 입력 도중에는 함수를 호출하지 않고 추가를 눌러서 list에 변화가 생긴 경우만 함수를 호출함
Designed by Tistory.