728x90
반응형

컨트롤러는 들어오는 request를 처리하고 response를 클라이언트에게 반환하는 역할을 한다.

 

예제를 살펴보면, 

 

cats.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller('cats') //경로
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

 

컨트롤러를 정의하는 데 필수인 @Controller() 안에 선택적 라우트 경로(path) 접두사인 cats를 지정한다.

이렇게 지정을 해두면 /cats로 들어오는 요청들을 컨트롤 할 수 있다.

 

CLI를 사용하여 컨트롤러를 만들려면 
$ nest g controller cats
 명령을 실행하면 됩니다.

 

findAll() 메서드 앞에 있는 @Get() HTTP 요청 메서드 데코레이터는 Nest에 HTTP 요청에 대한 특정 엔드포인트에 대한 핸들러를 생성하도록 지시한다. 

 

예를 들어 @Get('profile')과 결합된 constomer의 경로 접두사는  GET /customers/profile 과 같은 요청에 대한 라우트 매핑을

생성한다.

 

메소드 핸들러 시그니처(예: findAll(@Res() response))에서 @Res() 데코레이터를 사용하여 삽입할 수 있는 라이브러리별(예: Express) 응답객체를 사용할 수 있습니다. 예를 들어 Express에서는 response.status(200).send() 와 같은 코드를 사용하여 응답을 구성할 수 있습니다.

 

정리해보면,

  • /cats 로 요청을 받을 때 @Controller('cats') 로 지정해준다.
  • GET 메소드로 요청을 받을 때 @Get()을 선언해준다.
  • GET 메소드 하위경로를 지정해줄 땐 @Get() 안에 경로 이름을 지정해준다.
  • 기본적으로 지정된 라우터는 findAll()로 들어오게된다.
  • @Req() 데코레이터를 추가하면 요청객체에 액세스 할 수 있다.

 

 

다음으로 POST 핸들러를 만들어보자.

 

cats.controller.ts

import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

 

GET 메소드 요청 처리를 이해하면,  POST 핸들러는 아주 쉽게 이해할 수 있다.

@Get()@Post()@Put()@Delete()@Patch()@Options() 및 @Head(). 또한 @All()은 이들 모두를 처리하는 엔드 포인트를 정의한다.

 

 

Route wildcards

패턴 기반 라우트 또한 제공한다. 별표는 와일드카드로 사용되며 모든 문자조합과 일치한다.

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

 

 

Status code

201인 POST 요청을 제외하고 상태코드는 기본적으로 항상 200이다. @HttpCode() 데코레이션을 추가하여 변경할 수 있다.

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}
@nestjs/common 패키지에서 HttpCode를 가져옵니다.

 

Headers

커스텀 응답헤더를 지정하려면 @Header() 데코레이터를 이용한다. ( res.header()를 직접 호출 )

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}
@nestjs/common 패키지에서 Header를 가져옵니다.

 

 

Redirection

응답을 특정 URL로 리디렉션 하려면 @Redirect() 데코레이터를 이용한다. ( res.redirect()를 직접 호출)

@Get()
@Redirect('https://nestjs.com', 301)

 

그럼 이런식으로 반환하게 된다.

{
  "url": string,
  "statusCode": number
}

 

반환된 값은 @Redirect() 데코레이터에 전달된 모든 인수를 재정의 할 수 있다.

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

 

 

 

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | 개요 | Modules  (0) 2021.09.24
Nest.js | 개요 | Service  (0) 2021.09.24
Nest.js | 개요 | Controller(2)  (0) 2021.09.24
Nest.js | 개요 | 첫 번째 단계  (0) 2021.09.24
Next.js | 소개  (0) 2021.09.24
728x90
반응형

설정

 

우리는 먼저 새 프로젝트를 설정해야 한다. 전 글에서 말했듯이 먼저 npm이 설치되어있는 상태로 진행해보겠다.

$ sudo npm i -g @nestjs/cli
$ nest new project-name

 

먼저 패키지매니저를 설정할 수 있다. ( 저는 npm이 편해서 npm으로 했습니다! )

 

 

그러면 project-name 폴더가 생성되고 다른 몇가지의 상용구 파일들이 설치되며  src/  폴더가 생성되는것을 확인할 수 있다.

 

 

src폴더의 하위파일들을 살펴보면,

 

app.controller.ts ( 하나의 라우트가 있는 기본 컨트롤러 )

import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();

    appController = app.get<AppController>(AppController);
  });

  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).toBe('Hello World!');
    });
  });
});

 

app.controller.spec.ts ( 컨트롤러를 위한 유닛 테스트 )

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

 

app.module.ts ( 애플리케이션의 루트 모듈 )

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

app.service.ts ( 단일 메소드를 사용하는 기본 서비스 )

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

 

main.ts ( 핵심기능을 사용하여 Nest 애플리케이션 인스턴스를 생성하는 애플리케이션 엔트리 파일 )

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

 

 

애플리케이션 실행

$ npm run start

 

script 파일을 살펴보면 nest start와 같다는 것을 확인할 수 있다.

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | 개요 | Modules  (0) 2021.09.24
Nest.js | 개요 | Service  (0) 2021.09.24
Nest.js | 개요 | Controller(2)  (0) 2021.09.24
Nest.js | 개요 | Controllers(1)  (0) 2021.09.24
Next.js | 소개  (0) 2021.09.24
728x90
반응형

소개

 

Nest (NestJS)는 효율적이고 확장 가능한 Node.js 서버측 애플리케이션을 구축하기 위한 프레임워크이다. 프로그레시브 자바스크립트를 사용하고 TypeScript로 빌드되고 완벽하게 지원하며(하지만 여전히 개발자가 순수 자바스크립트로 코딩할 수 있음), OOP (객체 지향 프로그래밍 Object Oriented Programming), FP (함수형 프로그래밍 Functional Programming) 및 FRP (함수형 반응형 프로그래밍 Functional Reactive Programming) 요소를 결합한다.

 

 

설치

 

Next CLI를 사용하여 프로젝트를 스캐폴딩하거나 시작 프로젝트를 복제할 수 있다.

$ npm i -g @nestjs/cli
$ nest new project-name

 

 

Git으로 설치하는 방법

$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start

 

 

npm start를 하면 설정해두었던 포트에 정상적으로 서버가 실행되는것을 확인할 수 있다.

 

 

 

참조

https://docs.nestjs.kr/

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | 개요 | Modules  (0) 2021.09.24
Nest.js | 개요 | Service  (0) 2021.09.24
Nest.js | 개요 | Controller(2)  (0) 2021.09.24
Nest.js | 개요 | Controllers(1)  (0) 2021.09.24
Nest.js | 개요 | 첫 번째 단계  (0) 2021.09.24
728x90
반응형

리액트에서 배열이나 객체를 업데이트 해야 할 때에는 직접 수정 하면 안되고 불변성을 지켜주면서 업데이트를 해주어야 한다.

 

 

다음과 같은 객체가 있다고 생각해보자.

const state = {
  posts: [
    {
      id: 1,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 1,
          text: '와 정말 잘 읽었습니다.'
        }
      ]
    },
    {
      id: 2,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 2,
          text: '또 다른 댓글 어쩌고 저쩌고'
        }
      ]
    }
  ],
  selectedId: 1
};

 

여기서 posts 배열 안의 id 가 1 인 post 객체를 찾아서, comments 에 새로운 댓글 객체를 추가해줘야 한다면

보통 우리는 이렇게 할 것이다.

 

const nextState = {
  ...state,
  posts: state.posts.map(post =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat({
            id: 3,
            text: '새로운 댓글'
          })
        }
      : post
  )
};

 

 

 

방법이 잘못된건 아니지만 코드의 구조가 좀 복잡해서 한 눈에 안들어온다.

이럴 때 Immer 모듈을 사용할 수 있는데,

 

1. 먼저 설치한다.

$ sudo npm i immer

 

2. 코드의 상단부분에 import 해준다.

import produce from 'immer';

이 produce라는 함수를 쓸 때에는 첫번째 파라미터에는 수정하고 싶은 상태, 두번째 파라미터에는 어떻게 업데이트하고 싶을지 정의하는 함수를 넣어준다.

두번째 함수에서 불변성을 상관하지 않고 그냥 업데이트를 해주면 알아서 다 해준다.

 

const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

console.log(nextState);
// { number: 2, dontChangeMe: 2 }

 

 

이번엔 리듀서에서 Immer를 사용해보자.

 

import React, { useReducer, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import produce from 'immer';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      });
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      });
    default:
      return state;
  }
}

// UserDispatch 라는 이름으로 내보내줍니다.
export const UserDispatch = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const { users } = state;

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser />
      <UserList users={users} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

 

업데이트를 해주는 부분인 TOGGLE type은 다소 깔끔해지지가 않았다.

 

우리가 useState를 썼을 땐 업데이트를 할 때 보통 아래와 같이 코딩했었다.

const [todo, setTodo] = useState({
  text: 'Hello',
  done: false
});

const onClick = useCallback(() => {
  setTodo(todo => ({
    ...todo,
    done: !todo.done
  }));
}, []);

 

위에서 선언한 produce를 쓸 때 원래는 두 개의 파라미터를 이용해 선언했지만, 파라미터를 한 가지만 담아서 보낼 경우에는 반환된 값이 새로운 상태가 아닌 상태를 업데이트를 해주는 함수가 된다.

 

const [todo, setTodo] = useState({
  text: 'Hello',
  done: false
});

const onClick = useCallback(() => {
  setTodo(
    produce(draft => {
      draft.done = !draft.done;
    })
  );
}, []);

 

 

단, Immer 는 JavaScript 엔진의 Proxy 라는 기능을 사용하는데, 구형 브라우저 및 react-native 같은 환경에서는 지원되지 않으므로 (Proxy 처럼 작동하지만 Proxy는 아닌) ES5 fallback 을 사용하게 됩니다. ES5 fallback 을 사용하게 되는경우는 191ms 정도로, 꽤나 느려지게 됩니다. 물론, 여전히 데이터가 별로 없다면 크게 걱정 할 필요는 없습니다.
Immer 라이브러리는 확실히 편하기 때문에, 데이터의 구조가 복잡해져서 불변성을 유지하면서 업데이트하려면 코드가 복잡해지는 상황이 온다면, 이를 사용하는 것을 권장드립니다.
다만, 무조건 사용을 하진 마시고, 가능하면 데이터의 구조가 복잡해지게 되는 것을 방지하세요. 그리고 어쩔 수 없을 때 Immer 를 사용하는것이 좋습니다. Immer 를 사용한다고 해도, 필요한곳에만 쓰고, 간단히 처리 될 수 있는 곳에서는 그냥 일반 JavaScript 로 구현하시길 바랍니다.

 

 

 

참조

http://react.vlpt.us/basic/23-immer.html

728x90
반응형
728x90
반응형

전 글에서 설명할 때 

 

1. 상위 컴포넌트에서 내보내고,

export const UserDispatch = React.createContext(null);

 

 

 

2. 감싸고,

  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );

 

 

 

 

3. 하위 컴포넌트에서 import 하고,

import { UserDispatch } from './App';

 

 

4. 하위 컴포넌트에서 useContext로 가져왔다.

const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);

 

 

 

 

이 방법이 잘못된건 아니지만, 다른 방법도 있어서 설명하겠다.

 

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  return (
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

export function useTodoState() {
  return useContext(TodoStateContext);
}

export function useTodoDispatch() {
  return useContext(TodoDispatchContext);
}

먼저 상위 컴포넌트에서 위 코드가 같이 선언해준다.

 

 

 

1. 먼저 state 와 dispatch가 담길 Context를 선언해준다.

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();

 

 

2. 그 다음 다른 컴포넌트에서 import 할 함수들을 정의한다.

export function useTodoState() {
  return useContext(TodoStateContext);
}

export function useTodoDispatch() {
  return useContext(TodoDispatchContext);
}

 

 

3. 그 다음 Context를 사용 할 하위 컴포넌트들을 감싼다

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  return (
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

 

 

4. 그 다음 하위 컴포넌트에서 import하여 사용한다.

import React from 'react';
import { useTodoState, useTodoDispatch } from '../TodoContext';

function Sample() {
  const state = useTodoState();
  const dispatch = useTodoDispatch();
  return <div>Sample</div>;
}

 

 

차이점은

- useContext를 하위컴포넌트에서 사용하냐, 

- useContext를 상위컴포넌트에서 사용하여 Context가 return되는 함수를 정의하고 하위 컴포넌트에서 이 함수를 하위

   컴포넌트에서 사용하냐 이다.

 

사용하기 나름이라 편한걸로 쓰면 될 것 같다.

 

 

728x90
반응형

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

React.js | API 연동 | 기본  (0) 2021.10.07
React.js | 입문 | Immer  (0) 2021.09.17
React.js | 입문 | Context API 전역 값 관리(1)  (0) 2021.09.17
React.js | 입문 | React.memo  (0) 2021.09.17
React.js | 입문 | useCallback  (0) 2021.09.17
728x90
반응형

우리가 현재 만들고 있는 프로젝트를 보면, App 컴포넌트에서 onToggle, onRemove 가 구현이 되어있고 이 함수들은 UserList 컴포넌트를 거쳐서 각 User 컴포넌트들에게 전달이 되고 있다.

 

컴포넌트들이 상위 컴포넌트에서 하위컴포넌트로 전달되어지는 방법이다.

 

상위컴포넌트에서 함수들이 전달되어질 때, 거쳐야 할 컴포넌트가 여러개 ( 즉 하위 컴포넌트의 하위 컴포넌트의 ... )가

되면 매우 번거로운 작업이 될 것이다.

 

이 때 리액트의 Context API 와 이전 섹션에서 배웠던 dispatch 를 함께 사용하면 이러한 복잡한 구조를 해결 할 수 있다.

 

먼저 이렇게 React.createContext() 라는 함수를 사용한다.

const UserDispatch = React.createContext(null);

 

 

이러면 Context의 기본 값을 설정해줄 수 있다. 위에서 설정한 값은 따로 값을 사용하지 않았을 때의 기본 값이다.

 

Context를 만들면 Context안에 Provider 라는 컴포넌트가 들어있는데, 이 컴포넌트를 통해 Context의 값을 지정해줄 수

있다. value라는 값을 설정해주면 된다.

<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>

 

아래 전체 코드를 보면서 이해해보자.

 

App.js

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        ...state,
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

// UserDispatch 라는 이름으로 내보내줍니다.
export const UserDispatch = React.createContext(null);

function App() {
  const [{ username, email }, onChange, onReset] = useInputs({
    username: '',
    email: ''
  });
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    onReset();
    nextId.current += 1;
  }, [username, email, onReset]);

  const onToggle = useCallback(id => {
    dispatch({
      type: 'TOGGLE_USER',
      id
    });
  }, []);

  const onRemove = useCallback(id => {
    dispatch({
      type: 'REMOVE_USER',
      id
    });
  }, []);

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>
  );
}

export default App;

 

 

위에서 말했듯이 React.createContext 함수로 내보냈고,

 

    <UserDispatch.Provider value={dispatch}>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </UserDispatch.Provider>

이런 식으로 컴포넌트들을 .Provider에 감싸고, value로 Context의 값을 지정해주었다.

 

 

그 다음 감싼 컴포넌트 내에서 먼저 import를 해주고,

import { UserDispatch } from './App';

 

 

아래와 같이 useContext를 사용하여 불러왔던 UserDispatch를 사용하게끔 설정해주면 끝!!

const User = React.memo(function User({ user }) {
  const dispatch = useContext(UserDispatch);

 

 

이제 우리는 여러 컴포넌트를 거쳐서 전달할 필요없이 최상위 폴더에서 React.createContext 함수를 써서 내보내고,

Provider로 하위 컴포넌트를 감싼다음에 사용할 필요가 있을 때 useContext로 편하게 불러올 수 있다!

 

 

참고로 React.createContext를 안쓰고

import React, { useReducer, createContext } from 'react';

이런식으로 불러와도 된다.

 

 

참조 

http://react.vlpt.us/basic/22-context-dispatch.html

728x90
반응형

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

React.js | 입문 | Immer  (0) 2021.09.17
React.js | 입문 | Context API 전역 값 관리(2)  (0) 2021.09.17
React.js | 입문 | React.memo  (0) 2021.09.17
React.js | 입문 | useCallback  (0) 2021.09.17
React.js | 입문 | 커스텀 Hook  (0) 2021.09.17
728x90
반응형

이전에 useMemo와 useCallback이란 함수를 리렌더링 관리의 중요성과 함께 설명했었다.

바로 컴포넌트에 적용을 해보겠다.

 

UserList.js

import React from 'react';

const User = React.memo(function User({ user, onRemove, onToggle }) {
  return (
    <div>
      <b
        style={{
          cursor: 'pointer',
          color: user.active ? 'green' : 'black'
        }}
        onClick={() => onToggle(user.id)}
      >
        {user.username}
      </b>
      &nbsp;
      <span>({user.email})</span>
      <button onClick={() => onRemove(user.id)}>삭제</button>
    </div>
  );
});

function UserList({ users, onRemove, onToggle }) {
  return (
    <div>
      {users.map(user => (
        <User
          user={user}
          key={user.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
}

export default React.memo(UserList);

위 코드를 보면 User를 React.memo에 감싸는 방식과

export default를 할 UserList를 React.memo에 감싸서 보내는 방식을 볼 수 있다.

 

한 마디로 그냥 감싸주면 된다.

 

추가적으로, React.memo 에서 두번째 파라미터에 propsAreEqual 이라는 함수를 사용하여 특정 값들만 비교를 하는 것도 가능하다. 하지만 잘못 사용하면 의도치 않은 버그가 발생하니 주의하자!

export default React.memo(
  UserList,
  (prevProps, nextProps) => prevProps.users === nextProps.users
);
728x90
반응형
728x90
반응형

useMemo와 비슷한 useCallback이다. 

 

  const onCreate = useCallback(() => {
    const user = {
      id: nextId.current,
      username,
      email
    };
    setUsers(users.concat(user));

    setInputs({
      username: '',
      email: ''
    });
    nextId.current += 1;
  }, [users, username, email]);

 

 

useCallback 첫번째 파라미터에 연산내용이 담긴 함수를 정의하고, 두번째 파라미터에는 함수에서 쓰이는

상태 값들을 배열에 넣는다.

 

사실상 겉으로 보기에 useCallback을 쓰는것과 안쓰는것은 별반 차이가 없으나, 두번째 파라미터 배열에 담긴 상태 값들의 변경이 없을 경우에 렌더링이 되어도 메모이제이션으로 기억하고있던 함수를 반환하기 때문에 성능향상에 도움이 된다.

 

useMemo와의 차이점은 useMemo는 (컴포넌트 포함), useCallback은 함수를 호출한다는 점이다. 

 

모든 값들이 바뀔 때마다 전부 리렌더링을 해주면 상당히 비효율적일거 같은데, 다행히도 이런 함수들이 우리를 도와준다.

 

(이런 사소한것들을 놓치면 언제 어떤 상태 값 때문에 리렌더링이 되는지 모르게되고, 콘솔창에 여러 값들이 반복되어 마구 찍히는것을 볼 수 있다는점..)

728x90
반응형

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

React.js | 입문 | Context API 전역 값 관리(1)  (0) 2021.09.17
React.js | 입문 | React.memo  (0) 2021.09.17
React.js | 입문 | 커스텀 Hook  (0) 2021.09.17
React.js | 입문 | useReducer  (0) 2021.09.17
React.js | 입문 | useMemo  (0) 2021.09.17
728x90
반응형

컴포넌트를 만들다 보면 반복되는 로직이 자주 발생하게되기 마련인데, 이를 모듈화해서 재사용할 수 있는 방법이 있다.

예를들어 Input을 관리하는 부분을 커스텀 Hook을 만들어서 쉽게 재사용할 수 있도록 구현해보겠다.

 

먼저 파일을 만들어준다.

 

useInput.js

import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

 

그 다음 useReducer에서 사용하는 inputs를 없애고 useInput을 넣어준다.

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return {
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

function App() {
  const [{ username, email }, onChange, reset] = useInputs({
    username: '',
    email: ''
  });
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    reset();
    nextId.current += 1;
  }, [username, email, reset]);

  const onToggle = useCallback(id => {
    dispatch({
      type: 'TOGGLE_USER',
      id
    });
  }, []);

  const onRemove = useCallback(id => {
    dispatch({
      type: 'REMOVE_USER',
      id
    });
  }, []);

  const count = useMemo(() => countActiveUsers(users), [users]);
  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : {count}</div>
    </>
  );
}

export default App;

 

 

기존코드는 아래와같이 일일이 하나씩 선언을 해준것에 비해

const { username, email } = state.inputs;
  
const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, []);

 

 

커스텀 Hook을 사용해 더 간결하게 불러왔다.

  const [{ username, email }, onChange, reset] = useInputs({
    username: '',
    email: ''
  });

 

참조 

https://react.vlpt.us/basic/21-custom-hook.html

728x90
반응형

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

React.js | 입문 | React.memo  (0) 2021.09.17
React.js | 입문 | useCallback  (0) 2021.09.17
React.js | 입문 | useReducer  (0) 2021.09.17
React.js | 입문 | useMemo  (0) 2021.09.17
React.js | 입문 | useEffect  (0) 2021.09.17
728x90
반응형

우리가 이전에 만든 사용자 리스트 기능에서의 주요 상태 업데이트 로직은 App 컴포넌트 내부에서 이루어졌었다.

상태를 관리 할 때 useState말고 useReducer라는 Hook 함수도 쓰인다. 이 함수를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리하는 것이 가능하다.

 

우선 reducer가 무엇인지부터 알아보자. reducer란 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환

해주는 함수이다.

function reducer(state, action) {
  // 새로운 상태를 만드는 로직
  // const nextState = ...
  return nextState;
}

return되는 값이 곧 새로운 상태가 된다.

 

여기서 action은 업데이트를 위한 정보를 가지고 있다. 주로 type 값을 지닌 객체로 사용한다.

// 카운터에 1을 더하는 액션
{
  type: 'INCREMENT'
}
// 카운터에 1을 빼는 액션
{
  type: 'DECREMENT'
}
// input 값을 바꾸는 액션
{
  type: 'CHANGE_INPUT',
  key: 'email',
  value: 'tester@react.com'
}
// 새 할 일을 등록하는 액션
{
  type: 'ADD_TODO',
  todo: {
    id: 1,
    text: 'useReducer 배우기',
    done: false,
  }
}

 

 

자 그럼 useReducer의 틀을 일단 만들어보자.

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  return state;
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { users } = state;
  const { username, email } = state.inputs;

  return (
    <>
      <CreateUser username={username} email={email} />
      <UserList users={users} />
      <div>활성사용자 수 : 0</div>
    </>
  );
}

export default App;

 

 

그 다음 onChange를 구현해보자

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { users } = state;
  const { username, email } = state.inputs;

  const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, []);

  return (
    <>
      <CreateUser username={username} email={email} onChange={onChange} />
      <UserList users={users} />
      <div>활성사용자 수 : 0</div>
    </>
  );
}

export default App;

 

 

먼저 useReducer와 상태들을 선언해주고,

const [state, dispatch] = useReducer(reducer, initialState);
  const { users } = state;
  const { username, email } = state.inputs;

 

 

그 다음 reducer에 관한 함수를 정의해주고,

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      };
    default:
      return state;
  }
}

 

 

onChange 함수에 dispatch 함수를 넣어준다.

  const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, []);

 

dispatch함수로 action 에 type, name, value를 넣어서 reducer를 실행해주면 return으로 최신 상태가 반환하게된다.

 

 

이번엔 추가, 수정, 삭제를 구현해보았다.

import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';

function countActiveUsers(users) {
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user => user.active).length;
}

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false
    }
  ]
};

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      };
    case 'CREATE_USER':
      return {
        inputs: initialState.inputs,
        users: state.users.concat(action.user)
      };
    case 'TOGGLE_USER':
      return {
        ...state,
        users: state.users.map(user =>
          user.id === action.id ? { ...user, active: !user.active } : user
        )
      };
    case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.id)
      };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const nextId = useRef(4);

  const { users } = state;
  const { username, email } = state.inputs;

  const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    });
  }, []);

  const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    nextId.current += 1;
  }, [username, email]);

  const onToggle = useCallback(id => {
    dispatch({
      type: 'TOGGLE_USER',
      id
    });
  }, []);

  const onRemove = useCallback(id => {
    dispatch({
      type: 'REMOVE_USER',
      id
    });
  }, []);

  return (
    <>
      <CreateUser
        username={username}
        email={email}
        onChange={onChange}
        onCreate={onCreate}
      />
      <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
      <div>활성사용자 수 : 0</div>
    </>
  );
}

export default App;

 

참조

https://react.vlpt.us/basic/20-useReducer.html

728x90
반응형

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

React.js | 입문 | useCallback  (0) 2021.09.17
React.js | 입문 | 커스텀 Hook  (0) 2021.09.17
React.js | 입문 | useMemo  (0) 2021.09.17
React.js | 입문 | useEffect  (0) 2021.09.17
React.js | 입문 | 배열에 항목 수정 및 제거  (0) 2021.09.17

+ Recent posts