Archive

React에서 Typescript 사용하기 - 2

|

React에서 Typescript 사용하기 - 2


1. event

onClick, onChange 등 타입스크립트의 타입 추론은 inline 방식일 때에만 적용이 된다

만약 jsx 밖에서 함수를 정의하고 매개변수 event 객체를 받는다고 한다면 에러를 발생시킨다

const EventComponent: React.FC = () => {
  /*
  const onChange = (e) => { // e 부분에서 에러가 발생
      console.log(e);
  };
  */
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    //...  
  };
    
  return (
  	<div>
    	<input onChange={onChange} />  
    </div>
  );
};

저 긴걸 어떻게 아느냐고? input의 onChange 부분에 마우스를 올려보면 다 나온다. 그대로 복붙

2. Class based component

import { Component } from 'react';

interface User {
  name: string;
  age: number;
}

interface UserSearchProps {
  users: User[]
}

interface UserSearchState {
  name: string;
  user: User | undefined;
}

class UserSearch extends Component<UserSearchProps> {
  state: UserSearchState = {
    name: '',
    user: undefined
  };

  handleClick = () => {
    const foundUser = this.props.users.find((user) => {
      return user.name === this.state.name;
    });

    this.setState({ user: foundUser });
  }

  reunder() {
    const { name, user } = this.state;
    return (
      <div>
        User Search
        <input value={name} onChange={(e) => this.setState({ name: e.target.value })}/>
        <button onClick={this.handleClick}>Find User</button>
        <div>
          {user?.name}
          {user?.age}
        </div>
      </div>
    );
  }
};

export default UserSearch;

3. ref

useRef로 엘리먼트 참조 시 초기값 null을 주지 않으면 에러가 발생한다. 따라서, 타입도 null 추가

import { useEffect, useRef } from 'react';

const SearchBar: React.FC = () => {
    const inputRef = useRef<HTMLInputElement | null>(null);
    
    useEffect(() => {
        inputRef.current?.focus();
    }, []);
    
    return <input ref={inputRef} />
}

참고 자료


udemy - React and Typescript: Build a Portfolio Project

React에서 Typescript 사용하기 - 1

|

React에서 Typescript 사용하기 - 1


1. tsx, ts

  • React 컴포넌트이다 ? => tsx 사용
  • reducer, redux 등 jsx가 아닌 파일이다? => ts 사용
  • React 컴포넌트인데 .ts 쓰면 에러남

2. Props

  • 부모 컴포넌트에서 자식 컴포넌트에 props 전달 시 자식이 받게 될 props interface를 정의해야한다
// Child.tsx
interface ChildProps {
    color: string;
    onClick: () => void;
}
export const Child = ({ color, onClick }: ChildProps) => {
    return <div onClick={onClick}>{color}</div>
}

// Parent.tsx
import { Child } from './Child';

const Parent = () => {
  return <Child color="red" onClick={() => console.log('hi')} />
};
  • React 컴포넌트는 옵션으로 propTypes, displayName, defaultProps, contextTypes를 prop으로 내려줄 수 있지만 타입스크립트는 이게 리액트에서 만들었다는걸 알 수 없다
  • React.FC<>를 명시함으로서 해당 함수가 리액트의 컴포넌트라는걸 알려줄 수 있다
  • 이로써 propTypes, displayName, defaultProps, contextTypes 등도 배정받을 수 있다
  • React.FC<프롭타입> 제네릭에 프롭타입을 적어서 props의 타입도 정의할 수 있다
export const ChildAsFC: React.FC<ChildProps> = ({ color }) => {
  return <div>{color}</div>
}
  • children 전달 시에도 React.FC는 자동적으로 전달을 해주는 반면, 일반 함수형 컴포넌트는 interface 등에 수동적으로 타입을 명시해야한다
export const ChildAsFC: React.FC<ChildProps> = ({ color, children }) => {
    return (
    	<div>
        	{color}
            {children}
        </div>
    );
}

// Parent.js
const Parent = () => {
  return <ChildAsFC color="red" onClick={() => console.log('gdgd')}>
    children입니다.
  </ChildAsFC>
};


3. State

useState 사용 시 useState<type>의 제네릭 부분에 타입을 명시할 수 있다.

단순 string, number 등은 타입스크립트가 추론할 수 있지만 array, object등은 따로 적어줄 필요가 있다

const Example: React.FC = () => {
    const [name, setName] = useState('');
    const [lists, setLists] = useState<string[]>([]);
    
    const handleClick = () => {
    setLists([...lists, name]);
	}
    //...
}

// const [lists, setLists] = useState([]);
// 타입을 안적으면 lists의 타입은 never[]가 되어 에러가 발생한다


object는 분해하여 각각 타입을 적는 것도 가능하다. fetch 등 데이터를 받아서 뿌리거나, 유저의 행동에 따라 UI에 추가되는 부분이라면 초기 렌더링 시 state가 undefined가 되는 경우도 있다. 이럴땐 유니온타입을 이용해 타입을 명시할 수 있다.

const User: React.FC = () => {
  const [name, setName] = useState('');
  const [user, setUser] = useState<{ name: string, age: number } | undefined>();
  
  //...
  return (
  	<div>{user?.name}</div>
  );
};

참고 자료


udemy - React and Typescript: Build a Portfolio Project

SCSS Pre-processor 사용법

|

SCSS Pre-processor 사용법


  1. SCSS Pre-processor

    • CSS의 확장 기능으로 전처리기라고 부른다
    • 중첩, 조건문, 반복문, 연산 등 CSS에서 불가능했던 기능들을 작성할 수 있다
    • 웹은 CSS만 인식할 수 있기 때문에 컴파일 단계가 필요하다
    • CSS 확장 언어에는 SCSS (Sass), Less, Stylus 등이 있다
    • SCSS는 Sass 3버전에서 등장했으며 Sass의 모든 기능을 지원하고 CSS 구문과 완전히 호화되는 CSS의 상위집합이다


  2. Compile

    • SassMeister : 일종의 play-ground이다 링크

    • node-sass : Node.js의 컴파일러인 LibSass에 바인딩한 패키지로 SCSS 컴파일이 가능하다

      // npm install node-sass
           
      // node-sass [옵션] <입력파일경로> [출력파일경로]
      //ex) node-sass --watch scss/main.scss dist/style.css
      
    • Gulp : 빌드 자동화 도구인 Gulp에서 gulp-sass 컴파일러로 node-sass를 사용하도록 설정할 수 있다

      // npm install --save-dev gulp gulp-sass
           
      // gulpfile.js
      const gulp = require('gulp')
      const sass = require('gulp-sass')
           
      // 일반 컴파일
      gulp.task('sass', function () {
        return gulp.src('./src/scss/*.scss')  // 입력 경로
          .pipe(sass().on('error', sass.logError))
          .pipe(gulp.dest('./dist/css'));  // 출력 경로
      });
           
      // 런타임 중 파일 감시
      gulp.task('sass:watch', function () {
        gulp.watch('./src/scss/*.scss', ['sass']);  // 입력 경로와 파일 변경 감지 시 실행할 Actions(Task Name)
      });
           
      // gulp sass 혹은 gulp sass:watch 명령어로 컴파일
      
    • Webpack : sass-loader 사용

      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
           
      module.exports = {
          //...
          module: {
              rules: [
                  {
                      test: /\.(sass|scss)$/,
                      use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
                  }
              ]
          }
      }
      
    • Parcel : 웹앱 번들러인 Parcel을 사용한다

      // npm install -g parcel-bundler
           
      // npm install --save-dev node-sass
           
      // html에서 링크 태그로 scss 파일 연결
           
      // parcel build xxx.html
           
      // 빌드된 파일은 dist/에서 확인, 별도 포트지정 안하면 localhost:1234에서 확인 가능
      
    • Live Sass Compiler : VS Code의 플러그인으로 컴파일을 자동으로 해준다

  3. Syntax

    • 주석 : // 컴파일되지 않는 주석, /* 컴파일되는 주석*/

    • 데이터 종류: Number, String, Color, Boolean, Null, List, Map Map은 Key: Value 형태로 () 안에 정의해야한다 ex) (foo: a, bar: b)

    • Nesting : 중첩 기능으로 상위 선택자의 반복을 피할 수 있다

      .foo {
          .bar {
              li {
                  color: red;
              }
          }
      }
      

      & (ampersand) 선택자로 상위(부모) 선택자를 참조할 수 있다

      .list {
          li {
              &:last-child {
                  margin-bottom: 10px;
              }
          }
      }
      

      엠퍼샌드를 클래스명에 사용할 수도 있다

      .foo {
          &-bar { color: blue; }
          &-baz { color: red; }
      }
           
      // 컴파일 후..
           
      .foo-bar { color: blue; }
      .foo-baz { color: red; }
      

      중첩에서 벗어나고 싶으면 @at-root 키워드를 사용한다

      특정 변수를 정의하면 변수를 정의한 스코프에서만 적용할 수 있기 때문에 이런 경우에 사용한다

      .foo {
          $w: 20px;
          li {
              width: $w;
          }
          @at-root .bar {
              width: $w;
          }
      }
           
      // 컴파일 후..
           
      .foo li {
          width: 30px;
      }
      .bar {
          width: 20px;
      }
      

      font-size, font-weight 등 동일한 네임스페이스 font-를 가진 속성에서도 중첩이 가능하다

      .foo {
          font: {
              size: 10px;
              weight: bold;
              family: sans-serif;
          }
          margin: {
              top: 10px;
              bottom: 20px;
          }
      }
      


    • Variable : 반복적으로 사용되는 값을 변수로 지정할 수 있다. $ 키워드 사용

      $w : 200px;
      $color-primary: red;
      $url-bg: "/public/assets/images/";
           
      .btn {
          width: $w;
          background: $color-primary url($url-bg + "bg.jpg");
      }
      

      변수는 블록 스코프 단위를 갖는다. 블록 안에서 정의된 변수는 다른 변수에서 접근 불가능

      .btn1 {
          $color: red;
          background: $color;
      }
           
      /*
      .btn2 {
          background: $color;
      } --------> error
      */
      

      변수에 변수를 할당하는 변수 재할당도 가능하다

      $red: red;
      $color-danger: $red;
           
      .box {
          color: $color-danger;
      }
      

      !global 키워드로 전역 변수로 설정할 수 있다. 블록 스코프 안에서 정의해도 !global 키워드를 붙이면 다른 블록에서도 접근이 가능하지만, 같은 이름이 있을 경우 덮어져 사용될 수 있다

      .btn1 {
          $color: red !global;
          background: $color;
      }
           
      .btn2 {
          background: $color;
      }
           
      .btn3 {
          $color: blue;
          background: $color;
      } // ------> blue로 덮어짐
      

      !default 키워드로 초기값 설정이 가능하다. 기존에 할당한 값이 있으면 그 값을 사용한다

      $color-primary: red;
           
      .box {
          $color-primary: blue !default;
          background: $color-primary;
      } // ------> red
      

      #{} 을 이용해서 scss 구문 안에 변수를 넣을 수 있다. 문자열병합템플릿 비슷한 기능

      $family: unquote("Sans");
      @import url("http://fonts.googleapis.com/css?family=#{$family}");
           
      // unquote는 sass 내장함수로 문자에서 따옴표를 제거함
      

      #{}(문자보간)을 이용하여 동적인 변수명 사용은 허용하지 않는다. 대신 map을 써야한다

      @use "sass:map";
           
      $theme-colors {
          "success": green;
          "info": yellow;
          "warning": red;
      }
           
      .alert {
          background-color: map.get($theme-colors, "warning");
          // $theme-color-#{warning} (X)
      }
      
    • import : @import 외부에서 가져온 Sass 파일은 모두 단일 css 출력 파일로 병합된다 import한 파일에 정의된 변수, Mixin 등을 사용할 수 있다 CSS의 import 규칙으로 컴파일되는 몇 가지 예외 사항이 있다

      • 파일 확장자가 .css 일 때

      • 파일 이름이 http://로 시작하는 경우

      • url()이 붙은 경우

      • 미디어쿼리가 있는 경우

        @import "hello.css";
        @import "http://hello.com/hello";
        @import url(hello);
        @import "hello" screen;
               
        // 여러 파일은 , 로 구분함
        @import "header", "footer";
        

      파일 분할(Partials) : main 안에 여러 scss 파일을 import 하여 컴파일하면 각각의 scss파일이 모두 css로 나눠서 컴파일된다. 프로젝트 규모가 클 경우, 유지보수가 쉽지 않다

      파일 생성 시 _를 붙이고 import 하면 main 하나만 css 파일로 컴파일 할 수 있다

      // _header.scss
      // _footer.scss
           
      // main.scss
      @import "header", "footer";
           
      // 컴파일은 main.css 하나의 파일만 생성된다
      

      webpack, parcel, gulp는 설정에 따라 빌드하지만, 되도록 _의 사용을 권하고 있다

    • calculation : Sass는 기본적인 연산 기능을 지원한다

      • + , -, *, /, % : 산술 연산자
      • 나누기 시 주의할 점은 오른쪽 숫자가 반드시 숫자여야함 ex) 10px / 5px (X)
      • 곱하기 시 주의할 점은 하나 이상의 값이 반드시 숫자여야함 ex) 10px * 5px (X)
      • ==, !=, <, >, <=, >= : 비교 연산자
      • and, or, not : Boolean 연산자

      일반적으로 px 절대 단위로 연산을 하지만, %, em, vw 등 상대적 단위 연산 시 calc() 사용

      / 연산자를 구분이 아닌 연산 기능으로 사용하려면 다음 조건을 충족해야한다

      • 값이 변수에 저장되거나 함수에 의해 반환되는 경우

      • 값이 ()로 묶여있는 경우

      • 값이 다른 산술 표현식의 일부로 사용되는 경우

        div {
            $x: 100px;
            width: $x / 2; // 변수에 저장된 값 사용
            height: (100px / 2); // 괄호로 묶어서 사용
            font-size: 10px + 12px / 3; // 다른 연산자와 같이 사용
        }
        

      문자 연산 시 + 사용, 첫번째 값이 따옴표가 있느냐 없느냐에 따라 결과도 같이 따라감

      div::after {
          content: "Hello" + World;
          flex-flow: row + "-reverse" + " " + wrap;
      }
           
      // ----------Compiled to
           
      div::after {
          content: "Hello World";
          flex-flow: row-reverse wrap;
      }
      

      색상도 연산이 가능하다

      div {
        color: #123456 + #345678;
        // R: 12 + 34 = 46
        // G: 34 + 56 = 8a
        // B: 56 + 78 = ce
        background: rgba(50, 100, 150, .5) + rgba(10, 20, 30, .5);
        // R: 50 + 10 = 60
        // G: 100 + 20 = 120
        // B: 150 + 30 = 180
        // A: Alpha channels must be equal
      }
           
      // --------------Compiled to
      div {
        color: #468ace;
        background: rgba(60, 120, 180, 0.5);
      }
      

      알파값은 연산되지 않고 서로 동일해야 다른 값이 연산이 된다

      알파값 연산을 위해서는 opacify(), transparentize() 함수를 사용한다

      $color: rgba(10, 20, 30, .5);
      div {
        color: opacify($color, .3);  // 30% 더 불투명하게
        background-color: transparentize($color, .2);  // 20% 더 투명하게
      }
      

      @if 문에서 논리 연산자가 쓰인다

      $width: 90px;
      div {
        @if not ($width > 100px) {
          height: 300px;
        }
      }
      
    • mixin / include : 스타일을 정의하여 재사용할 수 있다 @mixin 키워드로 선언하고 사용처에서 @include 키워드로 사용한다

      @mixin example {
          background: red;
      }
           
      .box {
          @include example
      }
      

      매개변수를 사용하여 동적으로 사용할 수도 있다

      @mixin color($color) {
          color: $color;
      }
           
      .btn1 {
          @include color(red);
      }
      .btn2 {
          @include color(blue);
      }
      

      매개변수는 default value 설정이 가능하다

      @mixin size($width: 100px, $height: 50px) {
          width: $width;
          height: $height;
      }
           
      .box1 {
          @include size; // 100px, 50px
      }
      .box2 {
          @include size($height: 100px); // 100px, 100px
      }
      

      입력할 인수의 개수가 불확실한 경우 가변인수를 사용할 수 있다

      가변인수는 매개변수 뒤에 ... 를 붙인다

      // 전달받을 값에 가변인수 사용
      @mixin bg($width, $height, $bg-values...) {
          width: $width;
          height: $height;
          background: $bg-values;
      }
           
      div {
          @include bg(
              100px,
              200px,
              url("/images/a.png") no-repeat 10px 20px,
              url("/images/b.png") no-repeat,
              url("/images/c.png")
          );
      }
      
      // 전달할 값으로 가변인수 사용
      @mixin font (
          $style: normal,
          $weight: normal,
          $size: 16px,
          $family: sans-serif
          ) {
              font: {
      			style: $style;
                  weight: $weight;
                  size: $size;
                  family: $family;
              }
      }
           
      div {
          // 순서와 개수에 맞게 매개변수 전달
          $font-values: italic, bold, 16px, sans-serif;
          @include font($font-values...);
      }
      span {
          // 필요한 값만 키워드 인수로 변수에 담아 전달
          $font-values: (style: italic, size: 22px);
          @include font($font-values...);
      }
      a {
          // 필요한 값만 키워드 인수로 전달
          @include font((weight: 900, family: monospace)...);
      }
      

      @content 키워드가 mixin에 있으면 원하는 스타일 블록을 전달할 수 있다

      기존 mixin이 가지고 있는 기능에 선택자나 속성 등을 추가할 수 있다

      @mixin icon($url) {
          $::after {
              content: $url;
              @content;
          }
      }
      .icon1 {
          // icon mixin의 기존 기능 사용
          @include icon("/images/icon.png");
      }
      .icon2 {
          // icon mixin에 스타일 블록을 추가하여 사용
          @include icon("/images/icon.png") {
              position: absolute;
          };
      }
      
    • extend : 확장 기능으로 다른 선택자의 모든 스타일을 가져올 수 있다

      .btn {
          padding: 10px;
          margin: 10px;
          background: blue;
      }
      .btn-danger {
          @extend .btn;
          background: red;
      }
      

      확장은 원치 않는 side-effect를 초래할 수 있으므로 mixin으로 대체하는 것이 좋다

    • function : 함수를 정의하여 연산된 @return 값을 이용할 수 있다

      $max-width: 980px;
           
      @function columns($number: 1, $columns: 12) {
          @return $max-width * ($number / $columns)
      }
           
      .box-group {
          .box1 {
              width: columns(3);
          }
      }
      

      sass 내장 함수와 겹치지 않도록 접두어 등을 붙여서 정의하는 것이 좋다.

    • if : @if @else 키워드로 조건문을 사용할 수 있다. 조건문의 ()는 생략이 가능하다 삼항연산자 스타일로도 사용할 수도 있다

      $width: 555px;
      div {
          width: if($width > 300px, $width, null);
      }
      
      $color: orange;
      div {
          @if $color == red {
              color: red;
          } @else if $color == orange {
              color: orange;
          } @else {
              color: green;
          }
      }
      

      조건에는 논리 연산자 and, or, not을 사용할 수 있다

      @function limitSize($size) {
          @if $size >= 0 and $size <= 200px {
              @return 200px;
          } @else {
              @return 800px;
          }
      }
      div {
          width: limitSize(180px);
      }
      
    • for / each / while : 반복문을 사용할 수 있다 @forthrough 방식과 to를 사용하는 방식이 있다. 종료조건이 해석되는 방식이 다름

      일반적으로 through의 사용을 권하고 있다

      @for $i from 1 through 3 {
          .through:nth-child(#{$i}) {
              width: 20px * $i
          }
      }
      // 1, 2, 3 세번 실행
           
      @for $i from 1 to 3 {
          .to:nth-child(#{$i}) {
              width: 20px * $i
          }
      }
      // 1, 2 두번 실행 (1부터 3 직전)
      

      @each는 List와 Map을 반복할 때 사용한다

      $zoo: (fox, rabbit, monkey, dog, cat);
           
      .zoo {
          @each $animal in $zoo {
              li.#{$animal} {
                  background: url("/images/#{$animal}.png");
              }
          }
          // index가 필요한경우, index() 내장메소드 사용
          @each $animal in $zoo {
              $i: index($zoo, $animal);
              li:nth-child(#{$i}) {
                  left: 50px * $i;
              }
          }
      }
      

      각 데이터의 length가 같은 경우 여러 List를 동시에 처리할 수도 있다

      $apple: (apple, korea);
      $orange: (orange: japan);
      $banana: (banana, china);
           
      @each $fruit, $country in $apple, $orange, $banana {
          .box-#($fruit) {
              background: url("/images/#{$country}.png");
          }
      }
      

      Map 데이터를 반복할 경우 하나의 데이터에 두 개의 변수(key,value)가 필요하다

      $fruits: (
      	apple: korea,
          orange: china,
          banana: japan
      );
      @each $fruit, $country in $fruits {
          .box-#{$fruit} {
              background: url("/images/#{$country}.png");
          }
      }
      

      @while은 조건이 true인 동안 실행되므로 무한루프에 빠지지 않도로 주의해야한다

      $i: 6;
      @while $i > 0 {
          .item-#{$i} {
              width: 2px * $i;
          }
          $i: $i - 2;
      }
      
  4. Built-in Functions



참고 자료


sass doc

Sass(SCSS) 완전 정복!

Sass Guidelines

Next.JS의 서버사이드 렌더링 (feat.redux-saga)

|

[Next.JS] 서버사이드 렌더링 + redux-saga


1. Next 서버사이드 렌더링

getInitialProps (deprecated 예정)

getStaticProps : 언제 접속하든 같은 데이터를 받을 때 사용한다

import { END } from "redux-saga";
//...

export const getStaticProps = wrapper.getStaticProps(async (ctx) => {
  ctx.store.dispatch(loadPostsRequest());
  ctx.store.dispatch(END);
  await ctx.store.sagaTask.toPromise();
});

//...


getStaticPaths : Dynamic Routing일 때, getStaticProps쓰면 오류남, getStaticPaths 사용해야한다

import { useRouter } from 'next/router';

const Post = () => {
    const router = useRouter();

    if(router.isFallback) {
        return <div>로딩중...</div>
    }
    //...
};

export async function getStaticPaths() {
    return {
        paths: [
            { params: {id: '1' }},

        ],
        fallback: true, // false면 paths에 없는 값으로 진입하면 에러남
    };
};

export const getStaticProps = wrapper.getStaticProps(async (ctx) => {
    //...
});

Dynamic Routing에서 id값을 path에 지정해서 미리 HTML로 만들어놓고 갖고 있는다. fallback이 true일 때,

path에 없는 id로 진입하면 router의 isFallback으로 로딩 처리를 해줄 수 있고 그 동안 getStaticProps로 없는 id에

대한 데이터를 가져올 수 있다.


getServerSideProps : 접속한 상황에 따라 다른 데이터를 받을 때 사용한다

import { END } from "redux-saga";
// ...

export const getServerSideProps = wrapper.getServerSideProps(async (ctx) => {
  ctx.store.dispatch(loginRequestAction());
  //...
  ctx.store.dispatch(END);
  await ctx.store.sagaTask.toPromise();
});

//...

wrapper는 next-redux-wrapper 패키지의 createWrapper() 메소드로 만든 객체로, store가 들어있다.

getServerSideProps의 콜백의 파라미터인 ctx 안에는 store가 들어있어서 접근할 수 있다.

화면이 렌더링 되기 전에 위 로직이 먼저 실행되며 getServerSideProps 안에서 action이 dispatch되면 rootReducer의 switch문 중 HYDRATE case에 걸리게 된다.

import { combineReducers } from "react-redux";

const rootReducer = (state, action) => {
  switch (action.type) {
    case HYDRATE:
      return action.payload;
    default: {
      const combinedReducer = combineReducers({
        user,
        post,
      });
      return combineReducer(state, action);
    }
  }
};
export default rootReducer;


// ...

export const getServerSideProps = wrapper.getServerSideProps(async (ctx) => {
  const cookie = ctx.req ? ctx.req.headers.cookie : "";
  axios.defaults.headers.Cookie = "";
  if (ctx.req && cookie) {
    axios.defaults.headers.Cookie = cookie;
  }
});

//...

Next 서버가 실행되면 ctx.req가 존재하게 되는데 여기에 다양하 정보가 들어있다.

주의할 점은 getServerSideProps는 클라이언트가 아닌 프론트 서버에서 실행되는 로직이므로 잘못하면 사용자 정보를 공유해버릴 수도 있다는 점이다. 위에서도 쿠키를 헤더에 설정할 때, 사용자끼리 겹치지 않도록 분기를 해주었다.


2. Dynamic Routing (Next 9부터 지원)

pages 폴더 밑에 url 구조에 맞춰 폴더 설계를 할 수 있다.

ex) localhost:3000/post/3 => src/pages/post/[id].js

파일명의 [value] 안의 value 안의 값을 다음과 같이 받아올 수 있다.

서버사이드 props에서도 ctx.params.value, 혹은 ctx.query.value로 값을 받아올 수 있다.

import { useRouter } from 'next/router';

const Post = () => {
    const router = useRouter();
    const { id } = router.query;
    // ...
};

export const getServerSideProps = wrapper.getServerSideProps(async (ctx) => {
    ctx.store.dispatch({
        loadPostRequest(ctx.params.id);
    });
});


3. CSS의 SSR적용 + styled-components

next 자체적으로 babel과 webpack을 지원하고 있지만 이를 커스터마이징하는 것도 가능하다

.babelrc 파일을 root 디렉토리에 만든다 (webpack은 next.config.js)

{
    "presets": ["next/babel"],
    "plugins": [
        ["babel-plugin-styled-components", {
            "ssr": true,
            "displayName": true
        }]
    ]
}


pages 폴더 아래에 _document.js 파일 생성한다

(_document.js는 최초 SSR 시 한 번 실행되므로 다이나믹한 리소스들은 안쓰는 것이 좋다.

주로 meta 태그 설정, CDN 호출, 언어 설정 등을 정의한다.)

(이유는 모르나 class형 컴포넌트로 써야한다)

import React from 'react';
import Document, { Html, Head, body, Main, NextScript} from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
    static async getInitialProps(ctx) {
		const sheet = new ServerStyleSheet();
        const originalRenderPage = ctx.renderPage;

        try {
            ctx.renderPage = () => originalRenderPage({
                enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />)
            });
        	const initialProps = await Document.getInitialProps(ctx);
            return {
            	...initialProps,
              	styles: (
                	<>
                    	{initialProps.styles}
                    	{sheet.getStyleElement()}
                    </>
                )
            }
        } catch(err) {
            // ...
        } finally {
            sheet.seal();
        }
    }

    render() {
        <Html>
            <Head />
            <body>
            	<Main />
                <NextScript />
            </body>
        </Html>
    }
}


4. global window

window 객체 접근 시, 서버사이드에서는 이를 알 수 없기에 TypeError가 난다.

따라서, useEffect 안에서 사용해야한다. useEffect는 클라이언트 사이드 전용 이벤트이다.



참고 자료


zerocho - react-nodebird

redux-observable을 이용한 비동기 처리

|

[redux] redux-observable


1. redux-observable이란

redux-observable는 RxJS를 기반으로 만들어진 redux의 미들웨어로,

redux의 비동기 처리 로직을 커스텀하는 것이 가능해진다.

여기서 커스텀이란 action을 가로채서 무언가 처리를 하고 다음 action을 dispatch 할 수 있다는것을 말한다.

redux-thunk, redux-saga 등에 비해 러닝 커브가 높고 굳이 이걸 써야할까? 라고 생각할 수도 있지만

회사에서 사용하고 있으면 알아야한다..

redux-observable을 사용하면 기존 api 콜을 취소하고 새로 요청을 하는 등의 작업도 가능하여 비동기 처리에서는

막강한 파워를 자랑한다.


2. Epic

redux-observable에서의 핵심 기능이 아닐까 싶다.

보통 action이 발생하면 reducer에서 받아서 해당 로직을 처리한다.

이 reducer는 동기적이다. action이 dispatch되면 reducer에서 동기적으로 먼저 처리를 한 후에

action을 구독중인 epic이 비동기 처리를 수행하게 된다. (같은 action에 대해 epic이 더 나중에 시작)

epic은 action stream을 받아서 action stream을 내보내는 함수이다.

const somethingEpic = (action$) => action$.pipe(
    ofType('SOMETHING_TYPE'),
		 switchMap((action) => {
             const { id } = action.payload || {};
             return axios.get(`api/${id}`)
             			.then((res) => somethingSuccess({data: res.data}))
             			.catch((err) => somethingFailure({error: err.response.data}))
         });
    );


보통 이런 식으로 쓰인다. 개념 자체가 RxJS이고 해당 Operator들도 RxJS에서 나온 것이라 자주 쓰이는 것들은 알고 있는 것이 좋다. (ofType()은 redux-observable의 함수이다)

react-redux 패키지의 combineReducers처럼 redux-observable 패키지의 combineEpics를 사용해서 epic도 묶어서 사용할 수 있다.

import { combineEpics } from "redux-observable";

const epic1 = () => {
  //...
};
const epic2 = () => {
  //...
};

export const epics = combineEpics(
  epic1,
  epic2
  //...
);


3. Inject Dependency

처음 store 생성 시 middleware를 만들어 적용해야하며 이 때, epic들이 공통적으로 사용할 dependency도 주입할 수 있다.

import { createEpicMiddleware } from "redux-observable";

const epicMiddleware = createEpicMiddleware({
  dependencies: {
    service: something(),
  },
});

// 추후 epic에서 사용 시
const exampleEpic = (action$, state$, { service }) =>
  action$.pipe(
    ofType("EXAMPLE"),
    mergeMap((action) => service(action.payload))
    //...
  );



참고 자료


Redux Observable 간단 정리 하기

redux-observable 공식문서