컴포넌트 스타일링
1. 스타일 시트를 이용하는 방법 (CSS 파일을 통한 스타일링)
2. Inline Style을 이용하는 방법 (태그 자체에 스타일을 적용하는 방법)
//index.js
import "./Box.css"
const Box = ({ bgColor }) => {
return <div className="box" style={{ backgroundColor: bgColor}}/>
}
export default Box;
3. CSS in JS 방법 (emotion과 같은 CSS 라이브러리를 이용하는 방법)
=> emotion
Emotion 은 JS 로 css 스타일을 작성하도록 설계된 라이브러리
npm i @emotion/react 설치
npm i -D @emotion/babel-plugin 바벨플러그인 설치
create-react-app 에서 사용하기
pragma : 컴파일러에게 명령, /** @jsxImportSource @emotion/react */
crago : CRA로 앱을 만들어서 babel을 건들려면 eject를 해야 한다. eject를 하지 않고 바벨 설정하는 방법
//craco.config.js
module.exports = {
babel: {
presets: ["@emotion/babel-preset-css-prop"],
},
};
파일 생성 후 플러그인 설치
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
Styled Components를 사용해서 React 컴포넌트를 스타일링하는 방법
//index.js
import styled from "@emotion/styled";
const Box = styled.div`
width: 100px;
heigth: 100px;
background-color: cyan;
`;
export default Box;
//App.js
import Box from "./components/Box"
function App() {
return (
<div>
<div css={{ width: 200, heigth: 100, backgroundColor: "black"}} />
//더 간단한 방법
<Box />
</div>
)
}
export default App;
useMemo
컴포넌트는 함수로 구현되어있다.(JSX를 반환하는 함수)
리액트 내부에서 컴포넌트 함수를 실행하여 렌더링함.
- 함수 컴포넌트는 자신의 상태가 변경될 때 리렌더링 된다.
- 부모 컴포넌트로 부터 받는 prop이 변경될 때 리렌더링 된다.
- 부모 컴포넌트의 상태가 변경되면 리렌더링 된다.
이때 내부에 구현된 함수들이나 변수들이 다시 실행되는 과정을 거치게 되는데 리소스 낭비 발생한다.
useMemo는 리액트에서 컴포넌트의 성능을 최적화 하는데 사용되는 훅이다.
useMemo에서 memo는 memoization을 뜻하는데 이는 그대로 해석하면 ‘메모리에 넣기’라는 의미
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
쉽게 말해 동일한 값을 반환하는 함수를 반복적으로 호출해야한다면 처음 값을 계산할 때 해당 값을 메모리에 저장해 필요할 때마다 다시 계산하지 않고 메모리에서 꺼내서 재사용하는 것이다.
리액트에서 함수형 컴포넌트는 렌더링 => 컴포넌트 함수 호출 => 모든 내부 변수 초기화의 순서를 거친다.
useMemo를 사용하면 렌더링 => 컴포넌트 함수 호출 => memoize된 함수 재사용하는 순서를 거친다.
useMemo는 위에 말했듯이 처음에 계산된 값을 메모리에 저장해 컴포넌트가 계속 렌더링되어도 calculate를 다시 호출하지 않고 메모리에 저장되어있는 계산된 값을 가져와 재사용할 수 있게 해준다.
1부터 n까지 더하는 예제
//App.js
import { useState } from 'react'
import ShowSum from './components/ShowSum'
function App() {
const [label, setLabel] = useState("Result");
return (
<div>
<button onClick={() => setLabel(label + ":")}>Change Label</button>
<ShowSum label={label} n={30000000} />
</div>
)
}
export default App;
//ShowSum.js
function sum(n) {
let result = 0;
for (let i = 1; i <= n; i += 1) {
result += i;
}
console.log('fisished');
return result;
};
const ShowSum = ({ label, n }) => {
const result = sum(n)
return (
<span>
{label}: {result}
</span>
)
};
export default ShowSum;
useMemo 구조
const result = useMemo(() => sum(n), [n]);
useMemo는 useEffect처럼 첫 번째 인자로 콜백 함수, 두 번째 인자로 의존성 배열(dependancyArray)을 받는다.
의존성 배열 안에있는 값이 업데이트 될 때에만 콜백 함수를 다시 호출하여 메모리에 저장된 값을 업데이트 해준다.
React.memo
부모 컴포넌트가 렌더링 되면 자식 컴포넌트도 함께 리렌더링 된다.
React.memo를 통해 자식 컴포넌트에 전달된 prop의 상태가 변경되었을 때만 자식 컴포넌트가 렌더링 되도록 최적화 시켜줄 수 있다.
//App.js
import { useState } from 'react'
import Box from './components/Box'
function App() {
const [count, setCount] = useState(0);
return (
<div>
{count}
<button onClick={() => setCount(count + 1)}>+</button>
<Box />
</div>
);
}
export default App;
//Box.js
import React from "react";
const Box = React.memo(() => {
console.log("Render Box")
const style = {
width: 100,
height: 100,
backgroundColor: "red",
}
return <div style={style} />
})
export default Box;
useCallback
useCallback은 인자로 전달한 콜백 함수 그 자체를 메모이제이션 하는 것이다.
함수가 다시 필요할 때마다 함수를 새로 생성하는 것이 아닌 필요할 때마다 메모리에서 가져와서 재사용하는 것이다.
useCallback의 구조
useMemo(() => {
return value;
}. [item])
체크박스 예제
useCallback 을 통해 food가 체크되었을 때 food만 콘솔에 찍히는 것을 볼 수 있음.
즉 컴포넌트가 처음 렌더링 될 때만 이 함수 객체를 만들어서 이 checkbox를 초기화해주고 이후 렌더링에 checkbox 변수가 새로운 함수 객체를 다시 할당받는 것이 아니라 이전에 이미 할당받은 함수 객체를 계속해서 가지고 있으면서 재 사용하는 것을 말한다.
//App.js
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 = 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;
//Checkbox.js
import React from "react";
const Checkbox = React.memo(({ label, on, onChange }) => {
console.log(label, on);
return (
<label>
{label}
<input type="checkbox" defaultChecked={on} onChange={onChange} />
</label>
)
})
export default Checkbox;
Custom hook
기존 훅을 조합하거나 자주 사용되는 로직을 별도 사용자 훅으로 빼서 사용 => 중복코드를 제거, 편하게 사용가능
useToggle
//useToggle.js
import { useCallback, useState } from "react";
const useToggle = (initialState = false) => {
const [state, setState] = useState(initialState);
const toggle = useCallback(() => setState((state) => !state), []);
return [state, toggle];
};
export default useToggle;
//App.js
import useToggle from "./hooks/useToggle";
function App() {
const [on, toggle] = useToggle()
return (
<div>
<button onClick={toggle}>{on ? 'True' : 'False'}</button>
</div>
)
}
export default App;
위의 체크박스 예제를 간단하게 바꿀 수 있다.
//App.js
import Checkbox from './components/Checkbox';
import useToggle from "./hooks/useToggle";
function App() {
const [on, toggle] = useToggle()
return (
<div>
<Checkbox checked={on} onChange={toggle} />
{/* <button onClick={toggle}>{on ? 'True' : 'False'}</button> */}
</div>
)
}
export default App;
useHover
//useHover.js
import { useCallback, useEffect, useRef, useState } 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 el = ref.current;
if (el) {
el.addEventListener("mouseover", handleMouseOver);
el.addEventListener("mouseout", handleMouseOut);
return () => {
el.removeEventListener("mouseover", handleMouseOver);
el.removeEventListener("mouseout", handleMouseOut);
};
}
}, [ref, handleMouseOut, handleMouseOver]);
return [ref, state];
};
export default useHover;
//App.js
import Box from "./components/Box"
import Checkbox from './components/Checkbox';
import useHover from "./hooks/useHover";
import useToggle from "./hooks/useToggle";
function App() {
const [on, toggle] = useToggle()
const [ref, isHover] = useHover()
return (
<div>
<Checkbox checked={on} onChange={toggle} />
{isHover ? "hover" : "mouseout"}
<Box ref={ref} />
</div>
)
}
export default App;
//Box.js
import styled from "@emotion/styled";
const Box = styled.div`
width: 100px;
height: 100px;
background-color: blue;
`
export default Box;
useKeyPress
//useKeyPress.js
import { useCallback, useEffect, useState } from "react";
const useKeyPress = (targetKey) => {
const [keyPressed, setKeyPreseed] = useState(false);
const handleKeyDown = useCallback(({ key }) => {
if (key === targetKey) {
setKeyPreseed(true);
}
},[targetKey]);
const handleKeyUp = useCallback(({ key }) => {
if (key === targetKey) {
setKeyPreseed(false);
}
},[targetKey]);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
}, [handleKeyDown, handleKeyUp]);
return keyPressed
};
export default useKeyPress;
//App.js
import Box from "./components/Box"
import Checkbox from './components/Checkbox';
import useHover from "./hooks/useHover";
import useKeyPress from "./hooks/useKeyPress";
import useToggle from "./hooks/useToggle";
function App() {
const [on, toggle] = useToggle()
const [ref, isHover] = useHover()
const keyPressed = useKeyPress("a")
return (
<div>
<Checkbox checked={on} onChange={toggle} />
{isHover ? "hover" : "mouseout"}
<Box ref={ref} />
{keyPressed && "Pressed"}
</div>
)
}
export default App;
StoryBook
Storybook은 컴포넌트 단위의 UI 개발 도구이다.
React 프로젝트 안에 있는 많은 컴포넌트들을 문서화 할 수 있다.
컴포넌트를 문서화 하고, 눈으로 바로 확인가능하고 상태별로 어떤 스타일을 적용할지 결정할수 있다.
storybook 설치
npx -p @storybook/cli sb init
//Box.js
const Box = ({ width = 100, height = 100, backgroundColor = "red" }) => {
const style = {
width,
height,
backgroundColor,
};
return <div style={style}></div>;
};
export default Box;
//Box.stories.js
import React from "react";
import Box from "../components/Box";
export default {
title: "Example/Box",
component: Box,
argTypes: {
width: { control: "number" },
height: { control: "number" },
backgroundColor: { control: "color" },
},
};
export const Primary = {
args: {
primary: true,
},
};
//Counter.js
import { useState } from 'react'
const Counter = ({ onIncrease }) => {
const [count, setCount] = useState(0)
const handleIncrease = () => {
setCount(count+1);
onIncrease()
}
return (
<div>
<div>{count}</div>
<button onClick={handleIncrease}>+</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({});
Login, 회원가입 폼 만들기
'데브코스 프론트엔드 5기 > React' 카테고리의 다른 글
231206 [Day57] React(3) (0) | 2023.12.13 |
---|---|
231204 [Day55] React(1) (0) | 2023.12.10 |