데브코스 42일차 TIL : React [emotion, useMemo, useCallback, Hook, StoryBook]

te-ing·2021년 10월 18일
1
post-thumbnail

아직 리액트에 대해 감이 잡히지는 않는다. 배운 내용들을 응용하면서 프로젝트를 진행해봐야 조금 감이 잡힐 듯 하다.

컴포넌트에 CSS 적용하기

emotion

CSS in JS 기능의 라이브러리

npm i @emotion/react @emotion/styled

babel없이 emotion 사용

프레그마사용

/** @jsxImportSource @emotions/react */

/** @jsxImportSource @emotion/react */
import Box from './components/Box/index.js';
import { css } from '@emotion/react'

const style = css`
  color: hotpink;
`
const SomeComponent = ({ children }) => (
  <div css={style}>
    Some hotpink text.
    {children}
  </div>
);
function App() {
  return (
    <div>
      <SomeComponent />
    </div>
  );
}

export default App;

use memo

import { useMemo } from 'react';

function sum(n) {
  console.log('Start');
  let result = 0;
  for (let i=1; i<=n; i+=1){
    result += i;
  }
  console.log('Finished');
  return result
}

const ShowSum = ({ label, n }) => {
  const result = useMemo(() => sum(n), [n]); // 기존 const result = sum(n)
  return (
    <span>
      {label}: {result}
    </span>
  )
}

export default ShowSum

use memo 사용을 위해 import { useMemo } from 'react'; 이후 const result = sum(n)const result = useMemo(() => sum(n), [n]) 으로 변경해주었다.

위처럼 오랜시간이 걸리는 함수를 함께 사용할 때 useMemo를 이용하면 연산의 중복없이 다른 함수를 사용할 수 있다.

React.memo

부모컴포넌트가 변경되어도 리렌더링 되지 않도록 만듦

import React from 'react';

const Box = React.memo(() => { // const Box =()=>{} 함수를 React.memo()로 감쌈 
  console.log("Render box");
  const style = {
    width: 100,
    height: 100,
    backgroundColor: "red",
  };
  return <div style={style} />;
});

export default Box;

const Box =()=>{}const Box = React.memo(() => {} 처럼 React.memo()로 감싸기만 하면 적용가능

useCallback

import { useCallback, useState } from "react";
import Checkbox from "./components/Checkbox";

function App() {
  const [foodOn, setFoodOn] = useState(false);
  const [clothesOn, setClothesOn] = useState(false);
  const [shelterOn, setShelterOn] = useState(false);

  // const foodChange = (e) => setFoodOn(e.target.checked);
  const foodChange = useCallback((e) => setFoodOn(e.target.checked), []);
  const clothesChange = useCallback((e) => setClothesOn(e.target.checked), []);
  const shelterChange = useCallback((e) => setShelterOn(e.target.checked), []);

  return (
    <div>
      <Checkbox label="Food" on={foodOn} onChange={foodChange} />
      <Checkbox label="Clothes" on={clothesOn} onChange={clothesChange} />
      <Checkbox label="Shelter" on={shelterOn} onChange={shelterChange} />
    </div>
  );
}

export default App;

컴포넌트가 렌더링될 때 함수가 호출되면서 함수가 동작하면서 재정의된다.
이때 함수가 다시 재정의되지 않도록 만들어준다.

위 코드를 실행할 때 원래라면 food, clothes, shelter 모두 실행되어야 하지만, useCallback을 사용함으로 인해 각각의 함수들만 실행된다.

사용자정의 hook

코드재사용편리, 중복코드 제거가능

// App.js
import Box from "./components/Box";
import useToggle from "./hooks/useToggle";
import useHover from "./hooks/useHover";
import useKeyPress from "./hooks/useKeyPress";

function App() {
  const [on, toggle]  = useToggle();
  const [ref, isHover] = useHover();
  const keyPressed = useKeyPress('a');
  return (
    <div>
      <button onClick={toggle}>{on ? 'True':'False'}</button>

      {isHover ? 'hover':'mouseout'}
      <Box ref={ref}/>

      {keyPressed && "Pressed"}
    </div>
  );
}

export default App;

Toggle 훅

import { useState, useCallback } from "react";

const useToggle = (initialState = false) => {
  const [state, setState] = useState(initialState);
  const toggle = useCallback(() => setState(state => !state), []);

  return [state, toggle];
}

export default useToggle

Hover 훅

import { useCallback, useState, useRef, useEffect } from "react";

const useHover = () => {
  const [state, setState] = useState(false);
  const  ref = useRef(null);

  const handleMouseOver = useCallback(() => setState(true), []);
  const handleMouseOut = useCallback(() => setState(false), []);

  useEffect(() => {
     const element = ref.current;
     if (element){
       element.addEventListener('mouseover', handleMouseOver)
       element.addEventListener('mouseout', handleMouseOut)

       return () => {
         element.removeEventListener("mouseover")
         element.removeEventListener("mouseout")
       }
     }
  }, [ref, handleMouseOut, handleMouseOver])

  return [ref, state]
}

export default useHover;

KeyPress 훅

import { useCallback, useState, useEffect } from "react";

const useKeyPress = (targetKey) => {
  const [keyPressed, setKeyPressed] = useState(false)

  const handleKeyDown = useCallback(({ key }) => {
    if(key === targetKey) {
      setKeyPressed(true)
    } 
  }, [targetKey])

  const handleKeyUp = useCallback(({ key }) => {
    if(key === targetKey) {
      setKeyPressed(false)
    }
  }, [targetKey])

  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown)
    window.addEventListener('keyup', handleKeyUp)
    return () => {
      window.removeEventListener('keydown')
      window.removeEventListener('keyup')
    }
  }, [handleKeyDown, handleKeyUp])

  return keyPressed
}

export default useKeyPress

StoryBook

UI component를 모아서 정리한 툴

설치npx -p @storybook/cli sb init 실행 yarn storybook , yarn start, yarn test --watchAll

아래처럼 사용가능

// Box.js
const Box = ({width = 100, height = 100, backgroundColor = "red" }) => {
  const style = {
    width,
    height,
    backgroundColor,
  }
  return <div style={style} />;
}

export default Box

Actions 플러그인

// Counter.js
import { useState } from "react"

const Counter = ({ onIncrease }) => {
  const [count, setCount] = useState(0)

  const handleIncresae = () => {
    setCount(count + 1)
    onIncrease()
  }

  return (
    <div>
      <div>{count}</div>
      <button onClick={handleIncresae}>+</button>
    </div>
  )
}

export default Counter;

// Counter.stories.js
import React from 'react';
import Counter from '../components/Counter';

export default {
  title: 'Example/Counter',
  component: Counter,
  argTypes: { onIncrease : { action: "increased"}}
};

const Template = (args) => <Counter {...args} />;

export const Default = Template.bind({});

profile
프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2021년 10월 25일

우연히 검색을 통해 발견하게 된 프롱이의 TIL!
저는 useCallback 이 좀 어렵더라구요. useCallback은 컴포넌트의 리렌더링 시에 , 함수를 선택적으로 평가(계산) 할 수 있도록 하는 훅인가요?

1개의 답글