Hooks are a new addition in React 16.8

Basic Hooks(1)

npx create-react-app react-hooks-ex

// src/components/Example1.jsx
// 클래스

import React from 'react';

export default class Example1 extends React.Component {
  
  state={count:0};
  
  render(){
    const {count}= this.state;

    return(
      <div>
        <p>You clicked {count} times</p>
        <button onClick={this.click}>Click me</button>
      </div>
    );
  }

  click = () => {
    this.setState({count: this.state.count + 1});
  }
  
}
// src/components/Example2.jsx
// 함수

import { React, useState } from 'react';

export default function Example2 () {

  const [count, setCount] = useState(0);

  return(
    <div>
      <p>You clicked { count } times</p>
      <button onClick={ click }>Click me</button>
    </div>
  );

  function click(){
    setCount( count + 1 );
  }
}
// src/components/Example3.jsx
// 함수

import { React, useState } from 'react';

// useState => count
// useState => { count: 0 };
export default function Example3 () {

  const [state, setState] = useState({ count: 0 });

  return(
    <div>
      <p>You clicked { state.count } times</p>
      <button onClick={ click }>Click me</button>
    </div>
  );

  function click(){
    // setState({ count: state.count + 1 });
    setState((state) => {
      return {
        count: state.count + 1
      };
    });
  }
// App.js

import logo from './logo.svg';
import './App.css';
import Example1 from './components/Example1';
import Example2 from './components/Example2';
import Example3 from './components/Example3';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Example1 />
        <Example2 />
        <Example3 />
      </header>
    </div>
  );
}

export default App;
  • 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어려움
    • 컨테이너 방식 말고, 상태와 관련된 로직
  • 복잡한 컴포넌트들은 이해하기 어려움
  • Class 는 사람과 기계를 혼동 시킴
    • 컴파일 단계에서 코드를 최적화하기 어렵게 만듦
  • this.state 는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있음
    • 좋은 점 : render 사이에서 바뀐 State을 표현해야 할 때는 좋음
    • 안 좋은 점 : render 사이에 State를 레퍼런스로 사용하기에는 좋지 않음

Basic Hooks(2)

useState

  • state 를 대체 할 수 있음

useEffect

  • 라이프 사이클 훅을 대체할 수 있음
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount
// src/components/Example4.jsx

import React from 'react';

export default class Example4 extends React.Component {
  
  state={count:0};
  
  render(){
    const {count}= this.state;

    return(
      <div>
        <p>You clicked {count} times</p>
        <button onClick={this.click}>Click me</button>
      </div>
    );
  }

  componentDidMount(){
    console.log("componentDidMount", this.state.count);
  }

  componentDidUpdate(){
    console.log("componentDidUpdate", this.state.count);
  }

  click = () => {
    this.setState({count: this.state.count + 1});
  }
  
}
// src/components/Example5.jsx

import { React, useState, useEffect } from 'react';

export default function Example5 () {

  const [count, setCount] = useState(0);

  useEffect(()=>{
    // console.log("componentDidMount", count);
    console.log("componentDidMount");

    return () => {
      // cleanup
      // componentWillUnmount
    };

  }, []); // 비워두면 렌더가 계속 됨, []을 넣어주면 최초에만 실행됨


  useEffect(()=>{
    console.log("componentDidMount & componentDidUpdate by count", count);
  
    return () => {
      // cleanup
      console.log('cleanup by count', count);
    };

  }, [count]);

  return(
    <div>
      <p>You clicked { count } times</p>
      <button onClick={ click }>Click me</button>
    </div>
  );

  function click(){
    setCount( count + 1 );
  }
}

Custom Hooks

useSomething

// src/hooks/useWindowWidth.js

import { useEffect, useSate } from "react";

export default function useWindowWidth () {
  const [width, setWidth] = useSate(window.innerWidth); // 현재 브라우저 해상도

  useEffect(()=>{

    const resize = () => {
      setWidth(window.innerWidth);
    };

    window.addEventListener("resize", resize);

    return () => {
      window.removeEventListener('resize', resize);
    };

  }, []);

  return width;
}
// App.js

import logo from './logo.svg';
import './App.css';
import useWindowWidth from './hooks/useWindowWidth';

function App() {

  const width = useWindowWidth();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        { width }
      </header>
    </div>
  );
}

export default App;

useHasMounted vs withHasMounted

// src/hooks/useHasMounted.js

import { useEffect, useState } from 'react';

export default function useHasMounted () {
  
  const [hasMounted ,setHasMounted] = useState(false);

  useEffect(()=>{
    setHasMounted(true); 
  }, []);
  
  return hasMounted;

}
// src/hocs/useWithHasMounted.js

import React from 'react';

export default function WidthHasMounted (Component) {

  class NewComponent extends React.Component {
    
    state = {
      hasMounted: false
    }
    
    render () {
      const {hasMounted}=this.state;
      return <Component {...this.props} hasMounted={hasMounted} />;
    }
    componentDidMount() {
      this.setState({hasMounted: true});
    }
  }

  NewComponent.displayName = `WidthHasMounted(${Component.name})`;

  return NewComponent;

}
// App.js

import logo from './logo.svg';
import './App.css';
import WidthHasMounted from './hocs/WidthHasMounted';
import useHasMounted from './hooks/useHasMounted';
// import useWindowWidth from './hooks/useWindowWidth';

function App({hasMounted}) {

  const hasMountedFromHooks = useHasMounted();
  console.log(hasMounted, hasMountedFromHooks);

  // const width = useWindowWidth();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" /> 
       {width}
      </header>
    </div>
  );
}

export default WidthHasMounted(App);

Additional Hooks

useReducer

useCallback, useMemo

useRef, useImperativeHandle

useLayoutEffect

userDebugValue

useReducer

  • 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우
  • 다음 state 가 이전 state 에 의존적인 경우
  • Redux 를 안다면 쉽게 사용 가능
// useReducer
// src/components/Example6.jsx

import { useReducer } from "react";

// reducer => state 를 변경하는 로직이 담겨있는 함수
const reducer = (state, action) => {

  if(action.type === "PLUS") {
    return {
      count: state.count + 1
    };
  }
  return state;

};

// dispatch => action 객체를 넣어서 실행 

// action => 객체이고 필수 프로퍼티로 type 을 가짐

export default function Example6 () {

  const [state, dispatch] = useReducer(reducer, {count: 0});

  return(
    <div>
      <p>You Clicked {state.count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );

  function click(){
    dispatch({ type: 'PLUS' })
  }

}
// useReducer
// src/components/Example6.jsx

import { useReducer } from "react";

// reducer => state 를 변경하는 로직이 담겨있는 함수
const reducer = (state, action) => {

  if(action.type === "PLUS") {
    return {
      count: state.count + 1
    };
  }
  return state;

};

// dispatch => action 객체를 넣어서 실행 

// action => 객체이고 필수 프로퍼티로 type 을 가짐

export default function Example6 () {

  const [state, dispatch] = useReducer(reducer, {count: 0});

  return(
    <div>
      <p>You Clicked {state.count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );

  function click(){
    dispatch({ type: 'PLUS' })
  }

}
// src/components/Example8.jsx
// createRef, useRef

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

export default function Example8 () {
  
  const [value, setValue] = useState('');
  const input1Ref = createRef();
  const input2Ref = useRef();  

  console.log(input1Ref.current, input2Ref.current); // 최초 null undefined, input에 "a"입력 => null <input>

  return (
    <div>
      <input value={value} onChange={change} />
      <input ref={input1Ref} />
      <input ref={input2Ref} />
    </div>
  );
  function change(e) {
    setValue(e.target.value);
  }
}

React Router Hooks

useHistory(), useParams()

컴포넌트 간 통신

npx create-react-app component-communication

하위 컴포넌트 변경하기

A 의 button 을 클릭하여 E 를 변경하려면?

  1. <A /> 컴포넌트에서 button 에 onClick 이벤트를 만들고,
  2. button 을 클릭하면, <A /> 의 state 를 변경하여, <B /> 로 내려주는 props 를 변경
  3. <B /> 의 props 가 변경되면, <C /> 의 props 로 전달
  4. <C /> 의 props 가 변경되면, <D /> 의 props 로 전달
  5. <D /> 의 props 가 변경되면, <E /> 의 props 로 전달

// src/components/A.jsx

import { useState } from "react";

export default function A () {

  const [value, setValue] = useState('아직 안 바뀜');

  return (
    <div>
      <B value={value}/>
      <button onClick={click}>E의 값 바꾸기</button>
    </div>
  );

  function click(){
    setValue("E의 값을 변경");
  }
}

function B({value}) {
  return (
    <div>
      <p>여긴 B</p>
      <C value={value} />
    </div>
  );
}

function C({value}) {
  return (
    <div>
      <p>여긴 C</p>
      <D value={value} />
    </div>
  );
}

function D({value}) {
  return (
    <div>
      <p>여긴 D</p>
      <E value={value} />
    </div>
  );
}

function E({value}) {
  return (
    <div>
      <p>여긴 E</p>
      <h3>{value}</h3>
    </div>
  );
}

상위 컴포넌트 변경하기

E의 button 을 클릭하여 A 의 P 를 변경하려면?

  1. <A /> 에 함수를 만들고, 그 함수 안에 state 를 변경하도록 구현, 그 변경으로 인해 p 안의 내용을 변경.
  2. 만들어진 함수를 props 에 넣어서, <B /> 로 전달
  3. <B /> 의 props 의 함수를 <C /> 의 props 로 전달
  4. <C /> 의 props 의 함수를 <D /> 의 props 로 전달
  5. <D /> 의 props 의 함수를 <E /> 의 props 로 전달, <E /> 에서 클릭하면 props 로 받은 함수를 실행
// src/components/A.jsx

import { useState } from "react";

export default function A () {
  const [value, setValue] = useState('아직 안 바뀜');
  return (
    <div>
      <p>{value}</p>
      <B setValue={setValue} />
    </div>
  );
}

function B({setValue}) {
  return (
    <div>
      <p>여긴 B</p>
      <C setValue={setValue} />
    </div>
  );
}

function C({setValue}) {
  return (
    <div>
      <p>여긴 C</p>
      <D setValue={setValue} />
    </div>
  );
}

function D({setValue}) {
  return (
    <div>
      <p>여긴 D</p>
      <E setValue={setValue} />
    </div>
  );
}

function E({setValue}) {
  return (
    <div>
      <p>여긴 E</p>
      <button onClick={click}>Click</button>
    </div>
  );

  function click(){
    setValue('A 의 값을 변경');
  }

}

Context API

npx create-react-app react-context-ex

React 공식 사이트

context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.

일반적인 React 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달되지만, 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 (예를 들면 선호 로케일, UI 테마) 이 과정이 번거로울 수 있습니다. context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.

하위 컴포넌트 전체에 데이터를 공유하는 법

  • 데이터를 Set
    • 가장 상위 컴포넌트 ⇒ Provider
  • 데이터를 Get
    • 모든 하위 컴포넌트에서 접근 가능
      • Consumer
      • Class Component 의 this.context
      • Functional Component 의 useContext

데이터를 Set 하기

  1. Context 생성 (Context API)
  2. Context, Provider 사용
  3. Value 사용 (Provider 에 Data 전달)
// src/contexts/PersonContext.js

import { createContext } from "react";

const PersonContext = createContext();

export default PersonContext;
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import PersonContext from './contexts/PersonContext';

const persons = [
  { id: 0, name: "Mark", age: 39 },
  { id: 1, name: "Jane", age: 28 }
]

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <PersonContext.Provider value={persons}>
      <App />
    </PersonContext.Provider>
  </React.StrictMode>
);

reportWebVitals();

데이터를 Get 하기(1) - Consumer

  1. Context 가져오기
  2. Context, Consumer 사용
  3. Value 사용
// src/components/Example1.jsx

import PersonContext from "../contexts/PersonContext";

export default function Example1 () {
  return (
    <PersonContext.Consumer>
    {(persons) => {
      <ul>
        {persons.map((person) => (
          <li>{person.name}</li>
        ))}
      </ul>
    }}
    </PersonContext.Consumer>
  );
}

데이터를 Get 하기(2) - class

  1. static contextType 에 컨텍스트를 설정
  2. this.context ⇒ value
// src/components/Example2.jsx

import React from "react";
import PersonContext from "../contexts/PersonContext";

export default class Example2 extends React.Component {
  
  // static contextType = PersonContext;
  
  render(){

    const persons = this.context;

    return(
      <ul>
        {persons.map((person) => (
          <li>{person.name}</li>
        ))}
      </ul>
    );
  }
}

Example2.contextType = PersonContext;
export default App;

데이터를 Get 하기(2) - functional

(가장 많이 쓰는 방법)

  1. useContext 로 Context 를 인자로 호출
  2. useContext 의 return 이 Value
// src/components/Example3.jsx

import { useContext } from "react";
import PersonContext from "../contexts/PersonContext";

export default function Example3 () {

  const persons = useContext(PersonContext);

  return (
  
      <ul>
        {persons.map((person) => (
          <li>{person.name}</li>
        ))}
      </ul>
  
  );
}
profile
성장하는 개발자 유슬이 입니다!

0개의 댓글