아직 리액트에 대해 감이 잡히지는 않는다. 배운 내용들을 응용하면서 프로젝트를 진행해봐야 조금 감이 잡힐 듯 하다.
CSS in JS 기능의 라이브러리
npm i @emotion/react @emotion/styled
/** @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;
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를 이용하면 연산의 중복없이 다른 함수를 사용할 수 있다.
부모컴포넌트가 변경되어도 리렌더링 되지 않도록 만듦
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()로 감싸기만 하면 적용가능
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을 사용함으로 인해 각각의 함수들만 실행된다.
코드재사용편리, 중복코드 제거가능
// 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;
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
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;
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
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
// 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({});
우연히 검색을 통해 발견하게 된 프롱이의 TIL!
저는 useCallback 이 좀 어렵더라구요. useCallback은 컴포넌트의 리렌더링 시에 , 함수를 선택적으로 평가(계산) 할 수 있도록 하는 훅인가요?