728x90
반응형

이번 포스트부터는 백엔드에 Node.js(Sequelize)를 사용해서 간단하게 CRUD 하는 것을 구현해보겠다.

 

먼저 Node.js 환경구성을 해준다.

 

https://typo.tistory.com/entry/%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B11-node-%EC%84%A4%EC%B9%98?category=901285 

 

환경 구성 - node 설치

1. 업데이트를 실행해준다. sudo apt-get update 2. 업그레이드 해준다. sudo apt-get upgrade 3. npm을 설치한다. sudo apt-get install npm 4. n을 설치한다. sudo npm install n -g 5. 설치한 n을 이용해 node..

typo.tistory.com

 

MySQL 설치

이제 MySQL을 설치해보자.

$ sudo apt install -y mysql-server

 

설치가 완료되면 MySQL 서버를 초기화한다.

$ sudo mysql_secure_installation

 

충분히 강한 패스워드를 생성할 수 있는 플러그인을 활성화 할건지 물어보는 내용으로, 엔터키로 넘어간다.

 

사용할 패스워드를 2번 입력한다.

 

익명의 사용자를 제거하는지 묻는 내용으로, y를 입력한다.

 

최고 관리자 권한으로 외부 로그인을 허용하지 않을 것인지 묻는 내용으로 y를 입력한다.

 

test 데이터베이스를 삭제할지 묻는 내용으로 y를 입력해서 삭제한다.

 

마지막으로 privileges table을 다시 로드할지 묻는 내용으로 y를 입력해서 다시 로드한다.

 

 

그리고 아래 명령어로 MySQL 데몬을 다시 실행한다.

$ sudo /etc/init.d/mysql restart

 

MySQL 설정

 

1. 아래 명령어를 입력해서 MySQL 서버에 접속하고 설정한 패스워드를 입력해서 로그인한다.

$ sudo mysql -u root -p

 

2. 데이터베이스 사용자를 생성한다. ( 사용자명 : teepo, 패스워드 : test1234 )

mysql> CREATE USER 'teepo'@'localhost' IDENTIFIED BY 'test1234';

 

3. 데이터베이스를 생성한다. ( db이름 : example )

mysql> create database example;

 

4. 방금 생성한 사용자에게 example DB에 대한 권한을 부여한다.

mysql> grant all privileges on example.* to 'teepo'@'localhost';

 

5. 마지막으로 flush privileges를 입력한다.

mysql> flush privileges;

 

6. 설정을 끝냈으면 MySQL 접속을 종료한다.

mysql> exit;

 

 

MySQL 외부 접속 허용

외부 접속을 허용하기 위해 먼저 외부에서 접속할 수 있는 계정을 생성해야 한다.

위에서 이미 생성한 사용자같은 경우는 localhost의 사용 가능 영역을 가지기 때문에 외부에서의 접근은

불가능하다. 때문에 특정 IP 또는 % 기호를 입력하여 외부 접속이 가능하게끔 해준다.

 

1. 외부 접속이 가능한 teepo2를 만들어보자.

mysql> CREATE USER 'teepo2'@'%' IDENTIFIED BY 'test1234';
mysql> grant all privileges on example.* to 'teepo2'@'%';
mysql> flush privileges;

 

2. 그 다음 /etc/mysql/mysql.conf.d/mysqld.cnf 파일의 [mysqld] 영역에서 bind-address 부분을 아래와 같이 주석 처리 한다.

 

3. 아래 명령어로 MYSQL 서버 데몬을 재실행한다.

$ sudo /etc/init.d/mysql restart
728x90
반응형
728x90
반응형

React에서 ES8 문법인 async/await를 사용해 비동기 함수를 작성하면 브라우저에서 다음과 같은 에러가 발생한다.

 

ReferenceError: regeneratorRuntime is not defined

 

 

babel 7.4.0부터는 core-js/stable(ECMAScript 기능들의 polyfill 제공)과 regenerator-runtime/runtime (transpiled generator functions 사용을 위해 필요) 직접 포함하면서 @babel/polyfill이 deprecated되었다.

 

때문에 아래와 같이 문제를 해결하겠다.

 

1. 먼저 관련된 babel들을 설치해주고,

$ npm install --save-dev @babel/plugin-transform-runtime
$ npm install --save @babel/runtime

 

2. babel 설정에 plugin을 등록해주자.

{
  "plugins": ["@babel/plugin-transform-runtime"]
}
728x90
반응형
728x90
반응형

캘린더를 다루는 모듈 중 react-day-picker라는 모듈이 있다. 

해당 날짜를 클릭했을 때의 이벤트를 주고 싶다면 한 번 도전해보자.

 

먼저 react 앱과 react-day-picker를 설치한다.

 

$ npx create-react-app teepo
$ npm install react-day-picker --save

 

$ npm i react-day-picker

 

그 다음 App.js 를 다음과 같이 수정하고 실행해본다.

 

App.js

import './App.css';
import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css';

function App() {
  return (
    <div className="App">
      <DayPicker />
    </div>
  );
}

export default App;

 

 

이렇게 달력이 잘 뜨는 것을 확인할 수 있다.

그럼 이번에는 onDayClick()을 통해 날짜를 선택할 때 하단에 해당 날짜가 출력되게끔 수정해보자.

 

App.js

import './App.css';
import { useState } from 'react'
import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css';

function App() {

  const [day,setDay] = useState();

  const handleDayClick = (day) => {
    setDay(day);
  }

  return (
    <div className="App">
      <DayPicker onDayClick={handleDayClick} />
      {day ? <p>{day.toString()}</p> : <p>날짜를 선택해주세요</p>}
    </div>
  );
}

export default App;

 

 

여기서 DayPicker에 selectedDays() 메소드를 추가하면 선택한 날짜에 표시를 할 수 있다.

 

App.js

import './App.css';
import { useState } from 'react'
import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css';

function App() {

  const [day,setDay] = useState();

  const handleDayClick = (day) => {
    setDay(day);
  }

  return (
    <div className="App">
      <DayPicker
        onDayClick={handleDayClick}
        selectedDays={day}/>
      {day ? <p>{day.toString()}</p> : <p>날짜를 선택해주세요</p>}
    </div>
  );
}

export default App;

 

 

HandleDayClick의 두번째 파라미터에 selected를 사용하면 이미 선택된 날짜를 취소할 수도 있다.

 

App.js

import './App.css';
import { useState } from 'react'
import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css';

function App() {

  const [day, setDay] = useState();
  const [selectDay, setSelectDay] = useState(false);

  const handleDayClick = (day, { selected }) => {
    if (selected) {
      setDay(undefined);
    }
    else {
      setDay(day)
    }
  }

  return (
    <div className="App">
      <DayPicker
        onDayClick={handleDayClick}
        selectedDays={day} />
      {day ? <p>{day.toString()}</p> : <p>날짜를 선택해주세요</p>}
      
    </div>
  );
}

export default App;

 

728x90
반응형
728x90
반응형

이번 포스트에서는 리액트에서 무한스크롤 이벤트를 구현해보겠다.

 

IntersectionObserver이란?
타겟엘리먼트와 타겟의 부모 혹은 상위엘리먼트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API

 

먼저 리액트 앱을 설치한다.

$ npx create-react-app [프로젝트 명]

 

 

1. App.js 파일을 수정해서 Item을 여러개와 Item 요소들이 들어갈 Itemlist를 만들어준다.

App.js

import "./App.css";
import styled from "styled-components";
import { useState } from "react";

const ItemWrap = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  align-items: center;

  .Item {
    width: 350px;
    height: 300px;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    margin: 1rem;
    box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
    border-radius: 6px;
  }
`;

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>{index+1}</div>
        ))}
      </ItemWrap>
    </div>
  );
}

export default App;

 

이런 화면이 될 것이다.

 

 

2. 데이터를 받아오는 중에 로딩컴포넌트를 보여주기위해 다음을 설치하고 코드에 추가해준다.

 

또한 target State와 observer의 관찰 대상이 될 target Element를 최하단에 생성해준다.

 

$ npm i react-loading

 

App.js

import ReactLoading from "react-loading";

...

const LoaderWrap = styled.div`
  width: 100%;
  height: 80%;
  display: flex;
  justify-content: center;
  text-align: center;
  align-items: center;
`;

...

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}
export default App;

 

3. 이제 Intersection Observer를 사용하기위해 useEffect를 선언하고 Intersection Oberver의 인자로 쓰일 함수를 선언하며, option을 지정해준다.

 

App.js

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}

 

useEffect 함수 안에 보면 위에 선언한 callback함수와 option을 deps로 넣어준 것을 볼 수 있는데,

조금 더 자세히 알아보면

 


※ Intersection Oberver 첫 번째 deps : callback()

- callback : 타겟 엘리먼트가 교차되었을 때 실행할 함수

 

※ Intersection Oberver 두 번째 deps : root, rootMargin, threshold

 - root 

  • default : null, 브라우저의 viewport
  • 교차영역의 기준이 될 root 엘리먼트. observer의 대상으로 등록할 엘리먼트는 반드시 root의 하위 엘리먼트여야 한다. 

 - rootMargin

  • default : '0px 0px 0px 0px'
  • root 엘리먼트의 margin. 

 - threshold

  • default : 0
  • 0.0부터 1.0 사이의 숫자 혹은 이 숫자들로 이루어진 배열로, 타겟 엘리먼트에 대한 교차 영역 비율을 의미한다. 
  • 0.0의 경우 타겟 엘리먼트가 교차영역에 진입했을 시점이고, 1.0의 경우 타겟 엘리먼트 전체가 교차영역에 들어왔을 때의 observer를 실행하는 것을 의미한다.


 

다시 한 번 코드를 살펴보면

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

target 엘리먼트로 지정한 target State가 첫 렌더링 때 생성될 것이고, 첫 렌더링 때와 이 target의 변경이 감지될 때 useEffect가 실행된다. callback 함수로는 위에 선언한 onIntersect 함수이고, option으로 threshold : 0.4를 지정했다.

 

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

useEffect가 실행되고 callback함수인 onIntersection이 실행된다. target 엘리먼트가 교차지점에 들어오고

isLodingfalse일 때 target 지정을 철회하고 데이터를 가져올 때 동안 isLoding Statetrue로 만들어준다.

 

데이터가 정상적으로 들어오면 isLoding State를 다시 false로 바꿔주고, targetElement를 재지정한다.

데이터를 임의로 가져왔다고 예상하고 itemList에 추가하는 로직을 구현해보자.

 

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      await new Promise((resolve) => setTimeout(resolve, 2000));
      let Items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      setItemList((itemLists) => itemLists.concat(Items));
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

 

DB를 조회할 땐 그 만큼의 시간이 걸릴테지만 지금은 로딩컴포넌트를 보여주기위해 Timeout을 2초 설정했다.

  1. target Element가 첫 렌더링 될 때와 설정한 교차지점에 진입·들어왔을 때 observer 객체가 새로 생성된다.
  2. observer 객체가 callback함수option을 가진 채로 생성된다.
  3. observer 객체가 새로 생성될 때 callback 함수가 실행되어 데이터를 가져오거나 하는 로직이 실행된다.
  4. Itemlist가 수정되어 더 많아지게되고, target Element가 재지정되어 다시 최 하단으로 설정된다.

 

전체코드

App.js

import "./App.css";
import styled from "styled-components";
import { useState } from "react";
import { useEffect } from "react";
import ReactLoading from "react-loading";
import axios from "axios";

const LoaderWrap = styled.div`
  width: 100%;
  height: 80%;
  display: flex;
  justify-content: center;
  text-align: center;
  align-items: center;
`;

const ItemWrap = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  align-items: center;

  .Item {
    width: 350px;
    height: 300px;
    display: flex;
    flex-direction: column;
    background-color: #ffffff;
    margin: 1rem;
    box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
    border-radius: 6px;
  }
`;

function App() {
  const [itemList, setItemList] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // ItemList
  const [target, setTarget] = useState(""); // target
  const [isLoding, setIsLoding] = useState(false); // isloding

  const onIntersect = async ([entry], observer) => {
    if (entry.isIntersecting && !isLoding) {
      observer.unobserve(entry.target);
      setIsLoding(true);
      // 데이터를 가져오는 부분
      await new Promise((resolve) => setTimeout(resolve, 2000));
      let Items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      setItemList((itemLists) => itemLists.concat(Items));
      setIsLoding(false);
      observer.observe(entry.target);
    }
  };

  useEffect(() => {
    let observer;
    if (target) {
      // callback 함수, option
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.4,
      });
      observer.observe(target); // 타겟 엘리먼트 지정
    }
    return () => observer && observer.disconnect();
  }, [target]);

  return (
    <div className="App">
      <ItemWrap>
        {itemList.map((item, index) => (
          <div className="Item" key={index}>
            {index + 1}
          </div>
        ))}
      </ItemWrap>
      {isLoding ? (
        <LoaderWrap>
          <ReactLoading type="spin" color="#A593E0" />
        </LoaderWrap>
      ) : (
        ""
      )}
      <div ref={setTarget}></div>
    </div>
  );
}

export default App;

 

실행 화면

 

 


Next.js typescript 에서는 타겟 엘리먼트를 다음과 같이 선언해주자.

<div ref={(e: any) => { setTarget(e) }}></div>

 

728x90
반응형
728x90
반응형

프로젝트를 진행하다보면 웹과 모바일 웹을 어떻게 구분지을지 고민하게 될 때가 있다.

 

접속하는 해상도에 따라 컴포넌트 자체가 달라져야 할 상황에는 결국 모바일 전용웹을 구축하고 모바일 이외의 해상도(태블릿, 중소형 모니터) 접속웹을 반응형으로 구축해야 할텐데, 해상도에 따라 페이지 자체가 바뀌거나(path) 달라지는 컴포넌트들의 개수가 많아지면 웹을 나눠서 구축하는 것이 좋겠지만, 페이지를 그대로 사용하고 달라지는 컴포넌트들의 개수가 별로 없을 경우에는 접속하는 userAgent에 따라 해당 컴포넌트만 달라지게끔 하는 것이 좋을 수도 있다고 생각했다. 구글링을 한 결과 2가지의 방법을 찾아냈다.

 

1. react-device-detect

첫번째는 react-device-detect라는 npm 모듈을 이용하는 방법이다. 먼저 모듈을 설치하자.

$ npm install react-device-detect --save

 

달라져야 할 컴포넌트에 다음과 같이 import 한다.

import {
  BrowserView,
  MobileView,
  isBrowser,
  isDesktop,
  isMobile
} from "react-device-detect";

 

import 한 변수의 값에 따라 다른 컴포넌트를 return 할 수 있다. ( 모바일일 경우 )

import {isMobile} from 'react-device-detect';

renderContent = () => {
    if (isMobile) {
        return <div> This content is unavailable on mobile</div>
    }
    return <div> ...content </div>
}

render() {
    return this.renderContent();
}

 

특정 브라우저가 아닐 경우 다음과 같이 지원하지 않는다는 메세지를 줄 수도 있다.

import {isIE} from 'react-device-detect';

render() {
    if (isIE) return <div> IE is not supported. Download Chrome/Opera/Firefox </div>
    return (
        <div>...content</div>
    )
}

 

특정 브라우저의 조건에 따라 특정 컴포넌트를 사용할 수도 있다.

import { browserName, CustomView } from 'react-device-detect';

render() {
    return (
        <CustomView condition={browserName === "Chrome"}>
            <div>...content</div>
        </CustomView>
    )
}

 

- 그 밖의 props들 ( 포스트 하단 링크 참조 )

 

2. SSR(Server-Side-Rendering)

두번째 방법은 NextJS에서 사용할 수 있는 SSR을 이용한 방법이다. 컴포넌트가 호출될 때 실행되는 _app.tsx

파일에 useEffect Hook으로 userAgent를 검색하고 페이지에서 SSR이 실행될 때 pageProps로 넘겨준다.

 

navigator에 관한 내용은 아래 링크를 참조하면 된다.

_app.tsx

 import App, { AppContext, AppInitialProps } from 'next/app'
import { AppProps } from "next/app";
import { NextPage } from "next";
//store
import wrapper from "../store";
//css
import "bootstrap/dist/css/bootstrap.min.css";
import { useEffect, useState } from "react";

function MyApp({ Component, pageProps } : AppProps) {
	return (
		<>
			<Component {...pageProps} />
		</>
	);
};
MyApp.getInitialProps = async (appContext : AppContext) => {
	// calls page's `getInitialProps` and fills `appProps.pageProps`
	const appProps = await App.getInitialProps(appContext);
  
	//userAgent
	const userAgent = await appContext.ctx.req ? appContext.ctx.req?.headers['user-agent'] : navigator.userAgent
  
	//Mobile
	const mobile = await userAgent?.indexOf('Mobi')
  
	//Mobile in pageProps
	appProps.pageProps.isMobile = await (mobile !== -1) ? true : false;
  
	return { ...appProps }
  }
  
  export default wrapper.withRedux(MyApp);

 

SSR에도 사용이 가능하다.

 

 

 

 

 

 

 

 

 


참조 - react-device-detect

https://bestofreactjs.com/repo/duskload-react-device-detect-react-utilites

 

Detect device, and render view according to detected device type. | BestofReactjs

duskload/react-device-detect, react-device-detect Detect device, and render view according to the detected device type. Installation To install, you can use npm or yarn: npm instal

bestofreactjs.com

 

참조 - SSR userAgent

https://www.howdy-mj.me/next/how-to-detect-device/

 

Next.js에서 userAgent 정보 가져오기

Next.js에서 라이브러리 설치 없이 유저가 사용하는 기기를 탐지하는 방법에 대해 알아보자. 해당 글은 next , react , typescript 버전으로 작성되었다. userAgent 정보 가져오기 만들어진 Next 프로젝트의

www.howdy-mj.me

 

참조 - navigator

https://developer.mozilla.org/en-US/docs/Web/API/Navigator

 

Navigator - Web APIs | MDN

The Navigator interface represents the state and the identity of the user agent. It allows scripts to query it and to register themselves to carry on some activities.

developer.mozilla.org

 

728x90
반응형
728x90
반응형

리액트에서의 스타일 적용법은 다음 링크로 확인할 수 있다.

 

https://typo.tistory.com/entry/Reactjs-%EC%9E%85%EB%AC%B8-CSS-SASS-SCSS?category=891266 

 

React.js | 입문 | CSS, SASS, SCSS

ReactJS에 CSS를 추가하는 방법은 크게 세가지가 있다. 먼저 css, sass, scss의 차이점 부터 알아보자. HTML 1 2 3 이러한 코드가 주어졌을 때 CSS .list { width: 100px; float: left; } li { color: red; backgr..

typo.tistory.com

 

 

NextJS CSS는 다음 링크로 자세히 볼 수 있다.

 

https://nextjs.org/docs/basic-features/built-in-css-support#adding-a-global-stylesheet

 

Basic Features: Built-in CSS Support | Next.js

Next.js supports including CSS files as Global CSS or CSS Modules, using `styled-jsx` for CSS-in-JS, or any other CSS-in-JS solution! Learn more here.

nextjs.org


 

Global CSS

bootstrap 같은 전역 css는 pages/_app.js 에 추가해서 사용할 수 있다.

pages/_app.js

// pages/_app.js
import 'bootstrap/dist/css/bootstrap.css'

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

 

 

Component CSS

특정 Component에서 쓰이는 css는 컴포넌트와 이름이 같은 css 파일을 만들어서 import 할 수 있다.

 

예를들어 Components/Button 폴더 안에 index.js, Button.module.css 파일이 있다고 가정할 때 아래와 같이 사용할 수 있다.

 

Components/Button/Button.module.css

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
.error {
  color: white;
  background-color: red;
}

 

Components/Button/index.js

import styles from './Button.module.css'

export function Button() {
  return (
    <button
      type="button"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </button>
  )
}

 

css 파일은 어느 곳에서나 가져올 수 있습니다.

 

SASS in NextJS

SASS를 이용해 scss 파일을 사용하고자 할 때는 먼저 사용할 모듈을 설치하자.

$ npm install sass

 

그다음 next.config.js 파일을 수정해야 한다.

next.config.js

const path = require('path')

module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
}

 

 

React에서와 같이 변수를 사용할 수도 있으며 export로 변수를 밖으로 내보낼 수도 있다.

variables.module.scss

/* variables.module.scss */
$primary-color: #64FF00

:export {
  primaryColor: $primary-color
}

 

pages/_app.js

// pages/_app.js
import variables from '../styles/variables.module.css'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout color={variables.primaryColor}>
      <Component {...pageProps} />
    </Layout>
  )
}

 

 

참조

https://medium.com/sebride/next-js-with-module-sass-a8fe3976147

 

Next.js with module sass

제가 가장 선호하는 css는 module sass 방식입니다. 고전적인 css 방식에 익숙하신 분들은 어색한 개념일 수 있는데요. 차근차근 설명해보도록 하겠습니다.

medium.com

 

728x90
반응형

'Front-End > Next.js' 카테고리의 다른 글

Next.js | Json in Json 상태 관리  (0) 2022.01.04
Next.js | 기본기능 | userAgent 정보 확인하기  (0) 2021.11.03
Next.js | next-redux-wrapper  (0) 2021.10.15
Next.js | 기본기능 | 환경 변수  (0) 2021.10.13
Next.js | API 경로  (0) 2021.10.13
728x90
반응형

ReactJS에 CSS를 추가하는 방법은 크게 세가지가 있다. 먼저 css, sass, scss의 차이점 부터 알아보자.

 

HTML

<ul class='list'>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</div>

 

이러한 코드가 주어졌을 때 

CSS

.list {
  width: 100px;
  float: left;
  }
li {
  color: red;
  background: url("./image.jpg");
  }
li:last-child {
  margin-right: -10px;
  }

SCSS

.list {
  width: 100px;
  float: left;
  li {
    color: red;
    background: url("./image.jpg");
    &:last-child {
      margin-right: -10px;
    }
  }
}

SASS ( 중괄호, 세미콜론 사라짐 )

.list
  width: 100px
  float: left
  li
    color: red
    background: url("./image.jpg")
    &:last-child
      margin-right: -10px

 

CSS는 작업이 크고 고도화 될수록 코드가 길어지고 연산에 어려움이 생긴다.

 

SASS가 CSS의 태생적 한계를 보완하기 위해 아래와 같은 추가기능과 유용한 도구를 제공하면서 등장하였고

  • 변수의 사용
  • 조건문과 반복문
  • Import
  • Nesting
  • Mixin
  • Extend/Inheritance

더 넓은 범용성과 CSS의 호환성 등의 이유로 SCSS가 등장하였다.


 

이번 포스트에서는 리액트에서의 CSS 다루는 법을 다루고자 한다.

 

1. 인라인 스타일 적용법

말 그대로 태그가 선언된 그 자리에서 바로 CSS를 적용하는 방법이다. JSON(Key,Value) 형식으로 되어있는데, 

이 방법은 수정이 필요할 때 페이지나 컴포넌트를 일일이 다 조회해야되기 때문에 그닥 추천하는 방법은 아니다. 

const ReactComponent = props => {
	return (
    	<div
        	style={{
            	color: "blue",
                frontWeigt: "bold"
                }}
        >
        abcd
        </div>
    }
};

 

 

2. SCSS 파일 스타일 적용법 (기존 HTML CSS와 비슷)

 

또한 className을 사용하기위해 다음 모듈을 사용해준다.

$ npm i classnames

 

scss 파일을 사용하면 다음과 같이 변수를 지정할 수도 있다.

colors.css

 

또한 위에 만든 파일을 import 할 수도 있다.

customDiv.scss

 

그다음 사용할 곳에서 import 해주고 태그에 적용시켜주면 끝이다. scss파일에서 정의한 이름과 className을 동일하게 작성하면 된다.

App.js

 

 

 

3. 스타일 컴포넌트 적용법

먼저 styled-components 모듈을 설치한다.

$ npm i styled-components

 

타입스크립트는 아래 타입까지 추가한다.

$ npm i @types/styled-components

 

 

그 다음 컴포넌트 상단에 변수로 선언을 하고 return할 때 사용한다.

컴포넌트에 들어오는 Props의 값에 따라 유동적으로 변화를 줄 수도 있다.

 

 

컴포넌트 상단에 변수를 선언해주고 return할 때 쓰는 방법이다.

 

728x90
반응형
728x90
반응형

Nextjs 화면을 반응형으로 만들기 위해 우리는 bootstrap5를 이용할 것이다.

 

리액트에서의 bootstrap5는 아래 링크를 확인하면 된다.

https://designmodo.com/bootstrap-react-sass/#bootstrap-5-with-react-js

 

Getting Started with Bootstrap 5, React, and Sass

Get React JS and Bootstrap 5 install and integrate together using npm. Setup Sass for different screen resolutions of your app.

designmodo.com


SCSS에 관한 내용은 아래 링크를 참조하자.

https://typo.tistory.com/entry/Nextjs-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-Built-In-CSS-SupportSCSS?category=895504 

 

Next.js | 기본기능 | Built-In CSS Support(SCSS)

리액트에서의 스타일 적용법은 다음 링크로 확인할 수 있다. https://typo.tistory.com/entry/Reactjs-%EC%9E%85%EB%AC%B8-CSS-SASS-SCSS?category=891266 React.js | 입문 | CSS, SASS, SCSS ReactJS에 CSS를 추..

typo.tistory.com


 

먼저 bootstrap을 설치한다.

$ npm i bootstrap@5.0.0-alpha3

 

_app.tsx 파일에 추가해준다.

_app.tsx

import { AppProps } from "next/app";
import { NextPage } from "next";
//store
import wrapper from "../store";
//css
import "bootstrap/dist/css/bootstrap.min.css";

const MyApp: NextPage<AppProps> = ({ Component, pageProps }: AppProps) => {
	return (
		<>
			<Component {...pageProps} />
		</>
	);
};

export default wrapper.withRedux(MyApp);

 

 

 

기존에 만들어 두었던 dashboard 페이지를 다시 확인하자.

 

src/dashboard/index.tsx

import axios from 'axios'
import type { GetServerSideProps, NextPage } from 'next'
import Link from 'next/link'
import cookies from 'next-cookies'
import jwtDecode, { JwtPayload } from 'jwt-decode'


interface Props {
    data : any
}


const Dashboard:NextPage<Props> = ({data}) => {
    const ClickHandler = () => {

    }
    return (
        <div>
            <div className='dashboard'>
                Dashboard
            </div>
        </div>
        )
}


export default Dashboard;

export const getServerSideProps:GetServerSideProps = async (context) => { // SSR
    try {
        const token = cookies(context).token;
        const decodedToken: JwtPayload = jwtDecode<JwtPayload>(token ? token : '');
        
        if(decodedToken) {
            return {
                props: {
                }
            }
        }
        else {
            return {
                redirect: {
                destination: '/',
                permanent: false,
                },
            }
        }
    }
    catch(e) {
        console.log(e)
        return {
            redirect: {
            destination: '/',
            permanent: false,
            },
        }
    }
}

 

버튼을 눌렀을 경우 position이 "absolute"인 NavBar를 만들자.

 

dashboard/index.tsx

const Dashboard:NextPage<Props> = ({data}) => {
    const [sidebarOpen,setSidebarOpen] = useState(false);
    const SidebarOpen = () => {
        if(sidebarOpen)
            setSidebarOpen(false);
        else
            setSidebarOpen(true);
    }
    return (
        <div>
            {/* header */}
            <div className={"w-100 d-flex align-items-center mx-auto border border-bottom-1"}
                style={{
                    height: "50px"
                }}>
                <div style={{width: "5%"}}>
                    <button
                        style={{color:"blue"}}
                        onClick={SidebarOpen}>
                    o
                    </button>
                </div>
                <div 
                    style={{ width: "75%"}}>
                    로고
                </div>
                <div style={{width: "20%"}}>
                    프로필
                </div>
            </div>
            {/* nav */}
            {sidebarOpen ? 
                <div
                    style={{
                    backgroundColor: "lightblue",
                    position: "absolute",
                    width: "200px",
                    height: "100%",
                    opacity: 1
                    }}>
                        we
                </div>
                : ""
            }
            {/* body */}
            <div className='dashboard'>
                Dashboard
            </div>
            {/* footer */}
        </div>
        )
}

 

화면을 확인해보자.

 

 

버튼을 누르면 Navbar가 뜨고, 다시 누르면 사라진다. 너무 딱딱하니까 효과를 좀 넣어주자.

SCSS와 styled-components를 사용하기 위해 다음 모듈을 설치해준다.

 

$ npm install sass styled-components @types/styled-components

 

next.config.js 파일에 path를 import 하고 sassOptions를 설정해준다.

 

next.config.js

/** @type {import('next').NextConfig} */
const path = require('path');

module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles'), path.join(__dirname, 'src')],
  },
  reactStrictMode: true,
  async rewrites() {
    console.log(process.env.NODE_ENV)
    if (process.env.NODE_ENV !== 'production') {
      return [
        {
          source: process.env.SOURCE_PATH,
          destination: process.env.DESTINATION_URL,
        },
      ];
    }
    else {
      return [
        {
          source: process.env.SOURCE_PATH,
          destination: process.env.DESTINATION_URL,
        },
      ];
    }
  },
}

 

dashboard 페이지에 SCSS와 styled-components를 활용해준다.

 

dashboard/index.tsx

import axios from 'axios'
import type { GetServerSideProps, NextPage } from 'next'
import Link from 'next/link'
import cookies from 'next-cookies'
import jwtDecode, { JwtPayload } from 'jwt-decode'
import { useState } from 'react'
import styled, {keyframes} from 'styled-components'


interface Props {
    data : any
}

// 투명도 설정
const fadein = keyframes`
 from {
     opacity : 0
 }
 to {
     opacity: 1
 }
`

// NavDiv에 효과 설정
const NavDiv = styled.div`
  animation-duration: 0.25s;
  animation-timing-function: ease-out;
  animation-name: ${fadein};
  animation-fill-mode: forwards
`


const Dashboard:NextPage<Props> = ({data}) => {
    const [sidebarOpen,setSidebarOpen] = useState(false);
    const SidebarOpen = () => {
        if(sidebarOpen)
            setSidebarOpen(false);
        else
            setSidebarOpen(true);
    }
    return (
        <div>
            {/* header */}
            <div className={"w-100 d-flex align-items-center mx-auto border border-bottom-1"}
                style={{
                    height: "50px"
                }}>
                <div style={{width: "5%"}}>
                    <button
                        style={{color:"blue"}}
                        onClick={SidebarOpen}>
                    o
                    </button>
                </div>
                <div 
                    style={{ width: "75%"}}>
                    로고
                </div>
                <div style={{width: "20%"}}>
                    프로필
                </div>
            </div>
            {/* nav */}
            {sidebarOpen ? 
                <NavDiv
                    style={{
                    backgroundColor: "lightblue",
                    position: "absolute",
                    width: "200px",
                    height: "100%",
                    opacity: 1
                    }}>
                        we
                </NavDiv>
                : ""
            }
            {/* body */}
            <div className='dashboard'>
                Dashboard
            </div>
            {/* footer */}
        </div>
        )
}


export default Dashboard;

export const getServerSideProps:GetServerSideProps = async (context) => { // SSR
    try {
        const token = cookies(context).token;
        const decodedToken: JwtPayload = jwtDecode<JwtPayload>(token ? token : '');
        
        if(decodedToken) {
            return {
                props: {
                }
            }
        }
        else {
            return {
                redirect: {
                destination: '/',
                permanent: false,
                },
            }
        }
    }
    catch(e) {
        console.log(e)
        return {
            redirect: {
            destination: '/',
            permanent: false,
            },
        }
    }
}

 

inline-css가 좋지는 못하다고 하나 나중에 한꺼번에 정리하려고 일단 기능이 되는대로 넣어놨다.

 

다시 버튼을 눌러보면 효과가 적용되어 나타나는 것을 확인할 수 있다.

 

 

728x90
반응형
728x90
반응형

윈도우 하위시스템에서 우분투 컨테이너를 사용할 때 기존 mongo 사용법과 달리

sudo service mongod start 명령어가 안먹힐 때가 있다.

 

그럴 땐 먼저 아래 링크로 들어가 복사를 한다.

https://github.com/mongodb/mongo/blob/master/debian/init.d

 

GitHub - mongodb/mongo: The MongoDB Database

The MongoDB Database. Contribute to mongodb/mongo development by creating an account on GitHub.

github.com

 

데비안 계열의 우분투에서 쓰는 init script 내용을 아래 명령어로 들어가 붙여넣기한다.

$ sudo vi /etc/init.d/mongod

 

그 다음 권한을 설정해준다.

$ chmod 755 mongod

 

이제는 명령어가 잘 먹히는 것을 확인할 수 있다.

 $ sudo service mongod start
728x90
반응형
728x90
반응형

swagger에 관한 자세한 내용은 아래 링크에서 확인 가능합니다.

https://docs.nestjs.com/openapi/introduction#bootstrap

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com


 

먼저 Swagger를 설치해주자.

$ npm install --save @nestjs/swagger swagger-ui-express

 

 

main.ts 파일에 swagger를 추가해준다.

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser());

  const config = new DocumentBuilder()
  .setTitle('User example')
  .setDescription('The user API description')
  .setVersion('1.0')
  .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('swagger', app, document);

  
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )
    await app.listen(3001);
  }
bootstrap();

 

그 다음 UserController와 EmailController를 바꿔준다.

 

user.controller.ts

import { Controller, Request, Response, Get, Post, Body, Patch, Param, Delete, Res, Req, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

//auth
import { LocalAuthGuard } from '../auth/local-auth.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { AuthService } from '../auth/auth.service';

//swagger
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('user')
@ApiTags('유저 API')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private authService: AuthService
    ) {}

  @Get(':id')
  @ApiOperation({summary : '유저 확인 API', description: '유저를 찾는다.' })
  @ApiCreatedResponse({description: '유저를 찾는다'})
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  @Post('signup')
  @ApiOperation({summary : '회원가입 API', description: '회원가입' })
  @ApiCreatedResponse({description: '회원가입을 한다'})
  async signUp(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return this.userService.signUp(req.body.userInfo);
  }

  @UseGuards(LocalAuthGuard)
  @Post('login')
  @ApiOperation({summary : '로그인 API', description: '로그인' })
  @ApiCreatedResponse({description: '로그인을 한다'})
  async login(@Request() req, @Res({ passthrough: true }) res : any) {
    if(req.user.result === "success") { // ID,PW 둘다 확인 됐을 경우
      return this.authService.login(req.user.user, res);
    }
    else {
      return {result : req.user.result}; // 둘 중 하나 이상이 틀릴 경우
    }
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile')
  @ApiOperation({summary : '토큰확인 API', description: '토큰확인' })
  @ApiCreatedResponse({description: '토큰확인을 한다'})
  getProfile(@Request() req) {
    return req.user;
  }
}

 

email.controller.ts

import { Controller, Get, Post, Body, Patch, Param, Delete, Req, Res } from '@nestjs/common';
import { EmailService } from './email.service';
import { CreateEmailDto } from './dto/create-email.dto';
import { UpdateEmailDto } from './dto/update-email.dto';

//swagger
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('email')
@ApiTags('이메일 API')
export class EmailController {
  constructor(private readonly emailService: EmailService) {}

  @Get(':id')
  @ApiOperation({summary : '이메일 확인 API', description: '이메일로 유저를 찾는다.' })
  @ApiCreatedResponse({description: '이메일로 유저를 찾는다'})
  findOne(@Param('id') id: string) {
    return this.emailService.findOne(id);
  }

  @Post('send')
  @ApiOperation({summary : '이메일 전송 API', description: '이메일을 전송한다.' })
  @ApiCreatedResponse({description: '이메일을 전송한다.'})
  async emailsend(@Res({ passthrough: true }) res : any, @Req() req : any) {
    return await this.emailService.emailSend(req.body.email,res);
  }

  @Post('cert')
  @ApiOperation({summary : '이메일 인증 API', description: '인증번호를 확인한다.' })
  @ApiCreatedResponse({description: '인증번호를 확인한다.'})
  async emailCert(@Res({ passthrough: true}) res : any, @Req() req : any) {
    return await this.emailService.emailCert(req);
  }
}

 

swagger 화면(localhost:3001/swagger)으로 접속해본다.

 

 

정상적으로 뜨는 것을 확인했다.

728x90
반응형

+ Recent posts