Hooks & Context

스카치·2023년 2월 22일
0

Hook :

  • class 컴포넌트에서만 사용가능하던 state와 lifecycle을 함수 컴포넌트에서도 쓸수 있게해줌.
  • state와 관련된 로직을 재사용가능하게 해줌.

Basic Hooks

  • useState
  • useEffect
  • useContext
npx create-react-app react-hooks-example
cd react-hooks-example // 리액트 내부 라이브러리라 설치 필요x
code . -r

class component로 state 처리
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 Clicke {count} times </p>
        <button onClick={this.click}>Click me</button>
      </div>
    );
  }

  click = () => {
    this.setState({ count: this.state.count + 1 });
  };
}

function component로 state 처리

src => components => Example2.jsx =>

import React from "react";

export default function Example2() {
  const [count, setCount] = React.useState(0);

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

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

src => components => Example3.jsx =>

import React from "react";

// 현재 useState => count
// useState => {count : 0 } 바꾸기
export default function Example3() {
  const [state, setState] = React.useState({ count: 0 });

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

  function click() {
    setState({ count: state.count + 1 });
  }
}
 /* 
 	함수의 인자로 state를 받으니까 state에 의존적으로 처리하지 않음
   function click() {
    setState((state) => ({
      return {
        count: state.count + 1,
      };
    });
  }
 */ 


function 컴포넌트에서 state를 다루게 된 이유

렌더 사이에서 바뀐 state를 정확히 표현해야한다면
렌더 사이에서 state를 ref 삼아 활용할 땐 좋지 않음

src => components => Example5.jsx =>

import React from "react";

export default function Example5() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log("componentDidMount1", count);
  }); // react dependency list(빈 배열) => 최초에만 실행
  // 없으면 렌더 후 항상 실행

  React.useEffect(() => {
    console.log("componentDidMount2", count);
  }, []);

  React.useEffect(() => {
    console.log("componentDidMount & componentDidUpdate by count", count);
  }, [count]); // count가 바뀔 때만 update되어 useEffect 실행


  
  // componentWillUnmount
  React.useEffect(() => {
    console.log("componentDidMount & componentDidUpdate by count", count);

    return () => {
      // cleanUp
      // useEffeect안의 함수가 새로운 함수를 리턴하도록
      // 다음 render, dependency에 의해 useEffect가 실행될 때 먼저 실행된 후 다음 useEffeect가 실행
      // dependency가 비었으므로 최초 한번만 실행 후 실행한 함수 컴포넌트가 사라질 때 될 때 return부분 실행
      // == componentWillUnmount
      console.log("cleanup by count", count);
    };
  }, []);

  React.useEffect(() => {
    console.log("componentDidMount & componentDidUpdate by count", count);
    return () => {
      //cleanup
      console.log("cleanup by count", count);
    };
  }, [count]);
  // 최초 실행시 ->> componentDidMount & componentDidUpdate by count", 0
  // 두번째 렌더시 ->> cleanup by count" 0 , componentDidMount & componentDidUpdate by count", 1

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

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

useEffect :

  • react dependency list가 없으면 렌더시 항상 실행(componentDidMount와 유사)
  • react dependency list가 빈 배열이면 최초 렌더 시에만 실행
  • react dependency list가 있으면 react dependency list가 변할 때마다
    update 되어 useEffect 실행 (= componentDidMount, componentDidUpdate by react dependency list)

dependency , clean up 이해하기

Custom Hooks

useSometing

브라우저의 가로창 사이즈가 변경되었을 때 변경된 사이즈의 숫자를 가져오는 hook 만들기

src => hooks=> useWindowWidth.js
hook안에 다른 훅을 쓸 수 있다.

import React, { useState, useEffect } from "react";
export default function useWindowWidth() {
  const [width, setWidth] = React.useState(window.innerWidth);

  // // 사이즈 변경될 때마다 출력하기
  useEffect(() => {
    const resize = () => {
      setWidth(window.innerWidth);
    };
    window.addEventListener("resize", resize);
    //cleanup
    return () => {
      window.removeEventListener("resize", resize);
    };
  }, []);
  return width;
}


App.js =>

function App() {
	const width = useWindowWidth();
  
  	return (
    ...
      {width}
    )
}

useHasMounted vs withHasMounted

hooks => useHasMounted.js
src => hocs => withHasMounted.jsx

import React from "react";

export default function withHasMounted(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 = `withHasMounted(${Component.name})`;
  return NewComponent;
}

useHasMounted.js =>

import { useEffect, useState } from "react";

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

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

  return hasMounted;
}

App.js =>

function App({hasMounted}){
	const hasMountedFromHook = useHasMounted();
  	console.log(hasMounted, hasMountedfromHooks);
}

Additional Hooks

useReducer

useState의 확장판
다수의 하위 값을 포함하는 복잡한 정적로직을 만들때
다음스테이트가 이전 스테이트에 의존적인경우
리덕스를 안다면 쉽게 사용가능

component => Example6.jsx


// 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 Clicke {state.count} times </p>
        <button onClick={click}>Click me</button>
      </div>
    )
  
  function click() {
  	dispatch({type: "PLUS"}) =
  }
  
}

useCallback, useMemo

component => Example7.jsx


function sum(persons) {
  	console.log('sum...')
	return persons.map(person => person.age).reduce( (l,r) => l + r, 0 )
}

export default function Example7 () {
	const [value,setValue] = useState('')
  	const [persons] = useState([
      {name: 'Mark', age:39},
      {name: 'hanna', age:28},
    ])
    
 const count = useMemo(() => {
   return sum(persons);
 } , [persons])
 
 const click = useCallback( () => {
 	console.log(value)
 }, [])
    
  return (
    	<div>
      		<input value={} onChange={} />
      		<p>{count}</p>
		<button onClick={click}> click </button>
      	</div>

    )
    
    function change(e) {
    	setValue(e.target.value)
    }
}

components => Example8.jsx

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

  return (
    	<div>
      		<input value={} onChange={} />
      		<input ref={input1Ref} />
      		<input ref={input2Ref} />


      	</div>
    )
    
    function change(e) {
    	setValue(e.target.value)
    }
}

useRef, useimperativeHandle

useLayoutEffect

useDebugValue

React Router Hooks

React Router Example =>
useHistory :props로 들어오는 히스토리랑 ㅇ같은 히스토리를 구해서 주는 Hook
= withRouter

const history = useHistory()

Profile =>

const params = useParams()

컴포넌트 간 통신

npx create-react-app component-comunication
cd component-comunication
code . -r
npm start

하위 컴포넌트 변경하기

src => components => A.jsx

export default function A() {
  	const [value] = useState('아직 안바뀜')
	return ( 
  		<div>
       		<B value={value}/>
      		<Button onclick= /> E의 값을 바꾸기 </Button>
        </div>
      )
      	
}

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

App.js =>
```js
return (
	<A />
)

상위 컴포넌트 변경하기

Context Api

컴포넌트간 통신을 쉽게 도와줌

npx create-react-app react-context-example
cd react-context-example
code . -r
npm start

src => contexts => PersonContext.js

import React from 'react'
const PersonContext = React.createContext()

export default PersonContext;

index.js =>

const persons = [
  {id: 0, name: 'Mark', age: 39}
  {id: 1, name: 'Hanna', age: 28}
]
<PersonContext.Provider value={persons}>
  <App />
<PersonContext.Provider>

components => Example1.jsx

export default function Example1() {
	return (
      <PersonContext.Consumer>
        { (persons) => (
          <ul> 
              {persons.map( (person) => (
                  <li>{person.name}</ li>
              ))}
          </ul>
          )} 
      <PersonContext.Consumer>
	)
}
// class, function 컴포넌트 다 사용가능

components => Example2.jsx

export default class Example2 extends React.Component {
	// static contextType = PersonContext; // class 컴포넌트에서만
  
  	render() {
      const persons = this.context;
      return (
        <ul> 
              {persons.map( (person) => (
                  <li>{person.name}</ li>
              ))}
		</ul>
        )
    }
}

// function 컴포넌트에서 사용하고 싶다면
// Example2.contextType = PersonContext

단점 = static contextType는 여러개를 지정못함
만약 다른 context에서 데이터를 동시에 가져와 사용하고 싶다면 컴포넌트를 하나 더 만들어서 가져와야함.


가장 많이 쓰임

components => Example3.jsx

export default function Example3() {
	const persons = useContext(PersonContext)
  
  return (
	<ul> 
		{persons.map( (person) => (
    		<li>{person.name}</ li>
        ))}
    </ul>

	)
}

0개의 댓글