[JavaScript] 함수형 프로그래밍

 

 

 

# 평가

 - 코드가 계산되어 값으로 만들어지는 것

 

# 일급

 - 값으로 다룰 수 있다.

 - 변수에 담을 수 있다.

 - 함수의 인자로 사용될 수 있다.

 - 함수의 결과로 반환될 수 있다.

 

# 일급 함수

 - 함수를 값으로 다룰 수 있다.

 - 조합과 추상화의 도구

 

자바스크립트에서 함수는 일급이다. 변수에 함수를 값으로 담을 수 있고, 함수의 인자로 함수를 전달 할 수 있다. 또 함수를 평가해서 값으로 다른 함수에도 전달 할 수 있다.

const add5 = a => a + 5;
console.log(add5); // a => a + 5
console.log(add5(5)); // 10

const f1 = () => () => 1;
console.log(f1()); // () => 1

const f2 = f1();
console.log(f2); // () => 1
console.log(f2()); // 1

 

 

# 고차 함수

 - 함수를 값으로 다루는 함수

 

고차함수는 두가지로 나눠진다.

 

 

## 함수를 인자로 받아서 실행하는 함수

const apply1 = f => f(1);
const add2 = a => a + 2;
console.log(apply1(add2)); // 3 -> (a => a + 2)(1);
console.log(apply1(a => a - 1)); // 0

const times = (f, n) => {
  let i = -1;
  while(++i) f(i);
}

times(console.log, 3); // 0 1 2
times(a => console.log(a + 10), 3); // 10 11 12

함수를 인자로 받고 받은 인자를 가지고 내부적인 동작을 실행하며 원하는 인자를 적용하는 함수이다.

 

 

## 함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴하는 함수)

const addMaker = a => b => a + b;
const add10 = addMaker(10);
console.log(add10); // b => a + b;
console.log(add10(5)); // 15
console.log(add10(10)); // 20

addMaker 함수는 함수를 리턴하는 함수이며, 클로저를 만들어 리턴한다. b => a + b 함수는 함수이자 a를 기억하는 클로저이다. 그러므로 addMaker는 클로저를 리턴하는 함수이고 함수가 함수를 만들어서 리턴할 때는 결국엔 클로저를 만들어서 리턴하는 방식으로 사용한다.

 

[TypeScript] chap.2

 

 

 

추가 Narrowing

1. undefined & null 처리 narrowing

- && 연산자

1 && null && 3   // null이 남음
undefined && '안녕' && 100  // undefined 남음

&& 값은 and값 말고 true/false 대신 자료형을 넣게되면 falsy 값을 찾아주는데 falsy값이란 false와 비슷한 null, undefined, NaN 등의 값을 의미한다.

 

function printAll(strs: string | undefined) {
  if (strs && typeof strs === "string") {  
    console.log(s);
  } 
}

따라서, 해당 함수에서 strs 값이 undefined값이 나온다면 console.log()가 실행되지 않는다.

 

 

2. 여러가지 object자료 narrowing

- in 연산자

type Fish = { swim: string };
type Bird = { fly: string };
function 함수(animal: Fish | Bird) {
  if ("swim" in animal) { // fly도 in을 쓸 수 있다.
    return animal.swim
  }
  return animal.fly
}

해당 object의 유니크한 속성을 가지고 있다면 그 속성으로 narrowing을 할 수 있다. (해당 object의 키값 in 자료형)의 형태로 쓰면되고 서로 배타적인 속성이 있어야한다.

 

 

- instanceof

let 날짜 = new Date();
if (날짜 instanceof Date){
  console.log('참이에요')
}

어떤 클래스로부터 new 키워드로 생성된 object는 instanceof를 통해 부모 클래스가 누군지 검사할 수 있는데 그것으로 narrowing을 할 수 있다.

 

 

- literal type

type Car = {
  wheel : '4개',
  color : string
}
type Bike = {
  wheel : '2개',
  color : string
}

function 함수(x : Car | Bike){
  if (x.wheel === '4개'){
    console.log('the car is ' + x.color)
  } else {
    console.log('the bike is ' + x.color)
  }
}

typeof 연산자를 사용하면 같은 object 타입이기때문에 구분할 수 없고, in은 해당 타입에 배타적인 속성이 없기때문에 마찬가지로 구분할 수 없다.

 

이럴때 literal type을 사용한다. Car 타입의 wheel은 무조건 4개, Bike 타입의 wheel은 무조건 2개가 나온다. 이것을 가지고 narrowing이 가능하다.

 

 

 

Never

함수에서 사용되는 return 타입으로 never 타입이 있다.

 

1. 절대 return을 하지 않아야한다.

2. 함수 실행이 끝나지 않아야된다. (endpoint가 없어야 한다)

 

function 함수(){
  console.log(123)
} // never 붙일 수 없음 => return undefined

///////////////////////

function 함수() :never{
  while ( true ) { // 무한반복
    console.log(123)
  }
} // never 가능

function 함수() :never{
  throw new Error('에러메세지') // 에러 강제 발생
}// never 가능

 

 

- 파라미터가 never 타입이 되는 경우

function 함수(parameter: string) {
  if ( typeof parameter === "string"){
    parameter + 1;
  } else {
    parameter;
  }
}

함수에서 타입을 string으로 정해 놓았기 때문에 무조건 string 타입이 들어오기 때문에 if문으로 type 체크를 하여 string을 제외한 타입이 들어오는 경우는 일어날 수 없으므로 never 타입이 출력된다. never 타입은 일어날 수 없는 경우, 일어나면 안되는 경우를 보여줄 때 나타난다.

 

 

 

public & private

class 안에서 public이나 private 등의 접근제한자를 통해 데이터 사용 제한을 조정할 수 있다.

 

* public

class User {
  public name: string;

  constructor(){
    this.name = 'kim';
  }
}

let 유저1 = new User();
유저1.name = 'park';  //가능

public은 어디서든 마음대로 사용하고 수정이 가능하다.

 

 

* private

class User {
  public name :string;
  private familyName :string;  

  constructor(){
    this.name = 'kim';
    let hello = this.familyName + '안뇽'; //가능
  }
  
  changeSecret(){
    this.familyName = 'park';
  }

}

let 유저1 = new User();
유저1.name = 'park';  //가능
유저1.familyName = 456; //에러남

private은 오로지 class{} 안에서만 수정이 가능하다. 따라서 private 제한자가 붙은 필드를 수정한다면 class함수 안에서 수정하는 메서드를 만들어 사용하면 된다. (ex. changeSecret())

 

 

- 추가 기능

class Person { 
  name;
  constructor ( name :string ){  
    this.name = name;
  } 
}
let 사람1 = new Person('john')

////////////////////////////////////////

class Person { 
  constructor ( public name :string ){  
  
  } 
}
let 사람1 = new Person('john')

public이나 private같은 접근 제한자를 constructor에 쓰면 필드나 constructor안에 this.속성 등을 생략할 수 있다.

 

 

 

protected & static

* protected

class User {
  protected x = 10;
}

class NewUser extends User {
  doThis(){
    this.x = 20;
  }
}

class 간에 extends로 상속을 받았을 때, 상속 받은 class끼리 공유할 수 있는 속성이라면 protected를, 해당 class에서만 쓸 수 있는 속성을 만들고싶다면 private을 쓴다.

 

 

* static

class에 정의되는 필드나 메서드는 class로부터 생성되는 object(instance)에 부여되지만 static 키워드를 사용하면 class에 직접 변수나 함수를 부여할 수 있다.

class User {
  static x = 10;
  y = 20;
}

let john = new User();
john.x //불가능
User.x //가능

 

ex)

class User { 
  static skill = 'js'; 
  intro = User.skill + '전문가입니다'
}

var 철수 = new User();
console.log(철수); // js전문가입니다.

User.skill = 'python';
var 민수 = new User();
console.log(민수); // python전문가입니다.

class 안에 간단한 메모나, 기본 설정값을 입력하거나, class로부터 생성되는 object가 사용할 필요 없는 변수들을 만들때 사용한다.

 

 

 

import & export & namespace

* import & export

// (a.ts)
export var 이름 = 'kim';
export var 나이 = 30;

// (b.ts)
import {이름, 나이} from './a' // ts는 확장자를 안붙임
console.log(이름)

 

 

- type도 가능하다

// (a.ts)
export type Name = string | boolean;
export type Age = (a :number) => number;

// (b.ts)
import {Name, Age} from './a'
let 이름 :Name = 'kim';
let 함수 :Age = (a) => { return a + 10 }

 

 

* namespace

import와 export가 없을 때에 여러개의 파일을 첨부(<script src="...">)하는 방식으로 사용 했는데 변수명이 겹치는 문제로 인해서 namespace를 사용했다.

// (a.ts)
namespace MyNamespace {
  export interface PersonInterface { age : number };
  export type NameType = number | string;
}

// (b.ts)
/// <reference path="./a.ts" /> // import 대신 namespace에선 이게 필요하다
let 이름 :MyNamespace.NameType = '민수';
let 나이 :MyNamespace.PersonInterface = { age : 10 };

예전엔 namespace 키워드 대신 module 키워드를 사용했는데 namespace로 변경되었다.

 

 

 

 

 

Generic

타입스크립트는 unknown, any, union 타입이 들어왔을 때, 일련의 과정을 거칠 때 타입을 알아서 변경해주지 않기때문에 문제가 생긴다. 해결책은 1) narrowing을 하거나 2) 애초에 타입을 파라미터로 함수에 미리 입력하는 방법이 있다. 2)의 경우를 generic이라고 한다.

 

 

* generic

function 함수<T>(x: T[]) :T {
  return x[0];
}

let a = 함수<number>([4,2])
let b = 함수<string>(['kim', 'park'])

함수를 실행할 때, generic(<>)을 통해 실행하는 함수의 타입을 동적으로 지정할 수 있다. <T>와 같은 generic 타입은 여러개 선언도 가능하다.

 

function 함수<T>(x: T) {
  return x - 1 // 에러
}

let a = 함수<number>(100)

함수를 generic으로 설정 했을 때, -1 연산을 하게되면 에러가 난다. 이유는 generic <T>에 들어가는 타입이 number 말고도 string 등 -1 연산이 불가능한 타입을 넣을 수 있기때문에 1) narrowing을 하거나 2) 타입을 미리 제한 해야한다.

 

 

* constraints

2)에 해당하는 generic 타입을 제한하는 것이다.

function 함수<T extends number>(x: T) {
  return x - 1
}

let a = 함수<number>(100) //잘됩니다

extends를 사용하면 generic 타입을 받아오는 부분을 입력한 형식으로 제한할 수 있다. (class나 interface에서 쓰는 extends 와는 살짝 다르다) 다른 extends는 상속, 복사의 역할을 하지만 여기서의 extends는 체크하는 역할을 한다.

 

또한 커스텀 타입도 extends 가능하다.

interface lengthCheck {
  length : number
}
function 함수<T extends lengthCheck>(x: T) {
  return x.length
}

let a = 함수<string>('hello')  //가능
let a = 함수<number>(1234) //에러남

length의 타입은 number이고 string의 length는 해당 문자열의 갯수를, array에서의 length는 배열의 갯수를 가져오기 때문에 사용할 수 있지만 number는 length를 사용할 수 없다.

 

 

 

react typescript
npx create-react-app 프로젝트명 --template typescript

 

* .tsx

- ts랑 똑같은데 jsx문법을 지원한다. 함수, 컴포넌트, state, props 타입체크를 해야한다.

 

1. 일반 변수, 함수 타입지정

- 일반 타입스크립트처러 함.

 

 

2. JSX 타입지정

// 타입 지정
let 박스 :JSX.Element = <div></div>
let 버튼 :JSX.Element = <button></button>

// 더 정확히 타입 지정
let 박스 :JSX.IntrinsicElements['div'] = React.createElement('div'); // 함수
let 버튼 :JSX.IntrinsicElements['button'] = <button></button>; // JSX

HTML태그는 JSX.Element라는 타입을 쓰면되고 정확히 지정하려면 JSX.IntrinsicElements라는 타입을 사용하면된다.

 

 

3. function component 타입지정

type AppProps = {
  name: string;
}; 

function App (props: AppProps) :JSX.Element { // 생략해도 알아서 지정
  return (
    <div>{message}</div>
  )
}

props 파라미터가 어떻게 생겼는지 타입지정 하면되고 return은 JSX.Element 이지만 자동으로 지정된다.

 

 

4. state 문법 사용시 타입지정

const [user, setUser] = useState<string | null>('kim');

state 만들 땐 자동으로 타입이 할당되지만 나중에 타입이 변하는 경우가 생긴다면 generic을 통해 타입을 집어넣으면 된다.

 

 

5. type assertion 문법 사용시

let code: any = 123; 
let employeeCode = <number> code; //안됩니다

리액트에서는 컴포넌트와 혼동될 수 있으므로 <>는 안되고 as만 사용할 수 있지만 웬만하면 안쓰는게 좋다.

 

 

 

Redux

1. state와 reducer 만들어서 쓰는 방법 (전통 방식)

index.tsx에 설정하는 부분:

import { Provider } from 'react-redux';
import { createStore } from 'redux';

interface Counter {
  count : number
}

const 초기값 :Counter  = { count: 0 }; // 변수 타입 지정

function reducer(state = 초기값, action :any) { // 값 변경하는 부분 타입 지정
  if (action.type === '증가') {
    return { count : state.count + 1 }
  } else if (action.type === '감소'){
    return { count : state.count - 1 }
  } else {
    return initialState
  }
}

const store = createStore(reducer);

// store의 타입 미리 export 해두기 
export type RootState = ReturnType<typeof store.getState>

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

state에는 기본적으로 타입을 지정한 변수를 받아오기 때문에 따로 타입 설정이 필요 없다. 변수(state 초기값)나 함수(reducer) 등에 타입을 지정하고 action은 값 변경하는 부분의 타입을 지정하면 된다.

 

App.tsx에 받아오는 부분:

import React from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { Dispatch } from 'redux'
import {RootState} from './index'

function App() {
  const 꺼내온거 = useSelector( (state :RootState) => state );
  const dispatch :Dispatch = useDispatch();

  return (
    <div className="App">
      { 꺼내온거.count }
      <button onClick={()=>{dispatch({type : '증가'})}}>버튼</button>
      <Profile name="kim"></Profile>
    </div>
  );
}

1) react-redux의 useSelector를 받아오는 부분의 타입을 지정한다. 받아오는 타입을 매번 손수 지정하기 귀찮으니 index.tsx에 있는 export해서 가져올 수 있다. (export type RootState = ReturnType<typeof store.getState> 부분)

2) useDispatch에서 데이터를 보내는데 dispatch의 타입으로 redux에서 import한 타입인 Dispatch를 사용해야한다. 안쓰면 에러남.

 

 

2. redux toolkit 쓰는 방법 (요즘 방식)

npm install @reduxjs/toolkit

신식 redux를 사용하려면 해당 툴킷을 설치하면 된다.

 

index.tsx에 설정하는 부분:

import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';

const 초기값 = { count: 0, user : 'kim' };

const counterSlice = createSlice({ // state + reducer 하는 부분
  name: 'counter',
  initialState : 초기값,
  reducers: {
    increment (state){
      state.count += 1
    },
    decrement (state){
      state.count -= 1
    },
    incrementByAmount (state, action :PayloadAction<number>){
      state.count += action.payload
    }
  }
})

let store = configureStore({ // slice로 만든 부분 저장
  reducer: {
    counter1 : counterSlice.reducer
  }
})

//state 타입을 export 해두는건데 나중에 쓸 데가 있음
export type RootState = ReturnType<typeof store.getState>

//수정방법 만든거 export
export let {increment, decrement, incrementByAmount} = counterSlice.actions

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

1) state 초기값 타입지정 하기

2) reducer 안의 action 파라미터 타입지정하기

 - action 타입은 PayloadAction이라는 Type을 import 해와서 해당하는 자료의 타입을 generic해서 타입지정 할수 있다.

 

App.tsx에 받아오는 부분:

import { useDispatch, useSelector } from 'react-redux'
import {RootState, increment} from './index'

function App() {

  const 꺼내온거 = useSelector( (state :RootState) => state);
  const dispatch = useDispatch();

  return (
    <div className="App">
      {꺼내온거.counter1.count}
      <button onClick={()=>{dispatch(increment())}}>버튼</button>
    </div>
  );
}

1) useSelector() 안에 파라미터에 타입지정 해서 가져오기

 - state의 타입을 따로 지정하거나 index.tsx에 해당 state의 타입으로 미리 만든 RootState라는 타입을 export해놓고 import해서 타입으로 사용한다.

2) useDispatch()에 타입 지정해서 보내기

 - 공식 문서에는 index.tsx에서 (export type AppDispatch = typeof store.dispatch)을 설정하고 App.tsx에서 (useDispatch<AppDispatch>()) 하는 방식을 권장하지만 (export let {increment, decrement, incrementByAmount} = counterSlice.actions) 부분에서 가져와서 사용해도 된다.

 

 

 

Tuple

array에 붙일 수 있는 타입으로 자료의 위치까지 정확히 지정할 수 있다.

let 멍멍이 :[string, boolean];
멍멍이 = ['dog', true]

 

 

- Tuple: rest parameter

function 함수(...x :[string, number] ){
  console.log(x)
}
함수('kim', 123)  //가능
함수('kim', 123, 456)  //에러
함수('kim', 'park')  //에러

x에 들어오는 파라미터는 array 형태로 담겨오기 때문에 array처럼 타입을 지정한다. 튜플을 이용하면 rest parameter를 엄격하게 사용할 수 있다. 일반 파라미터를 2개 넣고 설정해도 되는데 자료형의 차이가 있다.(array이거나 다른 타입이거나)

 

 

- Tuple option

type Num = [number, number?, number?];
let 변수1: Num = [10];
let 변수2: Num = [10, 20];
let 변수3: Num = [10, 20, 10];

해당의 형태로 자료를 만들 수 있는데 '?' 옵션은 항상 마지막에만 붙일 수 있고 앞의 자료가 같은 '?' 옵션이 붙어있어야 여러개가 사용 가능하다.

 

 

- Tuple: spread 연산자

let arr = [1,2,3]
let arr2 :[number, number, ...number[]] = [4,5, ...arr]

let arr3 :[number, number, ...number[]] = [4,5,6,7,8,9,10]

tuple 타입에 rest parameter 처럼 사용한다. 

 

 

 

declare & ambient module

타입스크립트에서 외부 자바스크립트 파일을 이용할 때 에러가 발생한다.

// (data.js)
var a = 10;
var b = {name :'kim'};

// (index.ts)
console.log(a + 1);

// (index.html)
<script src="data.js"></script>
<script src="index.js"></script>  //index.ts에서 변환된 js 파일

해당 결과는 제대로 잘 나오지만 index.ts 파일에서 a가 정의가 되어있지 않다고 에러가 난다.

 

 

* declare

declare를 사용하면 이미 정의된 변수나 함수를 재정의(override) 할 수 있고 타입도 지정할 수 있다.

- 자바스크립트로만 작성된 외부 라이브러리를 쓸 때 유용하다.

// (data.js)
var a = 10;
var b = {name :'kim'};

// (index.ts)
declare let a :number;
console.log(a + 1);

data.js에 a라는 변수가 있으니 index.ts에서는 에러를 내지말라고 하는 뜻. declare가 붙은 코드는 js로 변환되지 않는다.

 

 

* Ambient Module 

전역으로 쓸 수 있는 파일을 ambient module이라고 한다.

// (data.ts)
type Age = number;
let 나이 :Age = 20;

// (index.ts)
console.log(나이 + 1) // 가능
let 철수 :Age = 30; // 가능

타입스크립트에서 let name 이라는 이름의 변수생성이 안되는 이유를 여기서 찾을 수 있습니다. 어떤 기본 ts 파일이 let name 이미 쓰고있음.

 

 

- import & export

// (data.ts)
export {};
type Age = number;
let 나이 :Age = 20;

// (index.ts)
console.log(나이 + 1) // 불가능
let 철수 :Age = 30; // 불가능

import나 export 키워드가 적어도 하나라도 있다면 그 파일은 로컬 모듈이 된다. 거기에 있는 모든 변수는 export를 해야 다른 파일에서 사용 가능하다.

 

 

* declare global

import나 export가 있는 로컬 모듈에서 전역 변수를 만들고 싶다면 ?

declare global {
  type Dog = string;
}

로컬 파일에서 이런 코드를 적는다면 Dog라는 타입을 전역으로 이용 가능하다. 일종의 namespace라고 보면 된다.

 

 

 

 

 

d.ts

파일명.d.ts 형식의 파일은 타입만 따로 import 하기위한 타입저장용 파일이거나 프로젝트에서 사용하는 타입을 정리해놓은 레퍼런스용 파일이다. (d => definition이며 자바스크립트로 컴파일 되지 않는다.)

 

1. 타입만 따로 정의하기

export type Age = number;
export type multiply = (x :number ,y :number) => number
export interface Person { name : string }

정의 해둔 타입은 export해서 써야한다. d.ts파일은 ts파일이 아니기때문에 global ambient module이 되지 않는다. 쓰려는 ts파일에서 import해서 써야한다.

 

 

2. 레퍼런스로 사용하기

// (tsconfig.json)
{
    "compilerOptions": {
        "target": "es5",
        "module": "es6",
        "declaration": true, // 이 부분
    }
}

declaration 옵션을 true로 바꿔준다. 그럼 ts파일마다 d.ts파일이 생성되고 타입 정의만 정리되어서 담긴다.

 

// (index.ts)
let 이름 :string = 'kim';
let 나이 = 20;
interface Person { name : string } 
let 사람 :Person = { name : 'park' }


// (index.d.ts)
declare let 이름: string;
declare let 나이: number;
interface Person {
    name: string;
}
declare let 사람: Person;

 

 

- d.ts 파일 글로벌 모듈 만들기

{
    "compilerOptions": {
        ...
        "typeRoots": ["./types"] // d.ts 파일 글로벌 모듈화 하기
        ...
    }
}

프로젝트 내에 types/common 이라는 폴더를 만들고 해당 경로에 d.ts 파일을 넣으면 된다. 하지만 이 경우엔 d.ts 자동생성 기능을 끄고 기존 ts파일명과 겹치지 않도록 작성하는게 좋다. 그러므로 웬만하면 중복을 피하기 위해서 import export를 사용하는게 좋다.

 

 

- 유명한 라이브러리 d.ts

jquery나 bootstrap과 같은 잘 알려진 라이브러리는 d.ts 파일을 제공한다. Definitely Typed 이라는 사이트에 주로 쓰는 라이브러리가 모여있어서 찾아쓰거나 타입스크립트 공식 홈페이지 Type Search 에는 npm으로 라이브러리 설치 시 타입스크립트로 정의된 버전이 node_modules/@types 의 경로에 설치되거나 d.ts파일(타입 부분)만 따로 설치할 수 있다. (typeRoots 옵션이 있을경우 제거하거나 node_modules/@types 폴더를 추가해야 한다.)

 

 

 

implements

class 타입을 확인할 때 interface 문법을 사용하는데 implements를 통해 사용한다.

interface CarType {
  model : string,
  price : number
}

class Car implements CarType {
  model : string;
  price : number = 1000;
  constructor(a :string){
    this.model = a
  }
}
let 붕붕이 = new Car('morning');

interface는 object의 타입지정할 때도 쓰지만 class의 타입을 확인할 때도 사용한다. Car 클래스가 model과 price 속성을 다 가지고 있는지 확인 할때 implements를 사용한다.

 

interface CarType {
  model : string,
  tax : (price :number) => number;
}

class Car implements CarType {
  model;   ///any 타입됨
  tax (a){   ///a 파라미터는 any 타입됨 
    return a * 0.1
  }
}

implements는 class의 타입을 체크하는 용도이지 할당하지는 않는다.

 

 

 

object index signatures

object 자료에 타입을 미리 만들 때

1. object 자료에 어떤 속성들이 들어올 수 있는지 아직 모르는 경우

2. 타입지정할 속성이 너무 많은 경우

index signatures를 사용한다.

 

 

* index signatures

interface StringOnly {
  [key: string]: string
}

let obj :StringOnly = {
  name : 'kim',
  age : '20',
  location : 'seoul'
}

[모든 string으로 들어오는 key값에 할당되는 value는 string이다]라는 말이 된다.

 

따라서,

interface StringOnly {
  age : number,   // 에러
  [key: string]: string,
}

interface StringOnly {
  age : string,   // 가능  
  [key: string]: string,
}

위의 코드를 가능하게 하려면

 

interface StringOnly {
  age : number,   ///가능
  [key: string]: string | number,
}

이런 형식으로 작성해야 한다.

 

 

- array

interface StringOnly {
  [key: number]: boolean,
}

let obj :StringOnly = {
  0 : 'kim'
  1 : '20',
  2 : 'seoul'
}

[] 안의 key 값의 타입을 number로 지정할 수도 있다. array처럼 쓰고싶은 object가 있으면 이렇게 타입지정도 가능하다.

 

 

 

* Recursive Index Signature

interface MyType {
  'font-size' : {
    'font-size' : {
      'font-size' : number
    }
  }
}

let obj :MyType = {
  'font-size' : {
    'font-size' : {
      'font-size' : 14
    }
  }
}

object 안의 object 형태로 되어 있는 object의 타입은

 

interface MyType {
  'font-size': MyType | number
}


let obj :MyType = {
  'font-size' : {
    'font-size' : {
      'font-size' : 14
    }
  }
}

이렇게 사용할 수도 있다.

 

 

 

keyof & in

- keyof

keyof 연산자는 object 타입이 가지고 있는 모든 key 값을 union type으로 합쳐서 보내준다. object의 key를 뽑아서 새로운 타입을 만들고 싶을 때 사용한다.

interface Person {
  age: number;
  name: string;
}
type PersonKeys = keyof Person;   //"age" | "name" 타입됩니다
let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //불가능

/////////////////////////////////////////////////////////

interface Person {
  [key :string]: number;
}
type PersonKeys = keyof Person;   //string | number 타입됩니다
let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //가능

 

 

- Mapped Types

type Car = {
  color: boolean,
  model : boolean,
  price : boolean | number,
};

type TypeChanger <MyType> = {
  [key in keyof MyType]: string; // [ 자유작명 in keyof 타입파라미터 ] : 원하는 타입
};

type 새로운타입 = TypeChanger<Car>;

let obj :새로운타입 = {
  color: 'red',
  model : 'kia',
  price : '300',
}

object 안에 있는 속성들을 다른 타입으로 한번에 전부 변환되어진 타입을 만들 수 있다.

 

 

 

조건문 타입 & infer
type Age<T> = T extends string ? string : unknown;
let age : Age<string> //age는 string 타입
let age2 : Age<number> //age는 unknown 타입

삼항 연산자를 통해 type을 지정해줄 수 있다.

 

ex)

type FirstItem<T> = T extends any[] ? T[0] : any 

let age1 :FirstItem<string[]>;
let age2 :FirstItem<number>;

 

 

- infer

조건문을 사용할 때 infer 키워드를 사용하면 지금 입력한 타입을 변수로 만들 수 있다.

type Person<T> = T extends infer R ? R : unknown; 
type 새타입 = Person<string> // 새타입은 string 타입입니다

1) infer는 조건문 안에서만 사용가능

2) infer 우측을 자유롭게 작명하면 타입을 T에서 유추해서 R이라는 변수에 집어넣는다

3) R을 조건식 안에서 맘대로 사용 가능하다.

 - 타입파라미터에서 타입을 추출해서 쓰고싶을 때 사용하는 키워드이다.

 

ex)

// array 안에 있던 타입이 어떤 타입인지 뽑아서 변수로 만들 수 있다
type 타입추출<T> = T extends (infer R)[] ? R : unknown; 
type NewType = 타입추출< boolean[] > // NewType 은 boolean 타입입니다 

// 함수의 return 타입이 어떤 타입인지 뽑아서 변수로 만들 수 있다
type 타입추출<T> = T extends ( ()=> infer R ) ? R : unknown; 
type NewType = 타입추출< () => number > // NewType은 number 타입입니다

타입을 추출하는 식이라고 생각하면 된다. 이 작업을 대체하는 ReturnType<>이런 예약 타입이 있는데 여기에 함수타입을 대입하면 return 타입만 알아서 뽑아준다.

 

type Age<T> = T extends [string, ...any] ? T[0] : unknown;
let age1 :Age<[string, number]>;
let age2 :Age<[boolean, number]>;

type 타입뽑기<T> = T extends (x :infer R) => any ? R : any;
type a = 타입뽑기<(x :number) => void> //이러면 number가 이 자리에 남음
type b = 타입뽑기<(x :string) => void> //이러면 string이 이 자리에 남음

 

 

 

'TypeScript' 카테고리의 다른 글

[TypeScript] chap.1  (0) 2021.12.15

[TypeScript] chap.1

 

 

 

설치 및 세팅

1. nodejs 설치

2. 타입스크립트 설치

npm install -g typescript
tsc -w // 자바스크립트로 저장시 자동 컴파일

 

 

* React에서 Typescript 사용

- 이미 있는 프로젝트

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

 

- 새로운 프로젝트

npx create-react-app my-app --template typescript

 

 

* Vue에서 Typescript 사용

vue add typescript
<script lang="ts">
  
</script>

lang 옵션을 키고 사용할 수 있다.

 

 

* tsconfig.json

- 타입스크립트 ts 파일들을 .js 파일로 변환할 때 어떻게 변환할 것인지 세부설정하는 파일

{
 "compilerOptions": {

  "target": "es5", // 'es3', 'es5', 'es2015', 'es2016', 'es2017','es2018', 'esnext' 가능
  "module": "commonjs", //무슨 import 문법 쓸건지 'commonjs', 'amd', 'es2015', 'esnext'
  "allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지 
  "checkJs": true, // 일반 js 파일에서도 에러체크 여부 
  "jsx": "preserve", // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
  "declaration": true, //컴파일시 .d.ts 파일도 자동으로 함께생성 (현재쓰는 모든 타입이 정의된 파일)
  "outFile": "./", //모든 ts파일을 js파일 하나로 컴파일해줌 (module이 none, amd, system일 때만 가능)
  "outDir": "./", //js파일 아웃풋 경로바꾸기
  "rootDir": "./", //루트경로 바꾸기 (js 파일 아웃풋 경로에 영향줌)
  "removeComments": true, //컴파일시 주석제거 

  "strict": true, //strict 관련, noimplicit 어쩌구 관련 모드 전부 켜기
  "noImplicitAny": true, //any타입 금지 여부
  "strictNullChecks": true, //null, undefined 타입에 이상한 짓 할시 에러내기 
  "strictFunctionTypes": true, //함수파라미터 타입체크 강하게 
  "strictPropertyInitialization": true, //class constructor 작성시 타입체크 강하게
  "noImplicitThis": true, //this 키워드가 any 타입일 경우 에러내기
  "alwaysStrict": true, //자바스크립트 "use strict" 모드 켜기

  "noUnusedLocals": true, //쓰지않는 지역변수 있으면 에러내기
  "noUnusedParameters": true, //쓰지않는 파라미터 있으면 에러내기
  "noImplicitReturns": true, //함수에서 return 빼먹으면 에러내기 
  "noFallthroughCasesInSwitch": true, //switch문 이상하면 에러내기 
 }
}

 

 

 

기본 타입
let 이름 :string = 'kim'; // string
let 나이 :number = 20; // number
let 결혼했니 :boolean = false; // boolean

let 회원들 :string[] = ['kim', 'park'] // array
let 내정보 : { age : number } = { age : 20 } // object

// **기본적으로 변수 생성 시 타입이 알아서 지정되기 때문에 일일이 할 필요는 없다.

 

 

 

 

union 타입
let 이름: string | number = 'kim';
let 나이: (string | number) = 100; // 같음

///////////////////////////////////////////

var 어레이: number[] = [1,'2',3]
var 오브젝트: {data : number} = { data : '123' }

var 어레이: (number | string)[] = [1,'2',3]
var 오브젝트: {data : (number | string) } = { data : '123' }

 

 

 

any, unknown 타입

* any

아무 자료나 대입할 수 있는 타입. (타입스크립트의 기능을 해제하는 타입)

let 이름: any = 'kim';
이름 = 123;
이름 = undefined;
이름 = [];

 

 

* unknown

아무 자료나 대입할 수 있는 타입. (타입은 uknown으로 지정되어 다른 타입과는 에러발생. any는 아님)

let 이름: unknown;

let 변수1: string = 이름;
let 변수2: boolean = 이름;
let 변수3: number = 이름;

///////////////////////////

let 이름: unknown;
이름[0];
이름 - 1;
이름.data;

 

타입스크립트는 언어가 엄격하기 때문에 변경하려는 변수의 타입이 확실해야만 연산을 수행해준다. -1, +1 같은 경우는 number 타입일 때만, .name 등은 object 타입 일때만 사용이 가능하다.

 

 

 

함수 & void

1. 함수로 들어오는 자료 (파라미터)

2. 함수에서 나가는 자료 (return)

function 내함수(x :number) :number { 
  return x * 2 
}

 

 

- void

function 내함수(x :number) :void { 
  return x * 2 //여기서 에러남 
}

함수에서 return을 방지할 때 사용한다.

 

 

- 파라미터가 옵션인 경우

function 내함수(x? :number) { // == x : number | undefined

}
내함수(); //가능
내함수(2); //가능

x? :number은 x :number | undefined 와 같은 의미이다.

 

 

 

 

 

Narrowing & Assertion

타입을 확정할 때 쓰는 문법.

 

* Type Narrowing

function 내함수(x :number | string){
  if (typeof x === 'number') {
    return x + 1
  } 
  else if (typeof x === 'string') {
    return x + 1
  }
  else { // 함수안에서 if쓸 때 else가 없으면 에러날 수도 있으니 쓰자
    return 0
  }
}

 

 

* Type Assertion

function 내함수(x :number | string){ 
    return (x as number) + 1 
}
console.log( 내함수(123) )

해당하는 변수를 number로 지정한다. (내가 꼭 ! number 타입이 들어올 것이라는 확신이 있을 때 사용한다.)

 

특징:

1. union 타입 중에서 하나의 정확한 타입으로 지정할 때 사용 (형 변환처럼 사용하면 에러 남)

2. 타입실드를 임시로 해제한다고 생각하면 된다. (타입을 바꿔주는건 아니다)

 

Assertion은

1. 타입에러가왜 나는지 모르겠는 상황에서 에러 해결용으로 사용

2. 어떤 타입이 들어갈지 확실히 아는데 컴파일 상에서 에러가 날 때 사용.

 

**웬만하면 Narrowing 쓰는게 더 나음.

 

 

 

type & readonly

* type

타입의 정의가 너무 길거나 복잡하게 나열된 타입을 변수에 담아 사용할 수 있다.

type Animal = string | number | undefined;
let 동물 :Animal;
type 사람 = {
  name : string,
  age : number,
}

let teacher :사람 = { name : 'john', age : 20 }

 

 

- object의 속성이 선택사항일 때

type Square = {
  color? : string,
  width : number,
}

let 네모2 :Square = { 
  width : 100 
}

함수의 파라미터와 마찬가지로 object에서도 ?를 통해 string | undefined 형태로 선택적으로 사용할 수 있다.

 

 

- type 키워드 여러개 합치기

type Name = string;
type Age = number;
type NewOne = Name | Age;

or 연산자를 이용해서 union 타입으로 만들 수 있다.

 

type PositionX = { x: number };
type PositionY = { y: number };
type XandY = PositionX & PositionY
let 좌표 :XandY = { x : 1, y : 2 }

and(&) 기호를 쓰면 object 안의 두개의 속성을 합칠 수 있다. (extend)

 

type Name = string;
type Name = number; // 불가

type 키워드는 재정의가 불가능하다.

 

 

* readonly

object 속성을 const처럼 재 할당할 시에 값이 변경하는걸 미리 감지하고 차단한다

type testType = {
  readonly name : string,
}

let 이름 :testType = {
  name : '홍길동'
}

이름.name = '고길동' //readonly라서 에러남

컴파일 시에 에러만 출력되는것이고, 자바스크립트로 변환하면 문제없이 작동한다.

 

 

 

Literal type

특정 글자나 숫자만 가질 수 있게 그것으로 타입 제한을 두는 타입

let john :'대머리';
let kim :'솔로';

let 방향: 'left' | 'right';
방향 = 'left';

function 함수(a : 'hello') : 1 | 0 | -1 {
  return 1 
}

 

- as const

1. 타입을 object의 value로 바꿔준다. (타입을 'kim'으로 바꿔준다)

2. object 안에 있는 모든 속성을 readonly로 바꾼다. (변경하면 에러)

var 자료 = {
  name : 'kim'
}
function 내함수(a : 'kim') { // 'kim' 타입

}
내함수(자료.name) // string 타입 => 에러

function의 파라미터 변수 a는 'kim'이라는 리터럴 타입이고, 자료.name으로 가져오는 'kim'은 string 타입이다.

 

var 자료 = {
  name : 'kim'
} as const;

function 내함수(a : 'kim') {

}
내함수(자료.name)

해결책:

1. object 만들 때 object의 타입을 지정한다.

2. assertion을 쓴다 (ex. as 'kim')

3. as const를 object 자료형에 붙인다.

 

 

 

function & method의 type alias

* function

type NumOut = (x : number, y : number ) => number ;

let ABC :NumOut = function(x,y){
  return x + y
} // 함수 표현식

함수에 type 지정하기 (함수 선언문 형태로는 type을 만들 수 없다. 사용할 때도 표현식으로 사용)

 

 

* method

type 회원정보타입 = {
    name: string,
    plusOne: (a: number) => number,
    changeName: () => void
};

let 회원정보 :회원정보타입 = {
    name: 'kim',
    plusOne(a: number): number{
        return a + 1;
    },
    changeName: () => {
        console.log('안녕');
    }
};
회원정보.plusOne(1);
회원정보.changeName();

 

 

 

HTML

- 세팅

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs",
        "strictNullChecks": true // true로 설정 (false는 타입스크립트 쓰는 의미가..)
    }
}

null 체크 옵션인데 html 조작할 때 셀렉터로 찾으면 null이 발생하는 일이 많다.

 

 

- HTML 변경

  • narrowing
  • instanceof
  • assertion
  • optional chaining
  • strict false
let 제목 = document.querySelector('#title');
if(제목 != null){
    제목.innerHTML = '반가우요';
}
if(제목 instanceof Element){
    제목.innerHTML = '반가우요';
}
제목.innerHTML = '반가우요'; //as Element => assertion
if(제목?.innerHTML != undefined){
    제목.innerHTML = '반가우요';
}

 

 

- instanceof & optional chaining

let 링크 = document.querySelector('.link');

// instanceof
if(링크 instanceof HTMLAnchorElement){
    링크.href = 'https://kakao.com'
}

// optional chaining
let 버튼 = document.querySelector('#button');
버튼?.addEventListener('click', function(){
    
})

ex:

<a> =  HTMLAnchorElement

<img> = HTMLImageElement

<h4> = HTMLHeadingElement

 

 

 

 

 

class

- 필드값 타입지정

class Person {
  data = 0;
}

let john = new Person();
let kim = new Person();

console.log(john.data);
console.log(kim.data);

 

 

- constructor 타입지정

class Person {
  name;
  age;
  constructor ( a :string ){
    this.name = a;
    this.age = 20;
  }
}

constructor에도 default parameter나 rest parameter를 사용 할 수 있다.

 

 

- method 타입지정

class Person {
  
  add(숫자){ // Person의 prototype에 저장 됌
    console.log(숫자 + 1)
  }
}

add라는 함수를 prototype으로 사용할 수 있다.

 

 

 

interface

object 자료형의 타입을 편리하게 저장할 수 있다.

interface Square { 
  color :string, 
  width :number, 
} 

let 네모 :Square = { color : 'red', width : 100 }

 

 

- extends

interface Student {
  name :string,
}
interface Teacher {
  name :string,
  age :number,
}

///////////////////////////////////////

interface Student {
  name :string,
}
interface Teacher extends Student {
  age :number
}

let 학생: Student = {name: 'kim'}
let 선생: Teacher = {name: 'kim', age: 20 }

interface의 extends를 사용하면 extends 되는 object 안에 있는 것들을 복사해서 넣어준다.

 

 

* interface와 type의 차이점

- interface

interface Animal { 
  name :string 
} 
interface Cat extends Animal { 
  legs :number 
}

extends하기

 

interface Student {
  name :string,
}
interface Teacher {
  age :number
}

let 변수 :Student & Teacher = { name : 'kim', age : 90 }

intersection(&) 기호를 통한 extends와 유사하게 사용하기

 

// 에러 안남 (타입 명 중복 허용)
interface Animal { 
  name :string 
} 
interface Animal { 
  legs :number 
}

////////////////////////////

// 에러 남
interface Animal { 
  name :string 
} 
interface Dog extends Animal { 
  name :number 
}

타입이름 중복 선언시 extends한 것이랑 동일하게 동작하며 interface 안에 개별적 속성을 한꺼번에 가질 수 있다. type 선언을 자주하는 외부 라이브러리 이용시 type 선언을 덮어쓰기(override) 하기 편하다.

 

 

- type

type Animal = { 
  name :string 
} 
type Cat = Animal & { legs: number }

extends 대신에 intersection(&) 기호를 통해서 object 두개를 합칠 수 있다. (하지만 interface도 이렇게 사용할 수 있다.)

 

// 에러 남
type Animal = { 
  name :string 
} 
type Animal = { 
  legs :number 
}

///////////////////

type Animal = {name: string}
type Cat = {name: number} & Animal

let 야옹이 :Cat = {name: 'kim'}

타입이름 중복 선언시 에러난다. 타입을 intersaction(&) 했을 때, 타입 안에 개별적 속성이 겹치는 경우, 사용할 때 에러가 난다.

 

 

결론: object형태의 자료형은 interface로 만들고 다른형태(string, number, boolean ... 등 primitive 타입)의 자료형은 type 키워드로 만들어 사용하자.

 

 

 

'TypeScript' 카테고리의 다른 글

[TypeScript] chap.2  (0) 2021.12.17

[React] chap.3

 

 

 

Build
// npm
npm run build

// yarn
yarn build

build 폴더 안에 컴파일된 파일 생성.

해당 파일은 리액트로 개발한 소스들과 JSX 파일 등을 브라우저가 읽을 수 있는 html, css, js로 컴파일한 파일들이 모여있다.

 

- 하위 경로에 배포

package.json 파일의 큰 object에 경로 추가

"homepage" : "http://url.com/blog",

 

 

 

Context API

컴포넌트가 여러개 중첩되어 있을 경우 props의 중복사용 하는것을 대체로 사용한다.

 

1. context 생성

let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
  )
}

 

2. context 범위 지정

let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
    <재고context.Provider value={재고}>
      <카드레이아웃3개생성하는부분/>
    </재고context.Provider>
    
  )
}

 

3. context 불러오기

import React, {useState, useContext} from 'react';

function Card(){
  let 재고 = useContext(재고context);

  return (
    <HTML많은곳/>
    <div>{재고[0]}</div>
  )
}

 

 

** 컴포넌트가 다른 파일에 있을 때

export let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
    <재고context.Provider value={재고}>
      <Detail/>
    </재고context.Provider>
  )
}

- context를 export한다. 

 

import {재고context} from './App.js';

function Detail(){
  let 재고 = useContext(재고context);

  return (
    <HTML많은곳/>
  )
}

 

 

 

애니메이션
// npm
npm install react-transition-group

// yarn
yarn add react-transition-group
import {CSSTransition} from 'react-transition-group';

 

 

function Detail(){
  return (
    <div>
      <CSSTransition in={true} classNames="wow" timeout={500}>
        <TabContent 누른탭={누른탭} />
      </CSSTransition>
    </div>
  )
}

function TabContent(){
  if (props.누른탭 === 0){
    return <div>내용0</div>
  } else if (props.누른탭 === 1){
    return <div>내용1</div>
  } else if (props.누른탭 === 2){
    return <div>내용2</div>
  }
}
.wow-enter {
  opacity : 0
}

.wow-enter-active {
  opacity : 1;
  transition : all 500ms;
}

 

 

 

 

Redux

1. props 전송 없이도 모든 컴포넌트 들이 state를 사용할 수 있게 만들어준다.

// npm
npm install redux react-redux

//yarn
yarn add redux react-redux

(redux, react-redux 두 개)

 

 

- 세팅

(index.js)

import 많은곳;
import {Provider} from 'react-redux';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

);

1. Provider import

2. state 공유를 원하는 컴포넌트 감싸기

 

(index.js)

import 많은곳;
import {Provider} from 'react-redux';
import {createStore} from 'redux';

let store = createStore(()=>{ return [{id : 0, name : '멋진신발', quan : 2}]  })

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

);

3. createStore() 함수로 redux에서 state 만들기

 

(index.js)

import 많은곳;
import {Provider} from 'react-redux';
import {createStore} from 'redux';

let store = createStore(()=>{ return [{id : 0, name : '멋진신발', quan : 2}]  })

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

4. Provider에 만든 state를 props처럼 등록하기

 

 

- redux로 등록한 데이터 쓰기

(Cart.js)

import 많은곳;
import {connect} from 'react-redux';
function Cart(props){
  return (
    <div>
      <Table responsive>
        <tr>
          <th>#</th>
          <th>상품명</th>
          <th>수량</th>
          <th>변경</th>
        </tr>
        <tr>
          <td>1</td>
          <td>{props.state[0].name}</td>
          <td>Table cell</td>
          <td>Table cell</td>
        </tr>
      </Table>
    </div>
  )
}

function state를props화(state){
  return {
    state : state
  }
}

export default connect(state를props화)(Cart);

1. connect함수를 import하고 아래에 function을 하나 만든다.

2. export default 부분에 import한 connect를 사용한다.

 

밑에 생성한 함수는 Cart라는 함수에서 props로 받아서 쓸 수 있도록 state를 props화 하는 역할의 함수이다.

 

 

2. state(상태) 관리에 용이하다.

createStore 함수로 만들었던 object가 있는 index.js에 수정에 용이한 함수를 만든다. (데이터의 수정방법을 미리 정의해둘 수 있다.)

 

- reducer

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){
  if (액션.type === '수량증가') {
    return 수량증가된새로운state
  } else {
    return state
  }
  
}
let store = createStore(reducer);

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>
);

redux의 데이터 수정은 reducer의 액션(데이터수정방법) 이라는 파라미터의 type 값을 통해서 데이터의 변경이 일어날지 체크한다.

 

(Cart.js에 있던 버튼)

<button onClick={()=>{ props.dispatch({type: '수량증가'}) }}> + </button>

기본state라는 object가 있고 해당 object의 데이터의 수정이 일어난 것을 다른 하위 컴포넌트에서 받아온다. 꼭 수정할땐 props.dispatch()를 사용한다.

 

 

- reducer 여러개

* 이렇게 object의 API 스펙을 변경하면 나중에 문제가 생긴다. (상품만 꺼낼땐 true를 걸러야 하는데 추가적인 작업이 또 필요하다.)

let state초기값 = [ {id : 0, name : '새로운상품', quan : 2}, true ];

 

 

- reducer 여러개 정의

(index.js)

import {createStore, combineReducers} from 'redux';

let state초기값 = [ {id : 0, name : '새로운상품', quan : 2} ];

function reducer(){
  저번시간 만든 리듀서
}

let alert초기값 = true;

function reducer2(state = alert초기값, 액션){
  return state
}

let store = createStore( combineReducers({reducer, reducer2}) )

createStore함수 안에 combineReducers 함수를 쓰고 안에 각 데이터를 object 형태로 파라미터를 넘겨주면 API 스펙 변경없이 만든 reducer들을 추가할 수 있다. (combineReducers import 필요)

 

 

- reducer 여러개 사용

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table>table부분</Table>
      { props.alert열렸니 === true
       ? (<div className="my-alert2">
            <p>지금 구매하시면 20% 할인</p>
            <button>닫기</button>
          </div> )
       : null 
       }
    </div>
  )
}

function state를props화(state){
  console.log(state); // {reducer: [], reducer2: true}
  return {
    state : state.reducer,
    alert열렸니 : state.reducer2 //리듀서2에 있는거 가져오는법
  }
}
export default connect(state를props화)(Cart);

reducer를 store에 저장하는 부분이 바뀌면 reducer 사용하는 부분의 데이터 형식도 바뀌게 된다. 때문에 받아오는 부분도 수정이 필요하다.

 

 

- dispatch의 추가 기능

import {connect} from 'react-redux'

(Detail.js)

function Detail(props){
  return (
    <HTML많은곳/>
    <button onClick={()=>{ 
  
      props.dispatch({type : '항목추가', payload : {id : 2, name : '새로운상품', quan : 1} }) 

    }}>주문하기</button>
  )
}

function state를props화(state){
   ...
   ...
}
export default connect(state를props화)(Detail)

dispatch함수의 payload 데이터를 보면 redux store까지 해당 데이터를 실어 보낼 수 있고 reducer안에서 보낸 데이터를 사용할 수 있다. (새로운 js에서 redux를 사용하면 connect import와 props로 사용할 수 있는 함수를 정의해야한다)

 

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){

  if (액션.type === '항목추가') {

    let copy = [...state];
    copy.push(액션.payload);
    return copy;
    
    } else {
      ...
      ...
    }
}

reducer(state, '액션') {} 에서 액션이라는 파라미터는 dispatch 함수 안에 들어있던 모든게 들어있다. 

 

 

데이터를 저장하고 수정할 때 react는 SPA이기 때문에 개발단계에서 페이지 이동이 일어날 시에 state 데이터가 초기화 된다. 데이터가 변경된 것을 확인하고 싶으면 라우터 함수(history.push 등)를 사용해서 결과를 나타내는 페이지를 강제로 호출하면 변경된 데이터를 확인할 수 있다.

 

 

* useSelector

redux store에 있는 state를 꺼내쓰려면 props화 해주는 함수를 첨부해야하는데 그 때 useSelector Hook을 사용하면 좀 더 쉽게 꺼내올 수 있다.

// function state를props화(state){
//     // console.log(state);
//     return {
//         state: state.reducer,
//         alert열렸니: state.reducer2
//     }
// }

// export default connect(state를props화)(Cart)
export default Cart;

- props화 함수 삭제하고 기존 export 주석해제

 

import { useSelector } from 'react-redux';

function Cart(props) {
  let state = useSelector((state) => state )
  console.log(state.reducer)
  
  (생략)
}
<tbody>
  {
   state.reducer.map((a,i)=>{
     return (
      <tr key={i}>
      <td>{ a.id }</td>
      <td>{ a.name }</td>
      <td>{ a.quan }</td>

- useSelector import 및 기존 (props.state => state.reducer 변경)

 

 

* useDispatch

props화 하는 함수말고도 dispatch함수도 간단하게 사용 가능하다.

import { useSelector, useDispatch } from 'react-redux';

function Cart(props) {
  let state = useSelector((state) => state )
  let dispatch = useDispatch()
  
  (생략)
}
<td><button className="btn btn-primary" onClick={() => {
    dispatch({ type: '수량증가', payload: { status: {i} } })
}}>+</button>

- useDispatch import 하고 변수로 선언하여 사용 (props.dispatch() => dispatch() 변경)

 

 

 

react if 응용

- if / else

function Component() {
  if ( true ) {
    return <p>참이면 보여줄 HTML</p>;
  } else {
    return null;
  }
}

return()안에서의 if는 쓸 수없다. if는 return을 반환 할때 사용한다.

 

 

- 삼항연산자(ternary operator)

function Component() {
  return (
    <div>
      {
        1 === 1
        ? <p>참이면 보여줄 HTML</p>
        : null
      }
    </div>
  )
}

return()안에서의 if는 쓸 수없으니 삼항 연산자로 대체 할 수 있다.

 

 

- 오브젝트 자료형을 응용한 enum

var 탭UI = { 
  info : <p>상품정보</p>,
  shipping : <p>배송관련</p>,
  refund : <p>환불약관</p>
}

function Component() {
  var 현재상태 = 'info';
  return (
    <div>
      {
        탭UI[현재상태]
      }
    </div>
  )
}

자바스크립트 오브젝트 자료형을 응용하여 if문처럼 사용할 수 있다. (var 변수 대신 state에 object형으로 넣어 사용할 수 있다)

 

 

 

state변경함수 주의: async

(2022년 이후 리액트 18.0버전 batching 업데이트 전까지)

import React, { useState } from "react";

function Test(){

    let [count, setCount] = useState(0);
    let [age, setAge] = useState(20);

    return(
        <div>
            <div>안녕하십니까 전 {age}</div>
            <button onClick={() => {
                /***** 22까지만 증가, 23부터는 그대로 *****/
                { count < 3
                    ? setAge(age + 1)
                    : setAge(age);
                }                
                setCount(count + 1)
            }}>누르면 한살먹기</button>
        </div>
    );
}

export default Test;

결과: 23까지 올라감

 

이유는 state변경 함수의 특징이 async로 작동하기 때문이다.

setCount(count + 1)에서 3으로 만들 때, 이 작업이 async로 동작하게 되어 count의 값을 3으로 만들기 전에 삼항연산자에 count < 3의 작업을 먼저 실행하는데 그 시점의 count의 값은 아직 2이기 때문에 age에 +1을 한다.

 

결국 state1을 변경하고 state2를 변경하는 코드를 작성할땐 가끔 문제가 생긴다. 때문에 sync처럼 실행시키기위해선 useEffect를 사용하면된다.

import React, { useEffect, useState } from "react";

function Test(){

    let [count, setCount] = useState(0);
    let [age, setAge] = useState(20);
    
    useEffect(() => {
        
        if(count != 0 && count < 3){ // 처음 로드 됐을 때 원치않은 값 증가 방지
            setAge(age + 1)
        }else{
            setAge(age)
        }        
        console.log(count);
    }, [count])

    return(
        <div>
            <div>안녕하십니까 전 {age}</div>
            <button onClick={() => {
                setCount(count + 1)
                // { count < 3
                //     ? setAge(age + 1)
                //     : setAge(age);
                // }                
            }}>누르면 한살먹기</button>
        </div>
    );
}

export default Test;

useEffect를 사용해서 count가 변하면 age+1을 sync하게 변경했다. 그러면 원래 의도대로 값을 증가 시킬 수 있는데 문제는, useEffect가 처음 로드될 때도 한번 실행이 되기 때문에 useEffect를 막는 코드를 사용해도 되고 if문에 조건을 하나 더 추가한다.

 

꼭 useEffect말고도

 

1. count와 age를 동시에 한 state에 array나 object 자료형으로 저장해도 되고

2. 바뀔때마다 렌더링이 필요한 변수는 state로 만들고 그럴 필요 없는건 let변수로 만들어서 사용해도 된다.

 

 

 

 

performance

1. 함수나 오브젝트는 변수에 담아쓰자 (메모리공간을 아낄 수 있다.)

var 스타일 = {color : 'red'};

function Cart(){
  return (
    <div style={ 스타일 } ></div>
  )
}

컴포넌트 바깥에 있는 변수에 저장해서 쓰자. ( => function() 밖 )

컴포는트가 재 렌더링 될 때 변수에 저장되지 않은 이름 없는 자료형들은 매번 새로운 메모리 영역을 할당해줘야하기 때문에 성능상 좋지 않다. (class는 class안에 함수 집어넣는 공간에 저장)

 

 

2. 애니메이션 줄 때 레이아웃 변경 애니메이션은 피하자

- css렌더링 단계에서 layout 영역의 스타일(width, margin, padding, left, right, top ,bottom 등) 말고 그것보다 가벼운 단계 (transform, opacity 등)과 같은 속성을 애니메이션으로 이용한다.

 

 

3. 컴포넌트 import할 때 lazy하게 import 한다.

컴포넌트 중에 메인페이지에 들어왔을 때, 첫 페이지 방문 시 필요하지 않은 컴포넌트들은 꼭 전부 로딩 할 필요가 없다.

 

 

* lazy loading

(App.js 상단)

import React, {useState, useContext, lazy, Suspense} from 'react';
let Detail = lazy( ()=>{ return import('./Detail.js') } );

(App.js 중단에 Detail 컴포넌트 쓰는 곳)
render (
  <Suspense fallback={ <div>로딩중입니다~!</div> }>
    <Detail/>
  </Suspense>
)
  • react 라이브러리에서 lazy, Suspense를 import한다.
  • lazy함수를 이용해서 lazy loading 할 페이지를 변수(Detail)로 만든다.
  • Suspense 컴포넌트로 해당 import한 변수(Detail)를 감싸준다.
  • fallback 속성엔 컴포넌트 로딩 전까지 띄울 HTML을 적어준다.

 

 

* React Dev Tools(리액트 개발자 도구 크롬 확장프로그램)

해당 크롬 확장 프로그램을 설치하면 개발자 도구에서 컴파일된 상태 이전에 리액트 버전의 컴포넌트들을 확인 할 수 있다.

- Components: 현재 페이지에 사용된 모든 컴포넌트와 props, state 확인 및 수정, 렌더링 조작 등

- Profiler: 렌더링 되는 속도 측정 및 상태 보기

 

**애드 블록이 설치되어있다면 비활성화 시켜야 동작 시킬 수 있다.**

 

 

* memo

컴포넌트는 컴포넌트와 관련된 state나 props 가 변경되면 자동 재 렌더링 되는데 의도하지 않은 컴포넌트 또한 재 렌더링 되는경우를 막고싶을 때 사용한다.

 

function Cart(){
  return (
    <Parent 이름="존박" 나이="20"/>
  )
}

function Parent(props){
  return (
    <div>
      <Child1 이름={props.존박} />
      <Child2 나이={props.나이} />
    </div>
  )
}

function Child1(){
  useEffect( ()=>{ console.log('렌더링됨1') } );
  return <div>1111</div>
}
function Child2(){
  useEffect( ()=>{ console.log('렌더링됨2') } );
  return <div>2222</div>
}

여기서 Parent컴포넌트에 있는 이름 값이 바뀌면 Child1의 값만 재렌더링 되는것이 아니라 Child2의 값 또한 재 렌더링 된다. (Parent를 구성하는 state나 props가 변경되면 관련된 모든 컴포넌트가 다 재 렌더링 되기때문)

 

 

import React, {useEffect, memo} from 'react';

function Cart(){
  return (
    <Parent 이름="존박" 나이="20"/>
  )
}

function Parent(props){
  return (
    <div>
      <Child1 이름={props.존박}/>
      <Child2 나이={props.나이}/>
    </div>
  )
}
function Child1(){
  useEffect( ()=>{ console.log('렌더링됨1') } );
  return <div>1111</div>
}
let Child2 = memo(function(){
  useEffect( ()=>{ console.log('렌더링됨2') } );
  return <div>2222</div>
})

1. react에서 memo를 import한다.

2. 원하는 컴포넌트(Child2)를 memo로 감싼다.

 

- memo의 동작은 memo로 감싼 컴포넌트를 아무렇게나 재 렌더링을 안시키려고 기존 props와 바뀐 props를 비교하는 연산이 추가로 진행되기 때문에 이 동작 자체로도 부담이 될 수 있다. 사용할지 말지 리액트 개발자 도구에서 렌더링속도를 측정해보고 작은 사이트거나 HTML의 양이 매우 적을 경우엔 안쓰는게 낫다.

 

 

 

PWA (Progressive Web App)

PWA는 웹사이트를 안드로이드/iOS 모바일 앱처럼 사용할 수 있게 만드는 웹개발 기술이다. react는 웹앱을 만드는 기술이라 모바일 App처럼 자연스럽게 동작하는 사이트를 만들 수 있는데 PWA를 사용하면 이 웹 사이트를 모바일 앱처럼 발행해서 쓸 수 있다.

 

장점:

1. 스마트폰, 태블릿 바탕화면에 웹사이트를 설치 가능하다.

2. 오프라인에서도 동작할 수 있다.

3. 설치 유도 비용이 매우적다. (앱설치를 유도하는 마케팅 비용 -> 플레이스토어에서 설치하고 다운받는 과정)

 

npx create-react-app 프로젝트명 --template cra-template-pwa

PWA를 사용하려면 프로젝트를 생성할 때, 이런 방식으로 생성해야한다. (기존 프로젝트에서 변경 못함) 때문에 새로운 프로젝트를 만든 후 파일들을 옮겨야한다. (* index.js는 바뀐점이 있어서 차이점을 보고 옮겨야 한다.) 물론 라이브러리도 전부 설치해야한다.

 

index.js를 보면

serviceWorkerRegistration.unregister();

이 부분을

serviceWorkerRegistration.register();

이렇게 수정하면 된다.

 

// npm
npm run build

// yarn
yarn build

그리고 그 후에 빌드한다.

 

 

* manifest.json

{
  "version" : "여러분앱의 버전.. 예를 들면 1.12 이런거",
  "short_name" : "설치후 앱런처나 바탕화면에 표시할 짧은 12자 이름",
  "name" : "기본이름",
  "icons" : { 여러가지 사이즈별 아이콘 이미지 경로 },
  "start_url" : "앱아이콘 눌렀을 시 보여줄 메인페이지 경로",
  "display" : "standalone 아니면 fullscreen",
  "background_color" : "앱 처음 실행시 잠깐 뜨는 splashscreen의 배경색",
  "theme_color" : "상단 탭색상 등 원하는 테마색상",
}

웹앱의 아이콘, 이름, 테마색 등을 결정하는 파일이다. 때문에,

 

<link rel="manifest" href="/manifest.webmanifest">

파일이 추가되어야 하지만 이 부분은 리액트가 알아서 해준다.

 

 

* service-worker.js

앱을 설치하고나면 앱 구동에 필요한 이미지, 데이터들이 전부 하드에 설치가 되는데 그렇게되면 앱을 켰을 때 서버에 요청해서 가져오는게 아니라 이미 하드에 설치되어있는걸 그대로 가져와서 쓴다.

 

service-worker 파일은 이런 앱의 특성을 비슷하게 도와주는 파일이다. 이 파일 때문에 웹앱을 설치했을 때, CSS, JS, HTML, IMG 파일 등이 하드에 설치되게 결정 하고 앱을 켤때마다 설치되어 있는 곳(Cache Storage)에 저장되어있는 파일을 사용하게 된다. (오프라인에서도 사용이 가능하게 된다) 또, 자주 변해서 저장하기 싫은 파일들은 제외 시킬 수 있다.

 

 

* PWA 커스터마이징

node_modules/react-scripts/config/webpack.config.js
// 구
new WorkboxWebpackPlugin.GenerateSW({
    clientsClaim: true,
    exclude: [/\.map$/, /asset-manifest\.json$/],
}) 

// 신
new WorkboxWebpackPlugin.InjectManifest({
    swSrc,
    dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
    exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],

exclude 부분에 정규식으로 작성하면 그 정규식과 일치하는 파일명은 제외한다.

 

 

 

storage

DB가 아닌 브라우저에 정보를 저장할 수 있는데 그 때 쓰는 것이 storage이다. (localStorage, sessionStorage 등)

- localStorage: 브라우저의 어떤 공간에 데이터를 저장 할 수 있다.

- sessionStorage: 브라우저가 종료되기전까지 데이터를 저장 할 수 있다.

localStorage.setItem('데이터이름', '데이터'); // item 저장
localStorage.getItem('데이터이름'); // item 가져오기
localStorage.removeItem('데이터이름') // item 삭제

// sessionStorage도 마찬가지

object 자료형처럼 key: value 형태로 자료를 저장할 수 있다.

 

 

* JSON

localStorage.setItem('obj', JSON.stringify({name:'kim'}) );

storage에 저장되는 값들은 단순 텍스트 자료만 저장하기때문에 object나 array 자료형을 그냥 저장하면 데이터가 깨지게 된다. 때문에 JSON 형태로 저장해야하는데 자바스크립트에선 JSON.stringify 함수를 사용하면 JSON 형태로 데이터를 변환하여 저장할 수 있다.

 

var a = localStorage.getItem('obj');
var b = JSON.parse(a)

반대로 해당 데이터를 다시 꺼내면 JSON데이터 그대로 나오기 때문에 해당 자료형으로 사용하려면 다시 원 자료형으로 돌릴 때 JSON.parse 함수를 사용한다.

 

 

'React' 카테고리의 다른 글

[React] chap.2  (0) 2021.12.07
[React] chap.1  (0) 2021.12.03

[React] chap.2

 

 

 

기본 세팅

* yarn

- 구글에 yarn 1.22 검색 (2 버전은 아직 불안정)

https://classic.yarnpkg.com/en/docs/install#windows-stable

 

Yarn

Fast, reliable, and secure dependency management.

classic.yarnpkg.com

npm install --global yarn // 설치
yarn --version // 버전 체크
yarn start // 서버 시작

 

 

 

* Bootstrap

https://react-bootstrap.github.io/getting-started/introduction

 

React-Bootstrap

The most popular front-end framework, rebuilt for React.

react-bootstrap.github.io

npm install react-bootstrap bootstrap@5.1.3 // 리액트 부트스트랩 설치
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" />

css 링크도 받아온다.

 

 

 

폴더 경로

* src: (./ img, js, css, html 등의 아무파일)

* public: 17버전 이상 부터 풀 경로를 사용해야 함.

 

 

 

React Router
* npm
npm install react-router-dom@5

* yarn
yarn add react-router-dom@5

터미널 세팅

 

import 어쩌구 많은곳;

import { BrowserRouter } from 'react-router-dom';
// import {HashRouter} from 'react-router-dom';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
    <!-- {/* <HashRouter> */} -->
    <App />
    <!-- {/* </HashRouter> */} -->
    </BrowserRouter>
  </React.StrictMode>
  document.getElementById('root')
);

: index.js

 

HashRouter는 서버가 없고 리액트로 라우팅만 담당할 시에 잘못된 페이지를 서버에 요청할 경우 '#'을 붙여줘서 안정성 있게 사용할 수 있다.

 

 

* Route

import 많은 곳;
import { Link, Route, Switch } from 'react-router-dom';

function App(){
  return (
    <div>
      HTML 잔뜩있는 곳 

      <Route exact path="/">
        <div>메인 페이지입니다</div>
      </Route>

      <Route path="/detail">
        <div>상세페이지인데요</div>
      </Route>
      
      <!-- {/* <Route path="/어쩌구" component={Modal}></Route> */} -->
    </div>
  )
}

path: 해당 라우팅 페이지의 경로를 지정한다.

exact: 상위 경로의 내용이 밑에 하위경로까지 전달되는 것을 막는다. 

component: 해당 경로의 내용을 컴포넌트 단위로 가져와서 사용할 수 있다.

 

 

 

* Link

function App(){
  return (
    <div>
      <Navbar>
         <Nav.Link> <Link to="/">Home</Link> </Nav.Link>
         <Nav.Link> <Link to="/detail">Detail</Link> </Nav.Link>
      </Navbar> 
      <나머지HTML/>
    </div>
  )
}

to: Link 태그 안에서 to 속성을 통해 경로를 지정하면 a 태그와 유사하게 페이지 이동을 할 수 있다.

 

 

- 페이지 이동함수

import React from 'react';
import { useHistory } from 'react-router-dom';

function Detail(){
  
  let history = useHistory();
  return (
    <button className="btn btn-danger" onClick={ () => {
       // history.push('/');
        history.goBack();
     } }>뒤로가기</button> 
  )
};

export default Detail

router의 기능중에 useHistory라는 훅 기능을 사용하면 페이지의 이동 내역과 다른 함수가 있기 때문에 해당 페이지 이동을 할 수 있다. history 라는 object의 goBack함수를 사용하면 뒤로가기 기능을 구현할 수 있다. 다른페이지로 이동하려면 push()함수를 사용하면 된다.

 

 

* Switch

function App(){
  return (
    <div>
      <나머지HTML/>
      <Switch>
        <Route exact path="/">
          어쩌구
        </Route>
        <Route path="/detail">
          <Detail/>
        </Route>
      <Route path="/:id"> <!-- URL 파라미터 -->
          <div>새로 만든 route입니다</div>
        </Route>
      </Switch>
    </div>
  )
}

: 매치되는 Router를 전부 보여주지않고 한번에 하나만 볼때 사용

 

여러개의 Route가 매칭 되어 있을 때 같은 내용이 출력되었는데 위처럼 Switch로 묶으면 맨위의 Route 하나만 보여준다. (exact랑 비슷)

 

 

* URL 파라미터

URL뒤에 어떤 문자가 오던간에 해당 URL로 이동할 수 있다. (파라미터처럼 적용할 수 있음)

function App(){
  return (
    <div>
      <나머지HTML/>
        <Route path="/detail/:id"> <!-- URL 파라미터 -->
          <Detail shoes={shoes}/>
        </Route>
    </div>
  )
}

** 데이터는 항상 위에서 아래로 흐르는게 좋다 (상위에서 하위로)

 

import React from 'react';
import { useHistory, useParams } from 'react-router-dom';

function Detail(props){

  let { id } = useParams(); // destructuring
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">{props.shoes[:id자리에 있던숫자].title}</h4>
          <p>{props.shoes[:id자리에 있던숫자].content}</p>
          <p>{props.shoes[:id자리에 있던숫자].price}원</p>
          <button className="btn btn-danger">주문하기</button> 
        </div>
      </div>
  </div>  
  )
};

export default Detail

useParams(): 해당 함수는 현재 URL에 적힌 모든 파라미터를 object 자료로 저장해주는 함수이다.

 

 

 

 

 

styled-components

컴포넌트를 제작할 때, 스타일을 바로 입혀서 제작하는 방식. 컴포넌트가 많아졌을 때, css의 중복을 피하거나 css의 너무 길어질 때 사용한다.

// npm
npm install styled-components

// yarn
yarn add styled-components

 

import styled from 'styled-components'

: styled 임포트

 

import React, { useState } from 'react';
import styled from 'styled-components';

let 박스 = styled.div`
  padding : 20px;
`;
let 제목 = styled.h4`
  font-size : 25px;
  color : ${ props => props.색상 };
`;

function Detail(){
  return (
    <div>
      <HTML 많은 곳/>
      <박스>
        <제목 색상="blue">안녕하세요1</제목>
        <제목 색상={'red'}>안녕하세요2</제목>
      </박스>
    </div>
  )
}

컴포넌트를 제작할 때 미리 스타일을 주입해서 만들 수 있다.

  • styled.태그 형식으로 사용한다.
  • 태그 뒤에 `` (backtick) 기호를 사용해서 기본 스타일링을 한다.
  • 변수로 저장해서 원하는 자리에 사용할 수 있다.

 

 

- props 추가하기

color : ${ props => props.색상 };

만들었던 스타일의 속성 값으로 변수처럼 받아와서 사용할 수 있다. 위 처럼 콜백함수 형식으로 비슷한 형태의 스타일을 조금씩 다르게 정의 할 수 있다.

 

 

 

React SASS
// npm
npm install node-sass

// yarn
yarn add node-sass

해당 라이브러리가 scss 파일을 css 파일로 자동변환 해준다.

 

import './Detail.scss';

라이브러리를 설치했다면 scss 파일은 scss확장자로 import하면 된다.

 

 

- SASS

  • 1. 변수 사용
$메인칼라 : #ff0000;
  • 2. @import
@import './어쩌구.css'; /* css나 scss파일 간 import */
  • 3. nesting
div.container {
  h4 {
    color : blue;
  }
  p {
    color : green;
  }
}
  • 4. @extends
.my-alert {
  background : #eeeeee;
  padding : 15px;
  border-radius : 5px;
  max-width : 500px;
  width : 100%;
  margin : auto;
}
.my-alert2 {
  @extend .my-alert; /* 클래스간 extend*/
  background : yellow;
}

.my-alert p {
  margin-bottom : 0;
}
  • 5. @mixin / @include
@mixin 함수() {
  background : #eeeeee;
  padding : 15px;
  border-radius : 5px;
  max-width : 500px;
  width : 100%;
  margin : auto;
}
.my-alert {
  @include 함수()
}

.my-alert p {
  margin-bottom : 0;
}

 

 

 

Lifecycle Hook & useEffect

컴포넌트의 주기 중간중간에 Hook을 사용할 수 있다.

 

- Lifecycle Hook

class Detail2 extends React.Component {
  componentDidMount(){
    //Detail2 컴포넌트가 Mount 되고나서 실행할 코드
  }
  componentWillUnmount(){
    //Detail2 컴포넌트가 Unmount 되기전에 실행할 코드
  }
}

 

 

- useEffect Hook

import React, {useState, useEffect} from 'react';

function Detail(){

  useEffect(()=>{
    //코드를 적습니다 여기    
    
    return function 어쩌구(){ 
    	// unmount될 때 실행할 코드 
    }       
  });
  
  // useEffect(() => {});
  // useEffect(() => {}); 
  // ... 여러개 사용 가능  
  
  return (
    <HTML많은곳/>
  )
}

 

 

* useEffect 추가 기능 및 특징

useEffect는 해당하는 컴포넌트가 실행됐을 때, 사라질 때 기능을 추가 할 수 있는데, 그렇게 되면 useState에 등록된 값이 바뀔때마다 재렌더링 되어 원치 동작이 계속 일어날 수 있다.

 

useEffect(()=>{
   let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
}, []); // 초기 한번 실행빼고 다시 실행 안하기

useEffect 뒤에 빈 배열을 두면 초기 실행 한번 후 안할 수 있다.

 

useEffect(()=>{
   let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
}, [ alert ]); // alert라는 useState가 변경될 때만 실행

또, useEffect 뒤의 배열에 해당하는 useState 값을 넣으면 그 값이 변경 되어 재렌더링 될때 다시 실행한다. (배열 형식이라 콤마를 통해 여러개 넣을 수 있다)

 

 

- setTimeout

useEffect(()=>{
  let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);

  return ()=>{ clearTimeout(타이머) }
}, []);

추가적으로 useEffect의 return은 해당 컴포넌트가 사라질 때 동작하게 되는데, setTimeout 함수를 썼으면 해제하는 clearTimeout 함수로 해제하는게 버그에 대해 안전하다.

 

 

 

Ajax

서버에 새로고침없이 요청을 할 수 있게 도와주는 일종의 자바스크립트 코드 (GET, POST)

  • jQuery Ajax
  • react axios
  • javascript fetch

 

 

* axios

// npm
npm install axios

// yarn
yarn add axios

라이브러리 다운

 

 

- get 요청

import axios from 'axios';

function App(){
  
  return (
    <HTML많은곳/>
    <button className="btn btn-primary" onClick={()=>{

      <!-- fetch('https://codingapple1.github.io/shop/data2.json') -->
      <!-- .then() -->

      axios.get('요청 url')
      .then(()=>{ 요청성공시실행할코드 })
      .catch(()=>{ 요청실패시실행할코드 })

    }}>더보기</button>
  )
}

 

리액트에선 ajax를 사용할 때, 문법도 비슷한 axios와 fetch를 자주 쓴다.

 

axios와 fetch의 차이는 서버에서 데이터를 받아 올때, json형태(object와 비슷하지만 key 값이 문자열 처리 되어있음)로 받아와야 안전하게 전달할 수 있는데 axios 같은 경우는 json 데이터를 가져와서 object형태로 알아서 파싱해주지만 fetch의 경우는 json으로 받은 데이터를 object로 파싱하는 작업을 해야한다.

 

 

- post 요청

axios.post('URL', { id : 'test', pw : 1234})
  .then((result)=>{  })
  .catch(()=>{ })

get과 마찬가지로 요청 성공/실패의 경우 then, catch로 구분할 수 있으며 라이브러리에 header나 cookie 등의 정보도 보낼 수 있다.

 

 

 

'React' 카테고리의 다른 글

[React] chap.3  (0) 2021.12.14
[React] chap.1  (0) 2021.12.03

[React] chap.1

 

 

 

React 설치 및 개발환경 세팅

1. node.js 설치

2. 작업하려는 폴더에서 npx create-react-app 작업폴더명

3. npm start로 웹 미리보기 (작업폴더로 경로변경 후 시작)

 

 

 

JSX
function App(){
  return (
    <div className="App">
      <div>개발 blog</div>      
    </div>
  )
}
.black-nav {
  background : black;
  width : 100%;
  display : flex;
  color : white;
  padding : 20px;
  font-weight : 600;
  font-size : 20px;
}

 

 

- 여러가지 문법

function App() {

  let posts = {color: 'blue', fontSize: '30px'};
  function 함수(){
    return 100
  }

  return (
    <div className="App">
      <div className="black-nav">
        <div style={ {posts} }>개발 Blog</div>
      </div>
      <img src={ logo }/>
      <h4>{ 함수() }</h4>
    </div>
  );
}

class = className (class 예약어 때문에)

css style = object 형태로 카멜케이스(fontColor)

데이터 바인딩 = {} (함수도 가능)

 

 

 

State

변수 말고도 데이터바인딩과 관련된 중요한 데이터를 저장할 때 쓰는 것.

function App(){
 
  let posts = '강남 고기 맛집';
  return (
    <div className="App">
      <div className="black-nav">
        <div>개발 blog</div>
      </div>
      <div className="list">
        <h3>{ posts }</h3>
        <p>2월 17일 발행</p>
        <hr/>
      </div>
    </div>
  )
}
.list {
  margin-top : 30px;
  text-align : left;
  padding-left : 20px;
  padding-right : 20px;
}

 

 

- state 사용

import React, { useState } from 'react';
import './App.css'

function App(){
 
  // destructuring 문법
  let [글제목, 글제목변경] = useState('남자 코트 추천'); // [a, b] => [남자 코트 추천, 함수]
  // let [글제목2, 글제목변경2] = useState(['남자 코트 추천2', '강남 우동 맛집']);
  let posts = '강남 고기 맛집';
  
  return (
    <div className="App">
      <div className="black-nav">
        <div>개발 blog</div>
      </div>
      <div className="list">
        <h3>{ 글제목 }</h3> <!-- 남자 코트 추천 -->
        <p>2월 17일 발행</p>
        <hr/>
      </div>
    </div>
  )
}

기존의 변수말고도 state로 데이터 바인딩이 가능하다. Array, Object 등 아무 자료형의 데이터도 넣을 수 있다.

 

 

* State를 쓰는 이유

변수가 변경될 때 자동으로 관련된 HTML을 재렌더링되게 만들려면 state에 저장해서 데이터 바인딩한다. 글 제목을 수정하거나 할때 새로고침 없이 웹페이지를 스무스하게 반영하고 싶으면 state를 사용한다. 리액트는 state가 수정되면 state가 포함된 HTML을 자동으로 재렌더링 해준다.

 

 

- JSX 기능 개발

<!-- HTML -->
<div onclick="실행할 자바스크립트">

<!-- JSX --> 
<div onClick={실행할 함수}>
<!-- ex) --> 
<div onClick={ showModal }>  (showModal은 어디 다른데 만들어둔 함수 이름)
<div onClick={ function(){ 실행할 코드 } }>
<div onClick={ () => { 실행할 코드 } }>

1. onClick (C가 대문자)

2. {} 중괄호를 사용한다.

3. 코드가 아니라 함수가 필요하다 (callback 함수 처럼 사용)

 

 

- state 변경 함수

let [ 따봉, 따봉변경 ] = useState(0); // 변수와 변수변경 함수

function App(){
  
  let [ 따봉, 따봉변경 ] = useState(0);
  return (
    <HTML 많은 곳>
      <!-- 콜백함수로 변경함수 사용 -->
      <h3> { 글제목[0] } <span onClick={ ()=>{ 따봉변경(따봉 + 1) } } >👍</span> { 따봉 }</h3>
    </HTML 많은 곳>
  )
}

1. JSX에서는 이벤트로 함수를 넣을 때 콜백 함수처럼 넣는다.

2. state는 값을 변경할 때 지정된 변경함수를 써야한다.

3. 변경함수는 데이터를 완전히 대체한다. ( 따봉 = 따봉 + 1 이런거 안됌)

 

 

function App() {
  let [글제목, 글제목변경] = useState(['남자 코트 추천', '강남 우동 맛집', '파이썬독학']);

  function 제목바꾸기(){
    // 글제목[0] = '여자코트추천' => 이런거 안됌
    var newArray = [...글제목];
    newArray[0] = '여자 코트 추천';
    글제목변경(newArray);
  }

  return (
    <div className="App">
      <button onClick={ 제목바꾸기 }>버튼</button>
      <div className="list">
        <h3>{ 글제목[0] } <span onClick={ () => {따봉변경(따봉 + 1)} }>👍</span>{ 따봉 }</h3>
        <p>2월 17일 발행</p>
        <hr/>
      </div>
    </div>
  );
}

1. onClick에는 함수만 들어갈 수 있으며 정의된 함수를 파라미터로 넣을땐 소괄호 {}는 넣지 않는다.

2. 제목 바꾸기라는 함수를 만들고 해당 로직을 입력하는데 state는 개별 조작할 수 없다.

3. 데이터를 바꾸기위해선 전체 state를 바꿔야하며 바꿀땐 state 변경함수를 쓴다.

4. 참조 타입의 자료형을 복사할 땐 꼭 원본은 놔두고 새로운 변수에 저장하며 [...xxx]식으로 딥 카피한다.

 

 

 

 

React Component

하나의 태그 그룹들을 하나의 태그로 만들고 싶을 때 사용한다.

<div className="modal">
  <h2>제목</h2>
  <p>날짜</p>
  <p>상세내용</p>
</div>
.modal{
  margin-top : 20px;
  padding : 20px;
  background : #eee;
}

 

위의 모달창을 하나의 태그로 관리 할 수 있다.

 

function App (){
  return (
    <div>
      HTML 잔뜩있는 곳
      ...
      <Modal></Modal>
    </div>
  )
}

function Modal(){ // 대문자로 사용해야 함
  return (
    <div className="modal">
      <h2>제목</h2>
      <p>날짜</p>
      <p>상세내용</p>
    </div>
  )
}

**return안에 태그를 2개 사용할 수 없다. (하나의 태그로 감싸서 사용)

 

 

:특징

1. 이름을 지을땐 영어 대문자로 시작한다.

2. return안에 태그가 평행하게 여러개 들어갈 수 없다. (<> </>를 사용하면 되긴 함)

3. function App과 동일한 위치에 생성한다.

4. 컴포넌트 안에 다른 컴포넌트를 주입할 수도 있다.

function Modal(){
  return (
    <div className="modal">
      <h2>제목</h2>
      <p>날짜</p>
      <p>상세내용</p>
      <다른컴포넌트></다른컴포넌트>
    </div>
  )
}
function 다른컴포넌트(){
  return ( <div> 안녕 </div> )
}

 

 

- Component 생성 관습

  • 반복해서 출현하는 HTML 덩어리
  • 내용이 자주 변경되는 HTML 부분
  • 다른페이지에 들어갈 내용
  • 협업할 때 ? 컴포넌트 단위

 

 

:단점

  • 많으면 관리가 힘들다
  • 다른 component 안에 있는 state를 사용할 때 바로 쓸 수 없다. (props 문법 사용해야함)

 

 

 

React에서 반복문 쓰기

* map

JSX에서는 중간중간에 중괄호 {} 안에 변수명을 넣을 수는 있으나, if나 for을 사용할 수 없다.

  let [글제목, 글제목변경] = useState(['남자 코트 추천', '강남 우동 맛집', '파이썬독학']);
  let [따봉, 따봉변경] = useState(0);

function App (){
  return (
    <div>
      HTML 잔뜩있는 곳
      ...
      { 글제목.map(function(글, i){
          return <div className="list" key={i}>
                    <h3 onClick={ ()=>{ 따봉변경(따봉+1) } }>{ 글 }</h3>
                    <p>2월 17일 발행</p>
                    <hr/>
                  </div>; <!-- ''가 없어도 알아서 리액트는 렌더링해줌 -->
      }) }
    </div>
  )
}

** 반복문 사용시 key를 정해줘야한다. key는 1,2,3,4.... 등의 순차적으로 올라가는 인덱스 같은것을 지정한다.

 

 

- 일반 for 반복문

function App (){

  function 반복된UI(){
    var 어레이 = [];
    for (var i = 0; i < 3; i++) {
      어레이.push(<div>안녕</div>) // ''없어도 리액트가 알아서 잘 렌더링 해줌
    }
    return 어레이
  }
  return (
    <div>
      HTML 잔뜩있는 곳
      
      { 반복된UI() }
    </div>
  )
}

1. 일반함수를 만들고 HTML을 담을 array 자료를 생성한다.

2. 함수 안에서 for반복문을 이용해 array에 HTML을 추가하고 완성된 array를 return 한다.

3. 원하는 곳에 { 함수() }식으로 데이터바인딩 한다.

 

 

 

props

자식 컴포넌트(Modal)가 부모 컴포넌트(App)의 state를 갖다 쓰고 싶을 때 사용한다.

function App (){
  let [글제목, 글제목변경] = useState(['남자코트 추천', '강남 우동맛집', '파이썬독학']);
  return (
    <div>
      ...
      <Modal 글제목={글제목}></Modal> <!-- 자식컴포넌트 전송할이름={state명} -->
    </div>
  )
}

function Modal(props){ // 전달받을 state를 받는 파라미터
  return (
    <div className="modal">
      <h2>제목 { props.글제목[0] }</h2>
      <p>날짜</p>
      <p>상세내용</p>
    </div>
  )
}

1. props는 <Modal 이런거={이런거}  저런거={저런거}> 방식으로 여러개 전달 가능

2. props를 전송할때 형식은 여러개이다. <Modal 글제목={변수명}> 변수 전달은 중괄호 {}, <Modal 글제목="강남우동맛집"> 텍스트 전달은 따옴표도 가능하다.

 

 

 

 

input
function App (){

  let [입력값, 입력값변경] = useState('');

  return (
    <div>
      HTML 잔뜩있는 곳...
      <input onChange={ (e) => { 입력값변경(e.target.value) } }/>
      {입력값}
    </div>
  )
}

input에 입력한 데이터는 중요한 데이터이기 때문에 state에 저장하여 사용하는게 일반적이다. 리액트에서는 자바스크립트와 다르게 input 이벤트리스너의 onChange와 onInput이 같게 작용한다. (input처럼) 때문에 둘중 하나를 사용하면되고, 이벤트리스너처럼 함수의 e.target등을 사용할 수 있다.

 

 

 

class
function App(){
  return (
    <div>
      HTML 잔뜩있는 곳
      <Profile />
    </div>
  )
}


class Profile extends React.Component { // 리액트 내장 클래스 상속
  constructor(){
    super(); // 상속 받은것 사용하기 위함
    this.state = { name: 'Kim', age: 30}
  }

  changeName = function() { // this 속성에따라 arrow나 function 사용
    this.setState( {name: 'Park'} )
  }

  render(){
    return (
      <div>
        <h3>프로필입니다</h3>
        <p>저는 { this.state.name }입니다.</p>
        <button onClick={ this.changeName.bind(this) }>버튼</button> <!-- this에 따라 바뀜 -->
      </div>
    )
  }
}

state를 변경할 때, this.setState()라는 내장함수를 꼭 사용해야 한다. 신 문법에서 쓰는 useState와 다른점은 useState는 변경 시 아예 state 자체를 바꾸는건데 예전 문법의 setState()는 필요한 부분만 수정한다.

 

onClick에 들어가는 로직이 길어져서 따로 함수로 빼서 쓰면 에러가 난다.

this.setState()라는 코드의 this가 함수(function)안에서 쓰면 값이 새롭게 재정의 되기때문에 arrow function()을 사용하거나 this.changeName.bind(this) 이런 식으로 사용해야 한다.

 

 

 

'React' 카테고리의 다른 글

[React] chap.3  (0) 2021.12.14
[React] chap.2  (0) 2021.12.07

[JavaScript] ES6 chap.3

 

 

 

Destructuring

* Array

// Array
var array = [2,3,4];
var a = array[0]; 
var b = array[1];
var c = array[2];

// ==
var [a,b,c] = [2,3,4];

// default 값
var [a,b,c = 5] = [2,3];

 

 

* Object

var { name : a, age : b } = { name : 'Kim', age : 30 }; // { a: 'Kim', b: 30 }

// key 생략
var { name, age } = { name : 'Kim', age : 30 }; // { name: 'Kim', age: 30 }

 

- 변수를 Object에 넣기

var name = 'Kim';
var age = 30;

var obj = { name : name, age : age } // { name: 'Kim', age: 30 }
// == //
var obj = { name, age } // { name: 'Kim', age: 30 }

 

 

* 함수

- Object

function 함수(name, age){
  console.log(name);
  console.log(age);
}

var obj = { name : 'Kim', age : 20 }
함수(obj.name, obj.age);


// destructuring
function 함수( { name, age }){
  console.log(name);
  console.log(age);
}

var obj = { name : 'Kim', age : 20 };
함수(obj);

 

 

- Array

function 함수( name, age ){
  console.log(name);
  console.log(age);
}

var array = [ 'Kim', 30 ];
함수(array[0], array[1]);


// destructuring
function 함수( [name, age] ){
  console.log(name);
  console.log(age);
}

var array = [ 'Kim', 30 ];
함수( ['Kim', 30] );

 

 

 

import & export

* default export

// import할 곳
<script type="module">
  import a from 'library.js';
  console.log(a);
</script>


// export할 곳
var a = 10;
export default a;

- export default는 한번만 사용이 가능하다.

- import 시에 변수명을 새롭게 작명이 가능하다.

 

 

* export

// import할 곳
<script type="module">
  import {a,b} from 'library.js';
  console.log(a);
</script>

// export할 곳
var a = 10;
var b = 20;
export {a, b};

- export는 {변수명1, 변수명2} 이렇게 사용해야하고 import 할때도 정확한 변수명을 써줘야한다.

 

 

* default export & export 동시사용

// import할 곳
<script type="module">
  import c, {a,b} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(c);
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

** default export로 설정한 변수가 export보다 먼저 와야한다.

 

 

* 알리아스(as)

// import할 곳
<script type="module">
  import c as 폭풍, {a as 간지, b as 폭발} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(폭풍); // 30
  console.log(간지); // 10
  console.log(폭발); // 20
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

as라는 키워드를 통해 새로운 변수명을 지정할 수 있다.

 

 

* 아스테리스크(*)

// import할 곳
<script type="module">
  import c, {* as 변수모음} from 'library.js'; // 순서는 default가 먼저 온다.
  console.log(a); // 10
  console.log(c); // 30
</script>

// export할 곳
var a = 10;
var b = 20;
var c = 30;
export {a, b};
export default c;

export 했던 모든 변수들을 지정할 수 있다.

** 그냥쓰면 안되고 꼭 as를 통해 변수명을 새로 지정해주어야 한다.

 

 

 

Stack & Queue

* Stack과 Queue의 동작

브라우저는 C++ 기반으로 짜여져 자바스크립트 코드를 발견하면 stack에 넣어 돌린다. 동기식 처리로 맨 윗줄부터 하나하나 실행시키는 공간이다. 하지만 ajax 요청이나 이벤트리스너, setTimeout 등의 코드를 만나게되면 처리하기까지의 시간이 오래 걸리기 때문에 Stack에서 제외하여 잠시 보류시킨다. 보류시키는 동안에 Stack에 있는 다른 처리들을 먼저 처리하고 보류되어있던 코드들은 실행 시점이 다 되었을 때 Queue에 집어 넣고 Queue에 있는 코드를 Stack에 옮겨서 처리하는데 Stack이 비어있을 때만 차례로 집어넣어 처리한다.

 

때문에 자바스크립트의 스택에 너무 무겁거나 많은 데이터가 쌓여있으면 Queue를 통해서 오는 ajax 요청이나 이벤트리스너 setTimeout 등의 코드를 실행 할 수 없다. (Stack이 10초간 실행중이면 해당 코드들은 실행 시점이 와도 실행이 되지 않음)

 

프로그램을 무겁게 돌려야 할 시

1. setTimeout을 통해 분할해서 실행한다.

2. Web worker를 이용한다.

 

 

 

 

 

Synchronous / Asynchronous

* Synchronous

- 동기식 처리: 한번에 코드 한줄씩 차례차례 실행

<script>
  console.log(1); 
  console.log(2); 
  console.log(3);
  // 1 2 3
</script>

 

 

- 비동기식 처리: 먼저 시작된 코드와 상관없이 새로운 코드를 실행

// python
print(1)
print(2)
time.sleep(1)
print(3)
// 1 2 1초쉬고 3

<script>

  console.log(1);
  setTimeout(function(){console.log(2);}, 1000);
  console.log(3);
  // 1 3 1초쉬고 2

</script>

 

 

브라우저는 비동기처리를 하는 코드들을 제쳐두고 원래 코드부터 실행한다. (ex. ajax, setTimeout, addEventListener)

 

코드를 실행하다가 ex에 해당하는 코드를 보게되면 Web API 대기실로 옮겨서 대기 시키다가 실행시점이 도달했을 때 코드를 꺼내서 실행시킨다. 때문에 시간이 오래 걸리는 코드들을 비동기식으로 처리할 수 있다.

 

 

* 콜백함수

- 함수 안에 들어가는 함수. 비동기 상황 등에서 순차적으로 실행하고 싶을 때 사용한다. 어떤 이벤트가 발생했거나 특정시점에 도달했을 때 시스템에서 호출.

function 첫째함수(){
  console.log(1)
}

function 둘째함수(){
  console.log(2)
}

function 셋째함수(){
  console.log(3);
}

첫째함수(); // 비동기 시 실행순서 보장 못함
둘째함수(); 


// 콜백함수
function 첫째함수(콜백){
  console.log(1);  
  콜백();
}

function 둘째함수(콜백){
  console.log(2);
  콜백();
}

function 셋째함수(){
  console.log(3);
}

첫째함수(function(){
  둘째함수(function(){
    셋째함수(function(){
      ...
    });
  });
}); // 1 2 3

다른 프로그래밍 언어라면 순차적으로 출력되겠지만 자바스크립트의 경우는 비동기식 처리 함수가 있을 시 Web API 대기실로 보내져서 나중에 실행된다.

 

 

- callback hell

첫째함수(function(){
  둘째함수(function(){
    셋째함수(function(){
      어쩌구..
    });
  });
}):

 

 

 

Promise

- 콜백함수의 한 디자인 패턴 (성공&실패 판정)

var 프로미스 = new Promise(function(성공, 실패){
  var 연산 = 1 + 1;
  성공(연산);
});

프로미스.then(function(결과){
  // Promise 성공 시 실행할 코드
  // 성공 파라미터를 통해 결과 값을 받아올 수 있음
}).catch(function(){
  // Promise 실패 시 실행할 코드
}).finally(function(){
  // Promise 결과와 상관없이 실행할 코드
});

성공하면 then(), 실패하면 catch()를 실행하는 코드를 짤 수 있다.

 

: 장점

 - 콜백함수 사용시 코드가 옆으로 길어지지 않아 보기 직관적이다.

 - 성공뿐만 아니라 실패 또는 결과와 상관없이 실행할 코드를 지정할 수 있다. (catch, finally)

 

 

* Promise 특징

1. Promise를 선언한 변수를 출력하면 대기<pending>, 성공<resolved>, 실패<rejected>의 3단계로 나뉜다. (한번 정해진 상태를 되돌릴 수는 없다.)

 

2. 동기를 비동기로 만드는 것이 아니라 콜백 함수를 지정하는 하나의 디자인 패턴이다. (Promise 안에 어려운 연산을 시키면 브라우저가 멈춤)

 

 

 

async / await

- Promise와 then을 쉽게 쓸수 있게 만들어주는 ES8 문법

 

* async

async function 연산 (){ // 이 함수 자체가 Promise
  return 1 + 1;
}

연산().then(function(결과){ // then을 사용할 수 있음
  console.log(결과); // 2
})

Promise를 알아서 생성해주기 때문에 함수를 실행할 때 then을 붙일 수 있다. 또한 async 함수 안에서 나온 결과를 then 안에서 파라미터로 받아서 사용할 수 있다.

 

 

* await

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    var 결과 = 1 + 1;
    성공();
  });
  어려운연산.then(function(result){
    console.log(result);
  });
}
더하기();

/////////// 같은 결과 /////////////

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    var 결과 = 1 + 1;
    성공();
  });
  var 결과 = await 어려운연산;
  console.log(결과);
}
더하기();

////////// async만 사용 했을 때 //////////

async function 더하기(){
  async function 어려운연산(){                
    return 1 + 1;
  }
  var result = await 어려운연산();
  console.log(result);
}
더하기();

단순히 then 대신에 await을 사용했다. await을 사용하면 문법이 훨씬 더 간단하다. 어려운연산 Promise를 기다린 다음에 완료되면 결과를 변수에 담는다. ( ** 비동기식 처리되는 코드를 담는다면 await을 기다리는 동안 브라우저가 잠깐 멈출수 있다.)

 

 

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  try {  
    var 결과 = await 어려운연산 // 성공했을 시 실행할 코드
  }
  catch { 
    // 어려운연산 Promise가 실패할 경우 실행할 코드 
  }
}

await은 Promise가 실패하면 에러가 나고 코드가 멈추기때문에 하단의 코드들은 더 이상 실행되지 않지만 그것을 방지하기 위해 try{} catch{}를 실행하면 된다. try 문에 성공했을 시 수행할 코드, catch 문엔 실패했을 시 수행할 코드를 넣으면 된다.

 

 

 

반복문
  • for 반복문 (공통)
  • forEach() 반복문 (Array 전용)
  • for in 반복문 (Object 전용)
  • for of 반복문 (iterable 전용)

 

* for in 반복문

- Object 자료형에 저장된 자료들을 꺼내고 싶을 때 사용

var 오브젝트 = { name : 'Kim', age : 30 };

for (var key in 오브젝트) { // 오브젝트의 자료 2개 = 2번 반복
  console.log(오브젝트[key]); // key는 오브젝트의 key
}

: 특징

 1. enumerable한 것만 출력한다.

var 오브젝트 = { name : 'Kim', age : 30 };

console.log( Object.getOwnPropertyDescriptor(오브젝트, 'name') ); // 오브젝트 속성
// {value: "Kim", writable: true, enumerable: true, configurable: true}
// enumberable은 셀수 있는지의 여부인데 object는 생성 시 true로 되어있음.

enumerable이 true로 체크 되어있어야 for in 문에서 거르지 않는다.

 

 

2. 부모의 prototype에 저장된 것도 출력한다.

class 부모 {

}
부모.prototype.name = 'Park';

var 오브젝트 = new 부모();

for (var key in 오브젝트) {
  console.log(오브젝트[key]); // Park
}

부모가 가지고 있는 prototype도 출력한다.

 

class 부모 {
 
}
부모.prototype.name = 'Park';

var 오브젝트 = new 부모();

for (var key in 오브젝트) {
  if (오브젝트.hasOwnProperty(key)) {
    console.log(오브젝트[key]);
  }
}

해당 오브젝트에 hasOwnProperty() 함수를 사용하면 오브젝트가 직접 가지고 있는지 true or false로 체크 하여 사용할 수 있다.

 

 

* for of 반복문

- 여러가지 자료형에 사용할 수 있는 반복문 (Array, 문자, arguments, NodeList, Map, Set 등)

var 어레이 = [2,3,4,5];
for (var 자료 of 어레이) {
  console.log(자료);
}

: 특징

1. iterable한 자료형들에만 사용 가능하다.

var 어레이 = [2,3,4,5];
console.log( 어레이[Symbol.iterator]() ); // ~~~ iterator {} 속성이 있음
// 위의 방법으로 콘솔 출력시 iterator속성이 있으면 iterable하다고 한다.

 

 

 

 

 

Symbol
var 심볼 = Symbol('설명아무거나적기');

- Object 자료형에 비밀스러운 key값을 부여하고싶을 때 사용한다.

 

 

var person = { name : 'Kim' };
person.weight = 100;

var weight = Symbol('내 진짜 몸무게');
person[weight] = 200;

console.log(person);

: 특징

- for문에 등장하지 않는다.

- 특이한 이름을 가진 자료를 Object안에 만들고 싶을 때 사용한다.

 

// 직접 입력
var height = Symbol('내 키임');
var person = { name : 'Kim', [height] : 160 };


// 심볼은 값은 같아도 서로 다른 Symbol
var a = Symbol('설명1');
var b = Symbol('설명1');
console.log(a === b); // false


// 전역 심볼
var a = Symbol.for('설명1');
var b = Symbol.for('설명1');
console.log(a === b); // true


// 기본 내장 Symbol
ar array = [2,3,4];
console.log(array[Symbol.iterator]); // Array iterator {}...

 

 

 

 

Map & Set

* Map

- 자료의 연관성을 위해 사용한다.

var person = new Map();
person.set('name', 'Kim');
person.set([1,2,3], 'Kim'); // key 값에 어떤 자료도 가능하다
person.set('age', 20);
// Map {"name" => "Kim", "age" => 20}

 

 

- 사용법

var person = new Map();
person.set('age', 20);

person.get('age'); //자료 꺼내는 법
person.delete('age'); //자료 삭제하는 법
person.size; //자료 몇갠지 알려줌

//Map자료 반복문 돌리기
for (var key of person.keys() ){
  console.log(key)
}

//자료를 직접 집어넣고 싶으면

var person = new Map([
  ['age', 20],
  ['name', 'Kim']
]);

Array 같은 자료형에 대량의 데이터가 있을시 Hash Map, Hash Table 등을 사용한다. Array 같은경우 필요한 데이터를 사용 시에 반복문을 돌려 일일이 출력해봐야 아는데, Hash Table은 미리 abc순으로 정렬되어 있어서 빠르게 찾을 수 있다. Hash Table은 자료에 key 값을 부여해놓고 정렬하는 식으로 구성되어있다. Object 자료형이랑 비슷하다. Object는 hasOwnProperty, toString 키들도 집어넣을수 있어서 너무 유연하고, key 값이 문자형태로 제한되어 들어올 수 밖에 없는 차이가 있다.

 

 

* Set

- 자료를 Array처럼 일렬로 저장할 수 있다. (***중복 제거)

var 출석부2 = new Set([ 'john' , 'tom', 'andy', 'tom' ]);

console.log(출석부2); // Set { "jhon", "tom", "andy"}

Set은 중복된 자료를 허용하지 않는다.

 

 

var 출석부2 = new Set([ 'john' , 'tom', 'andy', 'tom' ]);

출석부2.add('sally'); //자료더하기 
출석부2.has('tom'); //자료있는지 확인
출석부2.size;  //자료 몇갠지 세기

var 출석부 = [ 'john' , 'tom', 'andy', 'tom' ];

var 출석부2 = new Set(출석부); //Array를 Set으로 바꾸기

출석부 = [...출석부2]  //Set을 Array로 바꾸기

 

 

 

Web Components

- 여러개의 태그들을 하나의 단어로 축약해서 사용할 수 있다

<custom-input name="이름"></custom-input>
<custom-input name="비번"></custom-input>

<script>
  class 클래스 extends HTMLElement {

      connectedCallback() { // html 태그 사용 할 부분
        let name = this.getAttribute('name'); // attribute 추가 해서 사용 가능
        this.innerHTML = '<label>${name}을 입력하쇼</label><input>'
      }
      
      static get observedAttributes() {
        return ['name'] // 여러개면 [ 속성, 속성...] 이런식으로 사용
      }
      
      attributeChangedCallback() {
        // attribute 변경시 실행할 코드
      }
  }

  customElements.define("custom-input", 클래스); // 컴포넌트 등록
</script>

<custom-input> 같은 커스텀 태그를 컴포넌트라고 한다. 해당 기능은 React나 Vue에서 제공하는 자동 html 재렌더링 기능처럼 구현한 것이다. 

 

 

* Shadow DOM과 template 모듈화

- Shadow DOM

<div class="mordor"></div>
<script>
  document.querySelector('mordor').attachShadow({mode : 'open'}); // shadow 공간 만들기
  document.querySelector('mordor').shadowRoot.innerHTML = '<p>심연에서왔도다</p>' // html 사용
</script>

해당 방법으로 shadow DOM을 만들 수 있고 shadow DOM은 web component와 사용시 유용하다.

 

 

: web component 문제점

<custom-input></custom-input>
<label>왜 나까지 빨개짐?</label>

<script>

  class 클래스 extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `<label>이름을 입력하쇼</label><input>
        <style> label { color : red } </style>`
    }
  }

  customElements.define("custom-input", 클래스);
  
</script>

컴포넌트화할 때 스타일까지 포함하는 경우가 많은데 해당 컴포넌트를 제외한 다른 요소들까지 같이 적용될 수가 있다. 이럴때 스타일을 shadow DOM을 열어서 집어넣게 되면 shadow DOM안에 있는 스타일은 밖에까지 영향을 끼치지 않기때문에 html의 모듈화 개발이 가능하다.

 

 

- template 태그

해당 태그에 적힌 html은 렌더링 되지 않는다.

 <custom-input></custom-input>

  <template id="template1">
    <label>이메일을 입력하쇼</label><input>
    <style>label { color : red }</style>
  </template>

  <script>
    class 클래스 extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode : 'open'});
        this.shadowRoot.append(template1.content.cloneNode(true));
        let el = this.shadowRoot.querySelector('label');
        el.addEventListener('click', function(){ // 이벤트리스너 사용
          console.log('클릭함')
        })
      }
    }
    customElements.define("custom-input", 클래스);
  </script>

컴포넌트를 만들 때 html이 너무 길어지면 가독성이 떨어지니 template태그에 내용을 보관해두고 shodowRoot를 통해 집어 넣을 수 있다.

 

 

 

[JavaScript] ES6 chap.2

 

 

 

default parameter & arguments

- default parameter

function 더하기 (a, b = 10){ // b에 default 값을 부여한다. 연산이나 함수 사용 가능
  console.log(a + b)
}

더하기(1); // 11
더하기(1, 3); // 4

 

- arguments

function 함수(a,b,c){
  console.log(arguments);
}

함수(2,3,4); // [2, 3, 4]....arguments
function 함수(a,b,c){
  for(var i=0; i<arguments.length; i++){
  	console.log(arguments[i]); // 반복문으로 함수의 파라미터 조회
  }
}

함수(2,3,4);

 

 

 

Rest Parameter

ES6에 추가된 arguments랑 비슷한 용도의 파라미터 (나머지 파라미터)

function 함수2(...파라미터들){
  console.log(파라미터들)
}

함수2(1,2,3,4,5,6,7); // [1, 2, 3, 4, 5, 6, 7]

arguments와의 차이점은 arguments는 함수를 사용할 때의 파라미터의 갯수가 변경되면 기존 함수의 파라미터 갯수도 변경 해야하지만 rest parameter는 유동적으로 변경 가능하다.

 

function 함수2(a, b, ...파라미터들){
  console.log(파라미터들)
}

함수2(1,2,3,4,5,6,7); // [3, 4, 5, 6, 7]



// 주의
function 함수2(a, ...파라미터들, b){
  console.log(파라미터들)
} // ...파라미터 뒤에 또 다른 파라미터가 오면 에러 발생

function 함수2(a, ...파라미터들, ...파라미터들2){
  console.log(파라미터들)
} // rest 파라미터를 중복으로 써도 에러 발생

 

 

 

Primitive data type / Reference data type

* primitive data type

- 자료 자체가 변수에 저장되는 자료 (문자, 숫자, 문자열 등)

var 이름1 = '김';
var 이름2 = 이름1;
이름1 = '박';
console.log(이름1); // 박
console.log(이름2); // 김

 

 

* reference data type

- 자료가 변수가 아닌 컴퓨터 메모리에 저장되고 참조하여 사용하는 자료 (Array, Object 등)

var 이름1 = { name: '김'};
var 이름2 = 이름1;
이름1.name = '박';
console.log(이름1.name); // 박
console.log(이름2.name); // 박

primitive 타입과 달리 reference 타입은 데이터가 저장된 것이 아니라 참조한다는 값을 가리키는 값이 저장된 것이기 때문에 참조되는 데이터를 변경하게 되면 두 변수가 가리키는 값은 같다.

 

 

- cf

var 이름1 = { name : '김' };
var 이름2 = { name : '김' };

console.log(이름1 == 이름2); // false
console.log(이름1 === 이름2); // false
console.log(이름1.name == 이름2.name); // true

위 처럼 새로운 {}를 할당할 때, 새로운 object가 생성되기 때문에 {} 안에 있는 오브젝트 데이터는 같지만 서로 다른 object이며 다른 object를 각각 가리키고 있는 것이다.

 

var 이름1 = { name : '김' };

function 변경(obj){
  obj = { name : 'park' }; // obj라는 변수에 새로운 {} object 재할당
  obj.name = 'park' // 파라미터로 들어온 object의 name의 value 변경
}

변경(이름1);

 

 

 

constructor

기존의 object 생성 방식

var 학생1 = { name : 'Kim', age : 15 };
var 학생2 = { name : 'Park', age : 15 };
...

여러번 선언하고 정의하거나 복사를 해야하는데 복사하는데 있어서 데이터가 같은 값을 참조 할 수 있기 때문에 constructor 방식으로 object를 생성할 수 있다.

 

 

* constructor

function 기계(이름){ // 기계: constructor
  this.name = 이름; // instance
  this.age = 15; // instance
  this.sayHi = function(){ // instance
    console.log('안녕하세요' + this.name + ' 입니다');
  }
}
var 학생1 = new 기계('Park');
var 학생2 = new 기계('Kim');

function이라는 함수를 정의하고 해당 함수를 new 함수() 형태로 사용한다. 어떤 변수에 기계라는 함수를 new 기계()로 할당하게되면 기계는 constructor가 되며 기계() 안에 있는 this는 instance가 되어 여러가지의 object를 생성할 수 있다.

 

 

 

prototype

prototype = 유전자의 역할

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}
var 학생1 = new 기계(); // 상속의 개념 => 기계의 name과 age를 사용할 수 있음

console.log(기계.prototype); // 내부에 prototype이라는 항목이 생성

학생1이 기계의 constructor가 가진 name과 age의 instance를 사용할 수 있게되는 것을 상속(inheritance)했다고 한다. 하지만 constructor말고도 상속기능을 구현할 수 있는데 Java와 같은 다른 언어의 상속(extend)의 개념과 달리 Javascript는 prototype의 개념을 사용한다.

 

new 기계()를 생성하게되면 표면적으로 보이지 않는 내부의 공간에 prototype이라는 공간이 생성되는데 이것은 부모의 유전자 역할을 한다.

 

 

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}

기계.prototype.gender = '남';
var 학생1 = new 기계();
var 학생2 = new 기계();

console.log(학생1.gender); // 남
console.log(학생2.gender); // 남

기계라는 부모의 prototype(유전자)에 {gender: '남'} 이라는 속성을 추가하게되면 기계를 상속한 학생1, 학생2 자식은 {gender: '남'}이라는 속성을 사용할 수 있다.

 

 

* 작동원리

- 자바스크립트는 오브젝트에서 값을 출력할 때

  • 학생1에 gender라는 값이 있는가 ?
  • 기계라는 부모 유전자에 gender라는 값이 있는가 ?
  • 그럼 그 부모의 유전자에 gender라는 값이 있는가 ?
  • ...

 

// Array 생성 시
var arr = [1,2,3];
// == var arr = new Array(1,2,3);

// Object 생성 시
var obj = {name: 'kim'}
// == var obj = new Object({name: 'kim'});

Array나 Object 등의 생성 방식의 차이일 뿐 결국엔 내부에선 constructor 방식으로 생성되기 때문에 해당 object에 prototype으로 저장 되어있는 내장 함수 또한 사용할 수 있다.

 

 

** prototype과 constructor의 차이

  • constructor: 자식들이 직접 값을 소유하게 하기
  • prototype: 부모만 가지고 있고 그걸 참조해서 쓰게 하기

 

 

 

* 특징

1. prototype은 constructor 함수에만 몰래 생성된다.

 - 일반 object, array엔 prototype 없음 (constructor 함수, Object.create(), class 써야 함)

 

 

2. 부모 유전자 찾기

function 기계(){
  this.name = 'Kim';
  this.age = 15;
}
var 학생1 = new 기계();
console.log(학생1.__proto__); // 기계
console.log(기계.prototype);

 - 부모의 의해 생성된 자식엔 __proto__라는 속성이 있는데 해당 속성은 자식의 부모 object를 찾아준다.

 

 

3. __proto__를 직접 등록하면 object끼리 상속기능 구현가능

var 부모 = { name : 'Kim' };
var 자식 = {};

자식.__proto__ = 부모;
console.log(자식.name); // Kim

 

 

4. 자바스크립트는 모든게 다 Object이다.

- __proto__로 계속 올라가다보면 모든 부모는 Object라는 constructor로 통한다.

 

 

 

상속

* ES5

- Object.create()

var 부모 = { name : 'Kim', age : 50 };
var 자식 = Object.create(부모);
자식.age  = 20;

var 손자 = Object.create(자식);

console.log(손자.age); // 20 ==> 자식의 age

손자에서 age를 호출하면 자식의 속성에 age가 있는지 확인하고 있으면 그 것을 사용하지만 없다면 부모로 올라가서 age가 있는지 확인하는 순서이기 때문에 자식의 age 값은 20으로 되어있어 20이 출력된다.

 

 

* ES6

- class

class 부모 {
  constructor(){
    this.name = 'Kim';
    this.sayHi = function(){ console.log('hello') } // constructor안에 추가하기
  }
  sayHi = function(){ console.log('hello') } // prototype에 추가하기
  // == sayHi(){ console.log('hello') } // prototype에 추가하기
}

var 자식 = new 부모();

function말고도 constructor를 만드는 ES6의 방식. constructor 안에 있는 인스턴스를 만들어 사용할 수 있으며 그 외부에 만들면 prototype으로 사용할 수 있다.

 

 

* Object.getPrototypeOf()

함수 안에 object를 넣으면 부모 prototype을 출력할 수 있다. (__proto__랑 유사함)

 

 

 

extends & super
class 할아버지{
  constructor(name){
    this.성 = 'Kim';
    this.이름 = name;
  }
  sayHi(){
    console.log('안녕 나는 할아버지')
  }  
}

class 아버지 extends 할아버지{ // 할아버지라는 객체의 특성을 복사할 때 사용
  constructor(name){
    super(name); // 할아버지 객체의 속성들을 사용하기위해 선언
    this.나이 = 50;
  }
  sayHi2(){
    console.log('안녕 나는 아버지');
    super.sayHi();
  }
}

var a = new 아버지('만수');
a.sayHi2(); // 안녕 나는 아버지 안녕 나는 할아버지

 

 

할아버지 객체의 속성들을 아버지 객체에 복사하기 위해 extends를 사용한다. extends를 사용하게 되면 따라오는 super라는 예약어가 있는데 super는 부모 객체의 속성들에 접근하는 것이다.

 

super는 constructor 안에서 사용하면 부모(할아버지) class의 constructor이고 prototype 함수 안에서 쓰면 부모(할아버지) class의 prototype이다.

 

 

 

getter & setter

자바스크립트의 getter, setter는 오브젝트 내의 데이터의 무결성을 보존하기 위해 쓰는 키워드이다.

var 사람 = {
  name : 'Kim',
  age : 30,
  nextAge(){
    return this.age + 1  
  }
  setAge(나이){
    this.age = parseInt(나이);
  }
}

console.log(사람.nextAge()); // 31
console.log(사람.setAge(20)); // 20

: 함수로 데이터 꺼내오기

- object 안의 데이터가 복잡할수록 함수로 만들어 데이터를 꺼내기 쉽다.

- 내부에 있는 인스턴스 변수를 건드리지 않아 수정 시 실수를 방지할 수 있다.

 

: 함수로 데이터 수정하기

- 원본 데이터를 덮어쓰지 않고 카피 데이터로 관리할 수 있다.

- 내부에 있는 인스턴스 변수를 건드리지 않아 수정 시 실수를 방지할 수 있다.

 

 

* get, set 사용

var 사람 = {
  name : 'Kim',
  age : 30,
  get nextAge(){
    return this.age + 1  
  }
  set setAge(나이){
    this.age = parseInt(나이);
  }
}

console.log( 사람.nextAge );
console.log( 사람.setAge = 20 );

get, set을 이용하면 오브젝트 내의 함수들을 괄호 없이 사용할 수 있다.

 

 

* 사용하는 기준

- 데이터를 뽑거나 가져올 때는 get, 데이터를 입력하거나 수정할 때는 set을 쓰면 된다.

get: 함수에 파라미터가 있으면 안되며 함수 내에 return이 있어야 한다.

set: 함수는 입력을 받아 수정을 해줘야 하기 때문에 파라미터가 한개 이상 꼭 존재해야 한다.

 

class 사람 {
  constructor(){
    this.name = 'Kim';
    this.age = 30;
  }
  get nextAge(){
    return this.age + 1  
  }
  set setAge(나이){
    this.age = parseInt(나이);
  }
}

var 사람1 = new 사람();

console.log( 사람.nextAge );
console.log( 사람.setAge = 20 );

마찬가지로 class에서도 사용할 수 있다.

 

 

 

 

'JavaScript' 카테고리의 다른 글

[JavaScript] 함수형 프로그래밍 (일급함수, 고차함수)  (0) 2022.03.29
[JavaScript] ES6 chap.3  (0) 2021.12.02
[JavaScript] ES6 chap.1  (0) 2021.11.29
[JavaScript] Chap.2  (0) 2021.11.25
[JavaScript] Chap.1  (0) 2021.11.19

+ Recent posts