<!-- 자연수 A, B, C 를 입력받아 세 수 중 가장 작은 값을 출력하는 프로그램을 작성하세요. -->
<!-- 정렬 사용 금지 -->
<body>
    
    <script>
        function findSmallest(a, b, c) {
            let min = a;
            if(a > b) min = b;
            if(min > c) min = c;
            return min;
        }
        console.log(findSmallest(3, 4, 5));
    </script>
</body>

 

리덕스 툴킷!

 

와.. 제가 이거 이해하려고 한참을 공부했내요..

 

제가 이해한게 틀렸다면 언제든지 지적 해주세요! 

 

아래에서 사용할 단어를 간단하게 정리했습니다

 

처음엔 어려워서 이해가 가지 않을 수 있습니다.

 

어렵더라도 끝까지 읽어보시고 마지막에 간단하게 정리된 내용까지 읽어보신다면...

아마도..?

 

store : store 어플리케이션의 전체 상태(state) 저장하고, 이를 변화시키는 action reducer 함수를 포함합니다.

전역으로 상태를 관리함으로써 컴포넌트 간의 데이터 공유가 용이해지고, 중앙 집중적으로 상태를 관리할 있습니다.

(여기서 만든 store을 provider에 사용함으로써 provider의 하위 컴포넌트에서 전역으로 상태를 관리할 수 있게 됩니다.)

 

action : action은 state를 변경할 때 dispatch와 함깨 사용하는 객체입니다. (일반적으로 액션 객체라고 부릅니다)

(액션 생성자 함수를 통해 액션 객체를 반환하며, 액션 생성자 함수는 밑에서 추가로 설명하겠습니다.)

 

reducer : 이전 상태와 액션 객체를 받아서 새로운 상태를 반환하는 함수입니다. reducer는 순수 함수여야하며, 불변성을 지켜야합니다.

(자세한 내용과 사용방법은 아래에 있습니다)

 


리덕스 툴킷의 구조를 만들어보겠습니다

 

1. store만들기

provider에 넣어서 관리를 하기위한 store를 만듭니다 

 1-1. configureStore를 통해 store만들기

 1-2. reducer를 넣어주기 (지금은 리듀서가 없습니다.! 나중에 와서 넣어줄껍니다..)

 


 

2. provider 만들기

Provider로 감싸줍니다. 이전에 만든 store를 넣어 위와 같이 감싸주면,그 안에 있는 컴포넌트들이 store를 이용할 수 있게 됩니다. 


3. slice 만들기

- slice란 Redux store의 상태(state), action, reducer를 하나의 모듈로 묶어서 관리하기 위한 개념입니다.

 여기서 만든 slice는 configureStore 로 만든 store의  상태를 관리하기 위한 모듈입니다.

- createSlice를 사용하여 slice를 생성할 수 있습니다.

(createSlice 사용하면 리덕스 툴킷이 자동으로 리듀서 함수를 생성하는데, 불변성을 지키기 위해 immer를 사용하여 내부적으로 리듀서 함수를 추가합니다.. 위에서 말한 불변성을 지키기 위한 코드를 작성하지 않아도 됩니다.)

name : slice의 이름,

initialState : state의 초기값,

reducers : reducers 에 선언한 함수들은 각각 actions, reducer 프로퍼티에 들어갑니다.

(slice.actions,  slice.reducer 등으로 사용합니다. 위에선 actions를 구조분해할당으로 사용하기 편하게 작성했습니다)

 

actions : actions는 reducers에 있는 함수들을 통해 액션 생성자 함수를 생성해 가지고 있습니다. 

actions 에있는 increment, decrement, incrementByAmount 등(액션 생성자 함수) 를 이용해서 액션 객체를 생성하며, 액션 객체를  dispatch 와 같이 사용해서 state를 변환합니다.

 

reducerreducer에는 slice의 전반적인 내용이 담겨있습니다. (reducers와 다릅니다!)

(name, initialState, reducer등. 각종 자료가 담겨있습니다.)

 

여기서 만든 createSlice 를 통해 만들어진 actions, reducer를 이제 각각 사용해보겠습니다.


4. 다시store로 !

여기서 이제 configureStore 의 reducer: 부분에서 createSlice로 만든 reducer(counterReducer) 를 여기서 사용합니다.

 

(counterReducer 로 넘어온 이유는 counterSlice.reducer 가 counterReducer입니다,

export defalut로 counterReducer를 넘겼습니다. counterSlice를 넘겨  아래와 같이 사용할 수 있으나. 권장하지 않습니다.)


 

configureStore에서 reducer를 직접 만들지않고 createSlice로 만든 reducer를 쓰는 이유는 

slice의 reducer는 내부적으로 immer를 통해 불변성을 유지해주면서 새로운 상태를 반환 해주는 리듀서를 만듭니다.

 

createSlice 사용하지 않고 직접 reducer 함수를 만들어야 한다면, 해당 함수 안에서 상태를 변경하는 코드를 작성할 매번 새로운 객체를 생성하고 기존의 상태와 새로운 값을 합쳐서 반환해주어야 불변성을 유지할 있습니다. 이러한 작업은 번거롭고 실수하기 쉬운 작업이기 때문에, createSlice 사용하면 해당 부분을 자동으로 처리해줌으로써 코드 작성을 쉽고 간단하게 만들어줍니다.

 

정리하면

slice의 reducer 안에는 immer를 사용해 만든 reducer 함수와, slice의 기본적인 정보(이름, 초기 상태값)등이 들어있습니다. 그 정보를 하나의 객체 안에 담아서 사용하는 것 입니다.

reducer : {

      counter : counterReducer

}

위의 코드는  counter라는 state를 정의하고, 그 state를 관리하기 위해 reducer(counterReducer) 라는 함수를 사용한다는 의미입니다.

 

설명이 이상하지만 저런식으로 이해하시면 됩니다.


5. 실제 사용 부분

1. useSelector() 를 이용해서 store에 들어있는 값을 가져옵니다.

2. state.counter.value는 상태값을 의미하며 count에 넣었습니다.

3. useDispatch() 를 통해서 dispatch를 사용합니다. 

4. createSlice를 통해 만든 counterSlice.actions(액션 생성자 함수)를 구조분해 할당해서 가져왔고 dispatch에서 사용합니다.

(dispatch는 액션 객체를 받아서 리듀서 함수를 호출합니다)

5. dispatch에는 액션 객체가 들어가기 떄문에 액션생성자를 사용합니다.

increment, decrement의 경우 액션 생성자를 통해 액션 객체를 생성합니다.

(액션 객체 안에는 typepayload 프로퍼티가 있습니다. 그외에 다른 프로퍼티를 추가로 포함시킬 수 있으나 권장하지않음)

액션 객체의 type을 통해서 type에 맞는 리듀서 함수를 호출합니다.

 

별도의 값이 필요하지않은 리듀서 함수는 첫번째 인자인 state만 받아 사용합니다.

 

 

하지만 incrementByAmount 경우, dispatch(incrementByAmount(5))처럼 인자로 5 넘겨서 호출하면, 5만큼 증가하도록 액션 객체가 생성됩니다. (여기서 5가 payload 에 해당됩니다)

payload가 필요한 incrementByAmount 라는 리듀서 함수는 두번째 인자로 action객체를 받습니다

action객체의 payload를 통해 입력한 값을 상태에 적용합니다.

 

 

 

 


리덕스 툴킷을 공부하며..

 

저는 리덕스 툴킷에 대해 공부하면서 처음 접했을때 이 구조가 쉽게 이해 되지 않았습니다.

거의 3~4시간이 넘게 이해한게 맞는지 찾아보고, 시도해보고, AI를 이용해서 검증도 했습니다.

그래서 제가 이해하기 쉬운대로.. 이해한 그대로 작성해봤습니다.

 

정확하지 않을 수 있습니다.  chatGpt를 통해 많이 물어보고 이해한게 맞다는 답을 들었습니다. 하지만 chatGPT가 틀렸을 수 있습니다.

 

그래도 지금까지 제가 사용하면서 오류가 난 부분은 없습니다.

 

혹시라도 틀렸다면 언제든지 지적해주시면 감사하겠습니다.

 

 

'React > React Library' 카테고리의 다른 글

드래그 라이브러리 react-beautiful-dnd에 대해  (0) 2023.07.01

tsconfig.json은 크게 3가지로 나뉩니다

 

compilerOptions , include, exclude

 

compilerOptions

 - Ts코드를 Js로 변환하기 위해서 필요한 옵션

include

- 컴파일할 파일 경로 목록

- 배열 데이터로 목록화해서 제공

exclude

- 컴파일에서 제외할 파일 경로 목록

 

{
  "compilerOptions": {
    //타입 스크립트를 ES2015로 변환 (ES2015 권장)
    "target": "ES2015",

    // 모듈 시스템 지정 ESNext , CommonJS , AMD 등이 있으며
    // Node js 환경에서는 CommonJS를 사용하며, 브라우저에서는 ESM 방식 (ESNext와 동일)을 사용합니다
    "module": "ESNext",

    //모듈의 해석방식 Node와 Classic이 있으며 기본값은 Node입니다.
    "moduleResolution": "Node",

    // ECMA 방식과 Common js 방식을 호환가능 여부입니다 (기본값 false)
    // ESM 과 Common 방식을 구분짓지않고 사용가능하게 가능한 true
    "esModuleInterop": true,

    // 모든 파일을 모듈로 컴파일, import 혹은 export 필수
    "isolatedModules": false,

    // 모듈 해석에 사용할 기준 경로 지정
    "baseUrl": "./",

    // 컴파일러가 참조할 타입 선언(d.ts)의 경로를 지정합니다
    "typeRoots": ["./node_modules/@types"],

    // TS를 변환(컴파일)시 내부적으로 사용할 라이브러리의 목록을 지정합니다
    // 컴파일에서 사용할 라이브러리 목록- ESNext , DOM
    "lib": ["ESNext", "DOM"],

    // 엄격한 타입 검색 활성화 여부 (기본값 false) 가능한 true 사용하기
    // Ts를 사용함으로써 보다 엄격하게 작성하기 위함 , false 사용시 Ts의 장점중 일부가 없어집니다.
    // true 사용시 아래의 옵션들 true로 바뀌며, 세부 조정 가능합니다.
    "strict": true,

    // 기본값은 false이며, strict가 true가 됨에따라 같이 바뀌는 옵션들
    // 암시적 any 타입 검사 활성화
    "noImplicitAny": false,

    // 암시적 this 타입 검사 활성화
    "noImplicitThis": false,

    // 엄격한 Nullish 타입 검사 활성화
    "strictNullChecks": false,

    // 엄격한 함수의 매개변수 타입 검사 활성화
    "strictFunctionTypes": false,

    // 엄격한 클래스의 속성 초기화 검사 활성화
    "strictPropertyInitialization": false,

    // 엄격한 Bind, Call, Apply 메소드의 인수 검사 활성화
    "strictBindCallApply": false,

    // 이외에도 다른 옵션들이 더 있으며, strict에 따라서 바뀌지 않는 옵션도 존재합니다.

    //여기까지
  },
  "include": [
    // 프로젝트의 어느 위치에서  TS를 찾을 수 있는지
    "src/**/*.ts" //   src /하위 폴더의/ ts로 끝나는 파일들
  ],
  "exclude": [
    // 변환(컴파일) 제외 폴더
    "node_modules"
  ]
}

 

 

최소한의 옵션

{
  "compilerOptions": {
    "target": "ES2015", //타입 스크립트를 ES2015로 변환 (ES2015 권장)
    "module": "ESNext", // 모듈 시스템 지정 ESNext , CommonJS , AMD
    "moduleResolution": "Node", //모듈의 해석방식
    "esModuleInterop": true ,// ECMA 방식과 Commomjs 방식을 호환가능하게.
    "lib"  : ["ESNext","DOM"], // TS를 변환(컴파일)시 내부적으로 사용할 라이브러리의 목록을 지정
    "strict": true // TS를 엄격하게 사용할 것 인가.
  },
  "include": [  // 프로젝트의 어느 위치에서  TS를 찾을 수 있는지
    "src/**/*.ts" //   src /하위 폴더의/ ts로 끝나는 파일들
  ] ,
  "exclude": [ // 변환(컴파일) 제외 폴더
    "node_modules"
  ]
}

추론과 명시적 타입 선언

타입 추론은 코드 작성을 보다 간단하게 만들어주지만, 코드를 읽고 이해하기 어려울 수 있으며,

추론이 실패하는 경우가 있어 코드 안정성이 떨어집니다.

 

반면에, 명시적 타입 선언은 코드를 작성하고 읽는 과정에서 명확히 알려주고 가독성을 높이고, 컴파일시 타입 에러를 사전에 방지해줍니다.

하지만 타입을 명시적으로 선언하는 과정이 번거로울 수 있습니다.

 

간단한 함수에서는 타입 추론을 사용하고, 복잡한 함수나 인터페이스에서는 명시적 타입 선언을 사용하는등

적절히 선택하는 것이 중요합니다.

근대 그 적절히가 어려운 것 같습니다...

 

제네릭을 사용하며 명시적인 타입 지정을 할 경우


1.  제네릭을 사용하는 함수나 클래스가 다른 함수나 클래스와 협업하여 동작할 때

타입 추론만으로는 제대로 동작하지 않을 수 있습니다.  이런 경우에는 명시적으로 타입을 지정해주어야 합니다. 

Promise 객체를 반환하는 함수를 작성할 때는 Promise의 resolve 값의 타입을 명시적으로 지정해주어야 합니다.

2. 제네릭 타입이 여러 개 사용되는 경우 명시적으로 타입을 지정해주어야 합니다. 

React에서 제공하는 React.Component 클래스를 상속받는 경우, 제네릭 타입을 두 개 사용해야 합니다. 

이때, 두 개의 제네릭 타입이 서로 다른 타입을 나타내는 경우, 각각의 제네릭 타입을 명시적으로 지정해주어야 합니다.

3. 코드의 가독성을 높이기 위해 명시적으로 타입을 지정해주는 것이 좋을 수 있습니다. 

코드가 길어지고 복잡해질수록 타입 추론만으로는 코드를 이해하기 어려울 수 있습니다. 

이럴 때는 명시적으로 타입을 지정해주어 코드의 가독성을 높일 수 있습니다.

extends를 사용해 들어올 타입을 제한할 수 있습니다.

//제네릭
// 인터페이스, 제약 조건


//string , number만 받는다 라는 의미
interface MyData<T extends string | number>{
  name: string;
  value :T
}

const dataA: MyData<string> = {
  name: 'Data A',
  value: 'Hello world'
}

const dataB: MyData<number> = {
  name: 'Data A',
  value: 2342
}

const dataC: MyData<boolean> = {
  name: 'Data A',
  value: false
}

const dataD: MyData<number[]> = {
  name: 'Data A',
  value: [1,2,3,4]
}

 

크게 다르지 않아서 소스코드만 첨부하겠습니다.

 

//제네릭 클래스

class User<P> {
  constructor(public payload: P) {}

  getPayload() {
    return this.payload;
  }
}

interface UserAType {
  name: string;
  age: number;
  isValid: boolean;
}

interface UserBType {
  name: string;
  age: number;
  emails: string[];
}

const jplum = new User<UserAType>({
  name: "J-plum",
  age: 53492,
  isValid: true,
  emails: [], //에러 발생 UserAType 에는 emails가 없음
});

const euang = new User<UserBType>({
  name: "Eunag",
  // age가 없어서 에러 발생
  emails: ["euang@gamil.com"],
});

Ts의 제네릭 문법에서는  크게 보면 함수, 클레스, 인터페이스에서 사용할 수 있습니다

 

 

1. 함수

제네릭 클래스와 인터페이스는 아래의 링크를 참고하시면 됩니다.

제네릭 클래스 : https://j-plum.tistory.com/71

제네릭 인터페이스 : https://j-plum.tistory.com/72

 

 

array를 다양한 타입으로 오버로딩 한 상태입니다.

제네릭 문법

오버로딩으로 타입을 지정했던 부분을 전부 지워버린 상태입니다.

 

그런대 첫번재 array에서 숫자 부분에 에러가 발생했습니다.

string이라고 지정하지 않았지만 추론에 의해 string 이 아니라는 에러가 발생했습니다.

 

array<T>(x : T, y: T) 라는 구조에 의해 

 

x가 T의 타입을 사용하는데 x에 string이 넘어와 y또한 string이라고 Ts가 추론을 합니다.

 

순서를 바꿨더니 이번엔 뒤의 문자열에 에러가 발생했습니다.

 

 

추론이 아닌 미리 타입을 작성할 경우 아래와 같이 작성합니다.

string이 T에 해당하며 , x와 y는 T의 타입인 string 타입이 됩니다.

 

 


소스코드

interface Object {
  a: number;
}

type Arr = [number, number];

// function array(x: string, y: string): string[];
// function array(x: number, y: number): number[];
// function array(x: boolean, y: boolean): boolean[];
// function array(x: Object, y: Object): Object[];
// function array(x: Arr, y: Arr): Arr[];


function array<T>(x: T, y: T){
  return [x, y];
}

console.log(
  array('J-plum','Euang'),
  array(1,2),
  array(true,false),
  array({ a: 1 },{a: 2}),
  array([1,2],[3,4,5]), // 이와 같이 사용할 경우 number[] 타입을 추론합니다.
  array<Arr>([1,2],[3,4,5]), // Arr을 이용하려면 튜플에 맞게 5를 지워야합니다.
)

 

클레스

클레스에서 위에 타입을 지정해주지 않으면 에러가 발생합니다.

 

js에서는 문제가 없는 코드이지만

 

Ts에서는 constructor 가 지정되기 전에 미리 타입이 지정되어 있어야합니다.

 


접근 제어자

public : 자유롭게 접근 가능, 클래스내 생략 가능

protected : 자신과 파생된 후손 클래스 내에서 접근 가능

private : 클래스 내에서만 접근 가능

 


추가내용

클래스 위에서 타입을 지정하지 않고 생성자에서 접근제어자와 함깨 작성한다면 생략 가능합니다.

(단! 이때 public은 생략할 수 없습니다.)

 

소스코드

class User1 {
  constructor(
    public first: string,
    public pet: string,
    public age: number
  ) {
    this.first = first;
    this.pet = pet;
    this.age = age;
  }
  getAge() {
    return `${this.first} ${this.pet} is ${this.age}`;
  }
}

class User2 extends User1 {
  getAge() {
    return `${this.first} ${this.pet} is ${this.age}`;
  }
}

class User3 extends User2 {
  getAge() {
    return `${this.first} ${this.pet} is ${this.age}`;
  }
}

const Jplum = new User1("J-plum", "Euang", 4236798);
console.log(Jplum.first);
console.log(Jplum.pet);
console.log(Jplum.age);

위의 이미지는 

로직과 매개변수 개수도 똑같은데 타입이 다르다는 이유로 두개로 만들어서 사용하는 이미지입니다.

 

이걸 함수의 오버로딩을 이용한다면

타입을 분기해 이용이 가능합니다.

add 함수의 any는 위의&nbsp; 타입들을 받을수 있게 만든 것입니다.

 

에러 내용 add의 두가지 형식에 해당되지 않는다.

함수를 여러개 만들지 않더라도

함수의 타입 선언부를 여러개 만들어 줌으로써(시그니처), 같은 함수라도 타입을 여러개로 관리할 수 있습니다.

 

 

아 ~ 무 문제 없어보이지만

 

우리의 Ts  에러가 발생했다.

 

이러면 또 .. Ts를 사용하는 의미가..

 

요로코롬 사용하면! 에러가 사라집니다

 

뭔가.. 저렇게 사용하면 매개변수 처럼 보이긴 하지만.. 

 

그냥 this를 지정해주었다! 라고 생각하시면 될 것 같습니다.

+ Recent posts