스터디/KAKAOCLOUDSCHOOL

[React] 개발자 지망생 스터디 - 26일차

shineIT 2022. 12. 7. 17:50

10. useCallback

1) 개요

> 특정 함수를 새로 만들지 않고 재사용하고자 할 때 사용
> 컴포넌트에 구현한 함수들은 컴포넌트가 랜더링될 때 마다 다시 생성
> 컴포넌트가 많아지고 랜더링이 자주 발생하면 함수들을 다시 만드는 것은 비효율적이 될 수 있음
> useCallback을 이용하면 데이터가 변경된 경우에만 함수를 다시 만들도록 할 수 있음
> 첫번째 매개변수는 함수이고 두번째 매개변수는 데이터의 배열임

2) Average.jsx 파일을 수정

🗂 react_hooks → 📁 src → 📄 Average.jsx

import React, {useState, useMemo, useCallback} from 'react';

//배열의 평균을 구해서 리턴해주는 함수
const getAverage = (numbers) => {
    console.log("평균 계산");
    if(numbers.length === 0)
        return 0;
    //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴
    //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고
    //두번째 매개변수는 연산을 시작할 때의 초기값입니다.
    //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정
    //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데
    //첫번째는 누적값이고 두번째는 배열의 요소
    //세번째는 배열의 인덱스이고 네번째는 배열

    //[10, 20, 130, 240]
    //10+20=30, 30+130=160, 160+240=400 400리턴
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
}


const Average = () => {
    
    //숫자 배열
    const [list, setList] = useState([]);
    //useMemo를 이용해서 getAverage를 호출
    //list에 변화가 생긴 경우만 메서드를 호출하고
    //그 이외의 경우는 결과를 재사용합니다.
    const avg = useMemo(() => getAverage(list), [list])

    //입력받은 내용
    const [number, setNumber] = useState('');
    //input에 내용을 수정할 때 호출될 메서드
    const onChange = useCallback(e => {setNumber(e.target.value)},
    []);
    //추가를 눌렀을 때 호출될 메서드
    //이 함수는 number 와 list가 변경될 때 만 다시 생성
    const onInsert = useCallback(e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }, [number, list]);

    return(
        <div>
            <input value={number} onChange={onChange}/>
            <button onClick={onInsert}>추가</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균:</b>{avg}
            </div>
        </div>
    )
}

export default Average;

11. React.memo

1) 개요

> 컴포넌트의 props 가 변경되지 않았다면, 리랜더링을 방지해서 리랜더링의 성능 최적화를 해줄 수 있는 함수
> 이 함수를 이용해서 컴포넌트를 감싸주기만 하면 리랜더링이 필요한 상황에서만 리랜더링을 함

2) Average.jsx 수정

🗂 react_hooks → 📁 src → 📄 Average.jsx

import React, {useState, useMemo, useCallback} from 'react';

//배열의 평균을 구해서 리턴해주는 함수
const getAverage = (numbers) => {
    console.log("평균 계산");
    if(numbers.length === 0)
        return 0;
    //reduce는 배열을 순회하면서 연산을 수행한 후 하나의 값을 리턴
    //매개변수는 2개인데 첫번째 매개변수는 수행할 함수이고
    //두번째 매개변수는 연산을 시작할 때의 초기값입니다.
    //두번째 매개변수를 생략하면 배열의 첫번째 요소로 설정
    //첫번째 매개변수인 함수는 매개변수를 4개까지 갖는데
    //첫번째는 누적값이고 두번째는 배열의 요소
    //세번째는 배열의 인덱스이고 네번째는 배열

    //[10, 20, 130, 240]
    //10+20=30, 30+130=160, 160+240=400 400리턴
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
}


const Average = () => {
    
    //숫자 배열
    const [list, setList] = useState([]);
    //useMemo를 이용해서 getAverage를 호출
    //list에 변화가 생긴 경우만 메서드를 호출하고
    //그 이외의 경우는 결과를 재사용합니다.
    const avg = useMemo(() => getAverage(list), [list])

    //입력받은 내용
    const [number, setNumber] = useState('');
    //input에 내용을 수정할 때 호출될 메서드
    const onChange = useCallback(e => {setNumber(e.target.value)},
    []);
    //추가를 눌렀을 때 호출될 메서드
    //이 함수는 number 와 list가 변경될 때 만 다시 생성
    const onInsert = useCallback(e => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }, [number, list]);

    return(
        <div>
            <input value={number} onChange={onChange}/>
            <button onClick={onInsert}>추가</button>
            <ul>
                {list.map((value, index) => (
                    <li key={index}>{value}</li>
                ))}
            </ul>
            <div>
                <b>평균:</b>{avg}
            </div>
        </div>
    )
}

export default React.memo(Average);

 


react styling

  • 스타일 적용

1. styling 방식

  • 일반 CSS 사용
  • Sass : CSS 전처리기(pre-processor) 를 이용하는 방식으로 확장된 CSS 문법 사용
  • CSS Module : 스타일을 작성할 때 CSS 클래스 이름이 다른 CSS 클래스 이름과 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해주는 옵션
  • styled-components : 컴포넌트 안에 스타일을 내장시키는 방식으로 동일한 스타일이 적용된 컴포넌트를 사용하는 방식
    - 💡 실제 애플리케이션 제작 작업을 할 때 이런 방식으로 만들어진 컴포넌트들을 많이 이용

2. 일반 CSS 적용

1) 개요

> webpack 의 css-loader 를 이용해서 일반 CSS 를 불러오는 방식
> react 프로젝트를 생성하면 App.js 가 App.css를 불러와서 적용

2) Naming

> react 프로젝트에서는 컴포넌트-클래스 이름의 형태로 네이밍
   App-header : App 컴포넌트 안에 header라는 클래스를 의미함
> BEM(Block Element Modifier) 방법 - CSS 에서는 가장 많이 사용
   각각의 요소는 - 나 _ 로 구분함
   블럭요소이름__요소이름__수정자이름 의 형태로 네이밍
> SMACSS(Scalable and Modular Architecture for CSS) - 확장형 모듈식 구조
   Base에는 아무런 접두어도 붙이지 않고 Layout 관련된 경우는 I - 이름을 정하는 방식
   State의 경우는 is- 또는 s- 등을 추가
   용도를 파악하기 편리
> 이외에도 OOCSS 등이 있음

3) App.css를 수정해서 적용

🗂react_styling → 📁 src → 📄 App.js
> App.css 파일에 추가 (App 클래스 안에 a 태그)
.App a{
  color: #333333;
}

 

3. CSS Module 사용

1) 개요

> CSS를 불러와서 사용할 때 클래스 이름을 고유한값으로 만들어서 적용
   [파일이름]_[클래스이름]_[해시값] 을 추가해서 클래스 이름을 부여
> 사용하는 방법은 CSS 파일의 확장자를 .module.css 로 만들면 됨
> 이 기능을 사용할 때는 CSS 파일의 클래스 이름을 일반적인 이름으로 사용하면 됨
> 별도의 클래스 이름을 부여하지 못하도록 하고자 하는 경우에는 클래스 이름 앞에 : global을 추가하면 됨

2) 적용

# CSS 파일을 생성 - 이름.module.css
🗂react_styling → 📁 src → (CREATE) 📄 CSSModule.module.css
/* 파일 안에서만 사용하는 클래스 */
.wrapper{
    background: black;
    padding: 1em;
    color: white;
    font-size: 2rem;
}

/* 모든 곳에서 사용할 수 있는 클래스 - 이름을 수정하지 않음 */
:global .something{
    font-weight: 800;
    color: aqua;
}

# css 파일의 디자인을 적용할 컴포넌트 생성
🗂react_styling → 📁 src → (CREATE) 📄 CSSModule.jsx
import React from "react";
import styles from "./CSSModule.module.css";

const CSSModule = () => {
    return(
        <div className = {styles.wrapper}>
            처음 사용해보는
            <span className="something">CSS Module</span>
        </div>
    )

}
export default CSSModule;

3) 여러 개의 클래스 동시 적용

# CSSModule.module.css 파일에 클래스 추가
📁 src → 📄 CSSModule.module.css
.inverted{
    color:black;
    background: white;
    border: 1px solid black;
}​

# CSSModule.jsx 파일 수정
📁 src → 📄 CSSModule.jsx

import React from "react";
import styles from "./CSSModule.module.css";

const CSSModule = () => {
    return(
        <div className = {`${styles.wrapper} ${styles.inverted}`}>
            처음 사용해보는
            <span className="something">CSS Module</span>
        </div>
    )

}
export default CSSModule;

4. classnames 라이브러리

1) 개요

> CSS 클래스를 조건부로 설정할 때 유용한 라이브러리로 여러 클래스를 설정할 때 편리

# 설치

☑︎ yarn : yarn add classnames
☑︎ npm : npm install classnames

# 설정 예시

☑︎ 2개 설정 : classNames('one', 'two')
☑︎ 3개 설정 : classNames('one', ['two', 'three'])
☑︎ 조건부 설정
> two 적용 : classNames('one', {'two':ture})
> two 적용 불가 : classNames('one', {'two':false})
:: true 나 false 위치를 변수로 설정하면 조건부 설정을 쉽게 구현하는 것이 가능함

2) CSSModule.jsx 에 적용

# 설치
$yarn add classnames

#CSSModule.jsx 수정
📁 src → 📄 CSSModule.jsx
import React from "react";
import styles from "./CSSModule.module.css";
import classNames from 'classnames/bind';

// cx 안에서는 styles 생략하는 것이 가능
const cx = classNames.bind(styles);

const CSSModule = () => {
    return(
        <div className = {cx('wrapper', 'inverted')}>
            처음 사용해보는
            <span className="something">CSS Module</span>
        </div>
    )

}
export default CSSModule;​

 

5. SASS

1) CSS Preprocessor(전처리기)

> CSS 가 동작하기 전에 사용하는 기능
> CSS의 불편함을 해결하기 위한 확장 기능
> 문법 자체는 CSS와 유사하지만 선택자의 중첩이나 조건문, 반복문, 다양한 단위의 연산 등이 가능함
> Sass, Less, Stylus 등이 있음

2) SASS

> Syntactically Awesome Style Sheets
> 중복되는 코드를 줄여 가독성 좋게 작성이 가능함
> 가이드 참고 : https://sass-guidelin.es/ko/
# SCSS
> CSS 구문과 호환되도록 새로운 구문을 도입해 만든 SASS의 기능을 지원하는 CSS 의 super set
> 사용하기 위해서는 패키지 설치 필요
   $yarn add node-scss scss-loader sass  
> 확장자는 scss를 사용
 

Sass Guidelines — Korean translation

분별 있고, 유지∙확장 가능한 Sass 작성을 위한 주관적인 스타일가이드.

sass-guidelin.es

3) CSSModule.module.css 파일을 수정해서 적용

📁 src → 📄 CSSModule.module.css
/* 파일 안에서만 사용하는 클래스 */
.wrapper{
    background: black;
    padding: 1em;
    color: white;
    font-size: 2rem;

    /* wrapper 와 inverted가 모두 있는 경우에만 적용 */
    & .inverted{
        color:black;
        background: white;
        border: 1px solid black;
    }
}

/* 모든 곳에서 사용할 수 있는 클래스 - 이름을 수정하지 않음 */
:global {
    .something {
        font-weight: 800;
        color: goldenrod;
    }
}

# CSSModule.jsx 파일 수정
📁 src → 📄 CSSModule.jsx

import styles from "./CSSModule.module.scss";​

4) scss 파일을 생성하고 적용

# App.scss 파일을 생성하고 작성
📁 src → (CREATE) 📄 App.scss
import CSSModule from './CSSModule';

import styles from './App.scss';
import classNames from 'classnames/bind';

const cx = classNames.bind(styles);

function App() {
  const isBlue = false;
  return(
    <div>
      <div className={cx('box',{blue:isBlue})}>
        <div className={cx('box-inside')}/>
      </div>
      <CSSModule/>
    </div>
  )
}
export default App;​

5) 변수와 믹스 인 사용

> $변수명:값; 의 형태로 변수를 만들어서 다른 곳에서 @import를 이용해서 사용이 가능
> 믹스 인은 여러 속성을 모아놓은 것
@mixin 이름(){
	속성:값
    ...
}

@incluse 이름();​
> 변수와 믹스 인은 자주 사용하는 속성이 있을 때 유용함

# src 디렉토리에 styles 디렉토리 생성
📁 src → (CREATE) 📁 stytles

# 공통된 스타일 속성을 정의한 util.scss 파일 생성
📁 src → 📁 styles → (CREATE) 📄 util.scss
$size:100px;

@mixin place-at-center(){
    top:50%;
    left:50%;
    transform:translate(-50%, -50%);
}

# App.scss 파일 상단에 코드를 추가하고 설정을 수정
📁 src → 📄 App.scss
:: @import './styles/util.scss';
:: width : $size
:: height : $size
:: @include place-at-center();
@import './styles/util.scss';

.box{
    display: inline-block;
    width: $size;
    height: $size;
    border: 1px solid black;
    position: fixed;

    @include place-at-center();

    &.blue{
        background: blue;
    }

    &:hover{
        background: yellow;
    }

    &:active{
        background: red;
    }

    .box-inside{
        background: black;
        width: 50px;
        height: 50px;
    }
}

> 애플리케이션을 개발하다 보면 컴포넌트 1개와 scss 파일 1개가 쌍으로 만들어지는 경우가 많음
> 여러 컴포넌트를 만들다 보면 공통적으로 사용하는 scss 라이브러리들이 있는데 이러한 라이브러리들을 util.scss 에서 import하면 다른 곳에서도 별도의 import 없이 사용하는 것이 가능함

6) SCSS 라이브러리

> SCSS가 이미 적용된 라이브러리
> 반응형 웹 디자인(디바이스의 크기에 상관없이 동일한 콘텐츠를 사용할 수 있도록 하는 것) : include- media
> 색상 : open-color(색상을 자주 사용하는 색상 이름과 숫자를 이용해서 강도를 설정하는 형태)
> icon : react-icons

※ 참고 자료
include-media : https://www.npmjs.com/package/include-media
open-color : https://yeun.github.io/open-color/
react-icon : https://react-icons.github.io/react-icons/
 

include-media

Simple, elegant and maintainable media queries in Sass. Latest version: 1.4.10, last published: 2 years ago. Start using include-media in your project by running `npm i include-media`. There are 120 other projects in the npm registry using include-media.

www.npmjs.com

 

Open Color

Color scheme for UI design

yeun.github.io

 

React Icons

React Icons Include popular icons in your React projects easily with react-icons, which utilizes ES6 imports that allows you to include only the icons that your project is using. Installation (for standard modern project) npm install react-icons --save Usa

react-icons.github.io

# 라이브러리 설치
$yarn add include-media open-color react-icons

#util.scss 파일 수정
📁 src → 📁 styles → 📄 util.scss
@import '~open-color/open-color';
@import '~include-media/dist/include-media';

$breakpoints:(
    small:376px,
    medium:768px,
    large:1024px,
    huge:1200px
);

$size:100px;

@mixin place-at-center(){
    top:50%;
    left:50%;
    transform:translate(-50%, -50%);
};​

# 컴포넌트를 만들 때 디렉토리 구조
> src 디렉토리 안에 components 라는 디렉토리를 생성
   components 디렉토리 안에서 컴포넌트 파일과 디자인 파일을 생성
   디렉토리 안에 컴포넌트 이름으로 디렉토리를 생성하고 컴포넌트 파일과 디자인 파일 그리고 index.js(컴포넌트를 내보내는 역할
   만 수행) 파일을 생성

# 컴포넌트를 저장할 components 디렉토리 생성
📁 src → (CREATE) 📁 components

# components 디렉토리에 Button.jsx 파일 생성
📁 src → 📁 components → (CREATE) 📄 Button.jsx
import React from "react";
import styles from './Button.scss';
import classNames from "classnames/bind";

const cx = classNames.bind(styles);

// props를 받아오는데 children으로 넘겨준 것은 children으로 받고 나머지는 rest로 받기
 const Button = ({children, ...rest}) => {
     return(
         <div className={cx('button')}{...rest}>
             {children}
         </div>
     )
 }

 export default Button;


# components 디렉토리에 Button.scss 파일을 생성하고 작성
📁 src → 📁 components → 📄 Button.scss
@import '../styles/util.scss';

.button{
    background: $oc-green-7;
    transition: all .2s ease-in;
    display: inline-block;
    padding-top: 2rem;
    padding-bottom : 2rem;
    text-align: center;
    color: white;
    position: fixed;
    font-size: 2rem;
    border-radius: 4px;

    cursor:pointer;
    @include place-at-center();

    width: 1200px;
    // 반응형 웹디자인
    @include media("<huge"){
        width:1024px;
    }
    @include media("<large"){
        width:768px;
    }
    @include media("<medium"){
        width:90%;
    }

    &:hover{
        background: $oc-green-7;
    }
    &:active{
        margin-top: 3px;
        background: $oc-green-8;
    }
}

 

# App.js 파일에 Button component

7) Material Design

#개요
> 웹 과 앱을 통틀어 모든 개발 플랫폼에서 사용자 경험을 하나로 묶기 위해서 (Progressive Web Design) 구글이 제시한 디자인 방식

#사용방법
> CDN 방식 - 스타일시트의 외부 링크 이용하는 것으로 네트워크를 사용할 수 없는 상태가 되면 적용되지 않음

💡 CDN(Content Delivery-Distrubution Network)
서버와 사용자 사이의 물리적인 거리를 줄여 콘텐츠 로딩에 소요되는 시간을 최소화하기 위해서 동일한 콘텐츠를 여러 네트워크에 분산 저장해서 요청을 하면 가장 가까운 Network에서 다운로드 하도록 해주는 서비스

> scss 파일이나 css 파일을 다운로드 받아서 적용 - 네트워크 사용 여부와 상관없이 사용할 수 있지만 앱의 크기가 커짐

# public 디렉토리 index.html 파일에 내용 수정
📁 public → 📄 index.html

:: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"/>
...
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"/>
<title>React App</title>
...

# App.js 파일 내용 작성
📁 src → 📄 App.js
import './App.css';

function App() {
  return(
    <div>
      <nav>
        <div className="nav-wrapper">
          <div>REACT</div>
        </div>
      </nav>
      <div>머터리얼 디자인</div>
    </div>
  )
}
export default App;​
> 실행하고 위의 div와 아래의 div를 확인

# 다운로드 받아서 직접넣고 사용
> https://materializecss.com 에서 css나 scss를 다운로드
> 압축파일을 해제하고 프로젝트에 복사 (sass 폴더를 src 하위로 옮김)
🗂Project → 📁 src → (COPY) 📁 sass
> sass 디렉토리의 materialize.scss를 import 해서 사용
 

Documentation - Materialize

 

materializecss.com

6. styled-components

1) 개요

> 자바스크립트 파일 안에 스타일을 선언하는 방식 - CSS in JS
> 컴포넌트와 디자인을 분리하지 않고 컴포넌트 와 디자인을 하나의 파일로 만들어서 사용하는 것으로 컴포넌트를 배치하면
   자동으로 디자인이 적용
   react-icons 의 각 icon을 컴포넌트를 삽입하는 형태로 추가하면 이미 설정된 style이 적용되서 출력이 됨
> 이러한 라이브러리의 종류는 https://github.com/MicheleBertoli/css-in-js 에서 확인 가능
 

GitHub - MicheleBertoli/css-in-js: React: CSS in JS techniques comparison

React: CSS in JS techniques comparison. Contribute to MicheleBertoli/css-in-js development by creating an account on GitHub.

github.com

2) styled-component 를 이용한 버튼 만들기

# 패키지 설치
$yarn add styled-components

# components 디렉토리에 StyledComponent.jsx 파일로 만들고 작성
🗂react_styling →  📁 components → (CREATE) 📁 StyledComponent.jsx
import React from "react";
import styled, {css} from 'styled-components';

const Box = styled.div`
    backgroud:${props => props.collor || 'blue'};
    padding:1rem;
    display:flex;
`;

const Button = styled.button`
    background:white;
    color:black;
    border-radius:4px;
    padding:0.5rem;
    display:flex;
    align-items:center;
    justify-content:center;
    box-sizing:border-box;
    font-size:1rem;
    font-weight:600;

    &:hover {
        background: rgba(255, 255, 255, 0.9);
    }

    & + button {
        margin-left: 1rem;
    }
`;

const styledComponent = () => {
    <Box color='black'>
        <Button>안녕하세요</Button>
        <Button>반갑습니다</Button>
    </Box>
};

export default styledComponent;​

#App.js에서 파일 출력

import StyledComponent from './components/StyledComponent';
import './App.css';

function App() {
  return(
    <div>
      <StyledComponent/>
    </div>
  )
}
export default App;

7. 서버에서 데이터 받아오기

https://jsonplaceholder.typicode.com/users

1) 서버에서 데이터 받아오는 방법

#1 : ajax 이용 - load 이벤트 와 error 이벤트를 처리
import React from "react";

function App() {
  return(
    <div>
      <button onClick={(e)=>{
        let request = new XMLHttpRequest();
        // GET 방식으로 요청
        request.open('GET', 'https://jsonplaceholder.typicode.com/users');
        // POST 방식일 때는 send에 파라미터를 대입
        request.send('');
        request.addEventListener('load', ()=>{
          let data= JSON.parse(request.responseText);
          // 데이터를 가져오는데 성공했을 때 처리
          console.log(data);
        });
        request.addEventListener('error', (error)=>{
          // 데이터를 가져오는데 실패했을 때 처리
          console.log(error);
        });
      }}> 다운로드 </button>
    </div>
  )
}
export default App;​


#2 : Fetch API : Promise를 이용하는 API

import React from "react";

function App() {
  return(
    <div>
      <button onClick={(e)=>{
        fetch('https://jsonplaceholder.typicode.com/users')
        .then((response) => response.json())
        .then((data) => console.log(data))
        .catch((error) => console.log(error.message))
      }}> 다운로드 </button>
    </div>
  )
}
export default App;


#3 : axios 라이브러리를 사용
> $yarn add axios

import React from "react";
import axios from 'axios';

function App() {
  return(
    <div>
      <button onClick={(e)=>{
        axios.get('https://jsonplaceholder.typicode.com/users')
        .then(response => console.log(response.data))
        .catch(error => console.log(error))
      }}> 다운로드 </button>
    </div>
  )
}
export default App;​

> #3의 경우 코드가 상당히 간단해져서 사용하기 편리하나 라이브러리로서 변경될 경우에 대비해야한다.
   따라서, 라이브러리를 사용하지 않는 #2의 Fetch API로 사용하는 것을 권장함

2) Node 서버에서의 CORS 설정

# cors 라이브러리 설치
$yarn add cors
$npm add cors

# 서버 실행 파일에 추가 (Node에서만 사용 가능)
const cors = require('cors');
app.use(cors());

3) 서버를 수정할 수 없을 때 proxy 설정

# package.json 파일에 설정을 추가
> "proxy" : "서버의 도메인"
> 요청을 할 때 /api/도메인 뒤의 url 로 요청하면 됨

# 라이브러리를 이용
> 라이브러리 설치
$yarn add http-proxy-middleware

> src 디렉토리의 setupProxy.js 파일을 만들고 작성
const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = (app) => {
	app.use(createProxyMiddleware('/클라이언트공통URL',{
    	target:'서버의URL',
        changeOrigin:true
    	})
	);
}

> http://localhost:9000/item/itemid에 요청

const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = (app) => {
	app.use(createProxyMiddleware('/api',{
    	target:'http://localhost:9000',
        changeOrigin:true
        })
	);
}