728x90

Dropzone이란 라이브러리가 존재하고, 이를 React hook방식으로 사용하도록 만든 것이 react-dropzone이다.

설치

npm install --save react-dropzone
yarn add react-dropzone
### 목적
프리뷰가 있고, Drop한 이미지를 삭제할 수 있어야한다.

먼저 react-dropzone의 Preview 코드를 가져온다.

import React, {useEffect, useState} from 'react';
import {useDropzone} from 'react-dropzone';

const thumbsContainer = {
  display: 'flex',
  flexDirection: 'row',
  flexWrap: 'wrap',
  marginTop: 16
};

const thumb = {
  display: 'inline-flex',
  borderRadius: 2,
  border: '1px solid #eaeaea',
  marginBottom: 8,
  marginRight: 8,
  width: 100,
  height: 100,
  padding: 4,
  boxSizing: 'border-box'
};

const thumbInner = {
  display: 'flex',
  minWidth: 0,
  overflow: 'hidden'
};

const img = {
  display: 'block',
  width: 'auto',
  height: '100%'
};


function Previews(props) {
  const [files, setFiles] = useState([]);
  const {getRootProps, getInputProps} = useDropzone({
    accept: {
      'image/*': []
    },
    onDrop: acceptedFiles => {
      setFiles(acceptedFiles.map(file => Object.assign(file, {
        preview: URL.createObjectURL(file)
      })));
    }
  });
  
  const thumbs = files.map(file => (
    <div style={thumb} key={file.name}>
      <div style={thumbInner}>
        <img
          src={file.preview}
          style={img}
          // Revoke data uri after image is loaded
          onLoad={() => { URL.revokeObjectURL(file.preview) }}
        />
      </div>
    </div>
  ));

  useEffect(() => {
    // Make sure to revoke the data uris to avoid memory leaks, will run on unmount
    return () => files.forEach(file => URL.revokeObjectURL(file.preview));
  }, []);

  return (
    <section className="container">
      <div {...getRootProps({className: 'dropzone'})}>
        <input {...getInputProps()} />
        <p>Drag 'n' drop some files here, or click to select files</p>
      </div>
      <aside style={thumbsContainer}>
        {thumbs}
      </aside>
    </section>
  );
}

<Previews />

하나의 파일만 받아오려면 옵션을 넣어야 한다.

const {
        getRootProps,
        getInputProps,
        isFocused,
        isDragAccept,
        isDragReject,
    } = useDropzone({
        onDrop: acceptedFiles => {
            setFiles(acceptedFiles.map(file => Object.assign(file, {
                preview: URL.createObjectURL(file)
            })))
        }, accept: { 'image/*': [], multiple: false }
    });

mutiple: false란 옵션을 이용하면 된다.

### 올린 파일 삭제

제일 어려웠던 부분은 올린 파일을 삭제하는 방법이었다.

파일을 어떻게 삭제할까?

<aside style={thumbsContainer} onClick={() => setFiles([])} >
                    {files.map(file => (
                        <div>
                            <div style={thumb} key={file.name}>
                            <div style={thumbInner}>
                                <img
                                    src={file.preview}
                                    style={img}
                                    // Revoke data uri after image is loaded
                                    onLoad={() => { URL.revokeObjectURL(file.preview) }}
                                    alt="preview"
                                />
                            </div>
                        </div>
                            <div>{file.path}</div>
                        </div>
                    ))
              }
</aside>

파일의 내용을 보여주는 코드에 onClick을 달아 state를 초기화하는 방법이다.

또는 여러개의 파일을 사용한다면, setFiles( files.filter( f => ) )방법으로 하면 된다.

반응형
728x90

https://susuhan.notion.site/Spring-DI-6113d9eefba446c99413d1323abe9276

- 이쁘게 보기 -

 

Spring DI 방법론

글의 목적 필드 의존성 주입과 setter 의존성 주입, 생성자 의존성 주입에 관한 차이를 알아가기

susuhan.notion.site

Spring DI 방법론

A. 문제의 발단

코드 컨밴션이 존재하지 않아, 프로젝트 내에 코드의 일관성이 일치하지 않는 현상이 발생하였다.
그 중 하나로 의존성 주입하는 방식이 거론되어, 의존성 주입 방식의 차이와 실제 오류가 발생하는지를 알아보게 되었다.

의존성 주입 방식에는 크게 3가지가 존재한다.

  1. 필드 의존성
  2. 생성자 의존성
  3. setter 의존성

1. 필드 의존성 주입 (Field Injection)

public class A {
    @Autowired
    private AService aService;
}

2. setter 의존성 주입 (Setter based Injection)

public class A {
    private AService aService;

    @Autowired
    public void setAService(AService aService){
        this.aService = aService;
    }
}

3. 생성자 의존성 주입 (Constructor based Injection)

// 의존성 주입 대상이 한 개일 경우, 생성자에 @Autowired 어노테이션을 붙이지 않아도 된다.
public class A {
    private final AService aService;

    public A(AService aService){
        this.aService = aService;
    }
}

// 의존성 주입 대상이 2개이상인 경우, 생성자에 @Autowired 어노테이션을 붙여야 한다.
public class A {
    private final AService aService;
    private final BService bService;

    @Autowired
    public A(AService aService, BSerivce bService){
        this.aService = aService;
        this.bService = vService;
    }
}

B. 차이점: Bean 주입 순서

1. 필드 의존성 주입

  1. 주입 받으려는 빈의 생성자를 호출하여, 빈을 찾거나 빈 팩토리에 등록한다.
  2. 생성자 인자를 사용하는 빈을 찾거나 만든다.
  3. 필드에 주입한다.

2. Setter 의존성 주입

  1. 주입받으려는 빈의 생성자를 호출하여, 빈을찾거나 빈 팩토리에 등록한다.
  2. 생성자의 인자를 사용하는 빈을 찾거나 만든다.
  3. 주입하려는 빈 객체의 수정자(setter)를 호출하여 주입한다.

🟥 필드, Setter 의존성 방식은 런타임에서 의존성을 주입하여, 의존성을 주입하지 않아도 객체가 생성될 수 있다.

3. 생성자 의존성 주입

  1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만든다.
  2. 찾은 파라미터 빈으로 주입하려는 생성자를 호출한다.

🟥 객체가 생성되는 시점에 빈을 주입하여, 의존성 주입되지 않아 발생할 수 있는 NullPointerException을 방지한다.

C. 순환 참조

A에서 B를 호출하고, B에서 A를 호출하는 관계를 순환 참조라 한다. 이런 경우가 발생하는 일은 한 프로젝트 내에 관리해야하는 클래스가 많아지는 경우 실수로 발생하게 된다.

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

@Service
public class Bservice {
    @Autowired
    private Aservice aservice;
}
  1. 필드와 Setter 방식에서는 빈이 생성된 후에 참조를 하기 때문에 애플리케이션이 아무런 오류나 경고없이 구동된다. 실제 코드가 호출될 때까지 알 수 없다는 의미이다.
  2. 생성자를 통해 의존성 주입할 경우 BeanCurrentlyInCreationException이 발생한다. 순환참조뿐만 아니라 의존 관계에 내용을 외부로 노출 시킴으로 애플리케이션이 실행하는 시점에서 오류를 확인할 수 있다.

D. 코드의 깔끔함

1. 필드 의존성 주입

@Service
public class Aservice {
    @Autowired
    private Bservice bservice;
}

장점: 한 필드에 어노테이션을 붙임으로써 생성자를 만들지 않아 코드의 수가 줄어든다.

단점: 필드가 많아질 경우 한 줄씩 어노테이션이 추가적으로 붙게 된다.

2.생성자 주입과 Loombok

매번 생성자를 만들어서 주입하는 것이 코드의 깔끔함을 저해시킨다. 하지만 Lombok을 이용한다면 생성자를 자동으로 만들어준다.

@RequiredArgsConstructor
@Service
public class Aservice {
    private final Bservice bservice;
}

RequiredArgsConstructor는 final로 선언된 필드를 가지고 생성자를 만들어준다.

E. final 사용

필드나 setter 방식을 이용시에 final 키워드를 사용할 수 없다.

final 키워드 사용 시 런타임 상에서 의존성이 변경되는 가능성을 제거해준다.

F. 실제 코드 실행 시 발생 상황 관찰하기

@Service
public class A {

    @Autowired
    B b;

}

@Service
public class B {

    @Autowired
    A a;
}

@Service
public class A {

    private final B b;

    public A(B b) {
        this.b = b;
    }
}
@Service
public class B {

    private final A a;

    public B(A a) {
        this.a = a;
    }
}

오류가 발생하지 않았다.

앞에서 한 말이 거짓말일까?

2.6.0 버전 특징

기본적으로 순환참조가 발생하지 않는다.

spring.main.allow-circular-references=true

필드 주입 = 속성 추가후 발생 시 순환참조 오류가 발생하지 않고 그대로 동작한다.

결론

  1. 2.6.0 버전 이후부터는 순환참조 오류를 미리 잡아주게 되었다. 순환참조 상의 오류때문에 필드, set방식을 안 쓸 이유는 없다.
  2. 생성자 의존성 주입 방식의 이점은 런타임 상에서 bean 객체가 변경되지 않는 것을 선호한다는 이유만 남은 것 같다. 하지만 런타임 상에서 bean 객체를 의도적으로 변경하는 사람이 존재할까? 하지만 이런 생각은 이전에 순환참조를 안 만들면 된다는 생각과 동일하기 때문에, 간단하게 막을 수 있다면 막아두는 것이 좋다고 생각한다.
  3. 코드의 깔끔함에 있어서 사람들의 견해가 다르다고 생각한다.
    누군가는 어노테이션을 사용함에 있어 깔끔하다고 생각하고, 누군가는 어노테이션이 없는 것에 깔끔함을 느낄 수 있다.
    만약에 생성자 주입이 직접 설정해야하는 번거로움이 걱정된다면 final이 있는 필드만 생성자를 만들어주는 @RequiredArgsConstructor를 사용하면 될 것 같다.
  4. set방식도 구현하는 방식만 다르지 필드 의존성 주입과의 단점과 동일하여 위에 논한 것으로 충분하다고 생각한다.
반응형
728x90

React Query

상태 관리 라이브러리란?


1. 서버 상태 관리 라이브러리

리액트에는 상태 관리 라이브러리의 큰 종류로 서버 상태, 클라이언트 상태가 존재한다.
클라이언트 상태 관리 라이브러리는 리덕스, 리코일, Zustand 등으로 존재한다.

2. 왜 상태 관리 라이브러리를 사용하나?

리덕스를 사용하는 이유:

<아래 글 참조>

https://koolreview.tistory.com/119

서버 상태 라이브러리를 사용하는 이유:

간단히 서버의 데이터를 가져와 관리해주는 라이브러리라 생각하면 된다.

Client vs Server State


Client state

앱 메모리에 유지되고 이를 액세스 하거나 업데이트하는 것은 동기식입니다.

Server state

원격으로 지속되며 가져오기 또는 업데이트를 위해 비동기식 API가 필요함

리액트 쿼리를 사용하면서 생각한 내용:


  • 클라이언트 상태 관리 라이브러리는 컴포넌트를 타고 타고 내려가는 props의 불편함 때문에 사용한다.
  • 서버 상태 관리 라이브러리는 useEffect와 useState 등을 사용하여 가져온 값을 새로운 값으로 유지하는 불편함을 해소하기 위해 사용한다.

용어 정리


 

fetching: 데이터를 가져오는 중

fresh: 데이터가 신선한(오래되지 않은) 상태

  • 서버와 현재 가지고 있는 데이터가 일치한다는 의미입니다.

stale: fresh의 반대 의미

  • 데이터가 오래되어 서버와의 데이터가 다를 것이다 가정한 상태입니다.

inactive: 사용하지 않는 상태

delete: 가비지 컬렉터에 의해 캐시에서 제거됩니다.

캐시란?

  • fetching 통해 받아온 값들을 메모리 상에 적재하는 곳입니다.

staleTime: fresh 상태에서 stale 상태로 변화하기까지의 시간

cascheTime: 캐시 메모리에 데이터가 유지되는 시간

Query: 질의문정도로 이해하면 됩니다.

QueryKey: 캐시 메모리에서 데이터를 꺼내올 때 사용하는 유일 키입니다.

흐름


상태는 총 5단계로, 그림과 같이 흘러갑니다.

특이한 점:

  • staleTime이 0이라면 바로 데이터가 썩은 상태(데이터가 일치하지 않다)라고 가정합니다.

설치


npm install react-query || yarn add react-query

React-Query 초기 설정


Bold 처리된 코드가 추가된 코드입니다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
**import { QueryClient, QueryClientProvider } from 'react-query';**
import App from './App';
import reportWebVitals from './reportWebVitals';

// Query Client를 생성
**const queryClient = new QueryClient()**
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    // Provider에 prop으로 넘겨준다.
  **<QueryClientProvider client={queryClient}>**
    <React.StrictMode>
      <App />
    </React.StrictMode>
  **</QueryClientProvider>**
);

reportWebVitals();

최상단 파일에서 HTML 태그를 QueryClientPrivoider 태그로 감싸주고, client props에 QueryClient를 넘겨줍니다.

데이터 가져오는 방법


UseQuery

import axios from 'axios'
import { useQuery } from 'react-query'

const fetchDataFunction = () => {
    return axios.get('주소')
}

export const example = () => {

        // 방법 1
    const {isLoading, data} = useQuery('text-unique-key', () => {
        // function
        return axios.get('주소')
    })

      // 방법 2
    const {isLoading, data} = useQuery('text-unique-key', fetchDataFunction);

    if(isLoading){
        return <h2>Loading</h2>
    }

    return <>

    </>
}

useQuery를 통해 값을 읽어옵니다. 첫 번째 파라미터인 text-unique-key는 쿼리를 구분하는 키로 사용됩니다.

  • 방법 1과 2의 차이는 함수를 내부에 적거나, 외부로 빼서 사용하는 차이가 존재합니다.

에러 및 로딩 다루기

const {isLoading, isError, error} = useQuery('text-unique-key', fetchDataFunction);

if(isLoading){
    return <h2>Loading</h2>
}
if(isError){
    return <h2>{error}</h2>
}

Query에서 isLoading과 isError를 넘겨주어 각 상태에 따라 반환하는 값을 달리하여 화면을 상태별로 다룰 수 있다.

DevTools 띄우기

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App';
import reportWebVitals from './reportWebVitals';

const queryClient = new QueryClient()
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <QueryClientProvider client={queryClient}>
    <React.StrictMode>

    </React.StrictMode>
    **<ReactQueryDevtools initialIsOpen={false} position='bottom-right' />**
  </QueryClientProvider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
`<ReactQueryDevtools initialIsOpen={false} position='bottom-right' />`

이 코드를 작성하여 초기 오픈을 할지 말지, 어느 위치에 둘 것인지 선택 가능하다

오른쪽 하단에 꽃 모양으로 뜨며, 클릭 시 아래처럼 펼쳐진다.

마우스가 가리키는 곳이 유니크 키이며 클릭 시 하단과 같이 패널이 뜬다.

이 패널에서 액션을 실행할 수도 있고, 데이터에 대한 값에 대해 볼 수도 있다.

Query Cache


같은 데이터를 매번 받아 사용하면 데이터를 받아오기 때문에 로딩이 발생한다. 그렇기에 캐싱 기능을 사용하면 한번 받아온 데이터를 저장하여 페이지를 로딩 없이 사용할 수 있다.

기본적으로 useQuery 이용 시 staleTime은 0, cacheTime 5분으로 설정되어 캐싱은 자동으로 작동한다.

Stale Time

데이터가 자주 변경되지 않는 데이터들은 네트워크 요청을 하여 데이터를 수시로 가져올 필요가 없다. 그렇기에 데이터 요청 수를 줄이기 위해 staleTime을 조절한다.

특이한 개념 윈도 포커스 ( 탭 이동? )

브라우저 탭을 이동하다 돌아오면 오래되어버린 데이터, steal 상태의 데이터를 다시 가져온다. 하지만 staleTime 시간 동안에는 다시 받아오지 않는다.

다시 정리하자면, 기본적으로 캐싱은 5분으로 진행된다. 이는 곧 메모리에 저장된다는 의미이고, Query를 실행하는 페이지에 이동되어도 이미 데이터가 있기 때문에 로딩이 발생하지 않는다.

하지만 stealTime은 기본 값이 0 임으로 데이터를 받아온 순간부터 받아온 데이터는 썩어버린 데이터 취급을 받게 되고, 이에 따라 윈도우 포커스가 변하여 다시 원래 페이지를 가리키면 데이터 다시 받아온다.

캐시 메모리에 저장된 데이터가 cachTime이 지난 이후에 delete가 된다는 사실도 기억하자. 그렇기에 데이터가 삭제된 이후에는 loading이 발생한다.

const { isLoading, data, isError, error, isFetching } = useQuery(
    'text-unique-key',
     fetchDataFunction**,{
       cacheTime: 5000,
       staleTime: 0
     }**);
실제 time 명시는 위와 같이 한다. 하지만 기본 값과 동일함으로 적지 않아도 된다.

useQuery 옵션들

enabled: boolean

쿼리를 자동으로 실행되지 않도록 해주는 옵션

const { data } = useQuery(['todo', id], () => fetch(),{
    enabled: false
})

refetchOnMount: boolean | “always”

데이터가 stale 상태 시 마운트 할 때마다 refetch를 실행하는 옵션
default → true이며, always로 설정 시 마운트 시마다 매번 refetch를 실행한다.

const { data } = useQuery(['todo', id], () => fetch(),{
    refetchOnMount: true | false | "always"
})

refetchOnWindowFocus ⇒ boolean | “always”

데이터가 stale상태일 경우 윈도우 포커싱 될 때마 refetch가 실행된다.
default true, always는 항상 윈도우 포커싱마다 실행된다는 의미이다.

주기적으로 다시 받아오기

const { data } = useQuery(['todo', id], () => fetch(),{
    refetchInterval: 2000, // 2초마다 다시 받아옵니다.
    refetchIntervalInBackground: true, // 포커스를 잃은 경우, 백그라운드에서도 계속 동작하도록
})

OnClick마다 받아오도록 코드 작성

const { data, refetch } = useQuery(['todo', id], () => fetch(),{
    enabled: false,
})

enabled로 false로 만든 후 button의 onClick에 refetch functnion을 넣어서 클릭 시마다 호출하도록 할 수 있다.

Side Effect 처리

  • 일단 화면에 데이터를 보여 주는 용도로 사용
  • 사용자가 경험 측면에서 의미가 있음
export const RQSuperHeroesPage = () => {
    const onSuccess = () => {
        console.log('Perform side effect after data fetching')
    }

    const onError = () => {
        console.log('Perform side effect after encountering error')
    }

    const { isLodading, data isError, error, isFetching, refetch } = useQuery(
                    'super-heroes',fetchSuperHerores,{
                    onSuccess,
                    onError,
                })

onSuccess, onError 옵션에 따라 직접 만든 함수를 실행할 수 있다.

  • 위 onSuccess와 onError가 동일한 이름이기 때문에 추가적으로 작성하는 코드가 없다.(es6문법)

필터링

const { isLoading, data, isError, error, isFetching, refetch } = useQuery(
    'super-heroes', fetchSuperHerores, {
        onSuccess,
        onError,
        select: (data) => {
            const superHeroNames = data.data.map((hero)=> hero.name)
            return superHeroNames
        },
    }
)

select로 데이터를 선택(필터링)해서 사용 가능하다

  • 직접 사용하는 모습

커스텀 훅

따로 파일로 빼두어 사용하는 것을 커스텀 훅이라 한다.

동적 쿼리

id값을 넘겨서 주소 넣어 값을 받아온다.

멀티 쿼리

data의 이름이 겹쳐, :를 이용하여 이름을 바꿔 사용한다.

동적 병렬 쿼리

useQueries를 통해 배열에 map을 이용하여 쿼리 키와 쿼리 함수를 넘겨 사용한다.

쿼리 의존 상태

channelId가 의존 상태(user를 호출받은 다음 id값이 생겨서 의존 관계)이므로 있는 경우에 가져오려면, enabled 옵션을 사용한다.
enabled로 처음에는 false였다 true로 바뀌면서 값을 가져온다.

목록 → 아이템 쿼리 수 줄이기

상품 목록을 가져오고, 상품 상세 페이지 시 두 번의 쿼리 요청이 발생함.

같은 값인데 동일하게 가져오니 비효율적이며, 네트워크가 느린 곳에서는 좋은 성능을 발휘하지 못한다.

그러므로 useQueryClient를 이용하여 QueryClient에서 유일 키를 getQueryData에 넣어 값을 호출하여 사용한다.

Pagenation Query

keepPreviousData 옵션을 이용하여 새로운 데이터 요청 동안 마지막 data 값을 유지한다. 그렇기에 목록이 사라지는 깜빡임 현상을 방지할 수 있다.

무한 쿼리 페이지

외국 사이트의 쇼핑몰 사이트의 more버튼을 계속 누를 때 사용되는 query로 무한으로 요청함.

mutation (업데이트)

성공 시 invalidateQueryies로 값을 버려버린다.(무효화시킨다)

업데이트 시 가져온 데이터 관리

업데이트 쿼리 최적화

각 상태별로 로직을 따로 처리한다.

Axios 편리하게 사용하기

반응형
728x90

왜 우리는 상태 관리를 할까?


아무런 이유 없이 나는 강의에서 사용해서 사용했다 😥


 저는 초반 웹 개발을 배우면서 Vuex(상태 관리 라이브러리)를 사용하니까 사용했습니다. 그래서 상태 관리 라이브러리가 무엇인지 제대로 고민해보지 못한 것 같습니다. 개발을 점점 진행하다 보니 상태 관리가 무엇인지 서버에서 가져온 값은 무엇인지 고민하게 되어 이 글을 작성합니다.

상태 관리란?

상태 관리를 한국어로 할 때 더 직감적으로 이해하는 데 있어 방해가 된다고 생각합니다. 영어로 적을 시 State Management입니다. 여기서 State를 볼 수 있는데 State는 리액트의 useState 훅에서 가리키는 State를 말합니다. 한마디로 페이지나 컴포넌트에서 사용되는 State를 관리하는 라이브러리입니다.

왜 상태를 관리해야 할까?

 리액트나 뷰나 하나의 페이지를 컴포넌트 단위로 나누어 만들고 있습니다. 상위 컴포넌트에서 하위 컴포넌트로 데이터를 넘겨줍니다. 또 한번 더 컴포넌트에서 컴포넌트로 데이터를 넘겨주는 경우도 존재합니다. 그렇기에 점점 컴포넌트 깊이가 길어지면 상태를 관리하는 데 있어 불편함이 존재합니다. 아래 영상을 보면 쉽게 이해할 수 있습니다.

 예를 들어 매번 props로 데이터를 넘겨줘야한다거나, 상위 컴포넌트에서 어떤 값을 넘겨주었는지 기억을 해야한다거나, 원하는 데이터가 형제 컴포넌트에 있어서 상위 컴포넌트에서 값을 두개로 넘겨줘야한다거나 등의 불편을 느낄 수 있습니다. 그래서 나온 것이 리덕스입니다.

https://miro.medium.com/max/700/1*Rwq-0CxITh6DCwW8J9p9pw.gif

왜 리덕스?

 Recoil, Vuex, Ngrx 등 모두 리덕스 개념에서 시작되었으며, 리덕스는 리액트에만 사용하라고 만든 라이브러리가 아닙니다. 위 Gif와 같이 Store란 친구로 Props 대신해 데이터를 뿌려줍니다. 실제 사용하면 Store가 프론트의 DB처럼 느껴집니다.

클라이언트? 서버? 상태 관리?

 우아한형제들에서 발표한 React-Query 소개 영상을 통해 서버 상태 관리 단어를 처음 봤습니다. 그리고 리덕스와 비슷한 라이브러리들이 클라이언트 상태 관리란 것도 처음 들었습니다. 클라이언트 상태 관리? 서버 상태 관리? 이게 무슨 차이일까요?

서버 상태 관리 라이브러리 React-query/Swr

 SWR 라이브러리를 사용해보지 못해 모르지만 react-query는 사용해봐 알고 있습니다. 주식을 생각해보면 주식의 데이터는 값이 계속 변동이 되어야 합니다. 바로 돈과 직결되는 문제가 발생하기 때문입니다. 그렇기에 이 데이터를 서버의 데이터베이스의 상태와 동일하게 유지되어 좋습니다. 그렇기에 주기적으로 서버 데이터를 프론트로 가져와야합니다. 이 과정의 코드 구현을 해소해주는 라이브러리가 서버 상태 관리입니다.

클라이언트 상태 관리 라이브러리란?

 그럼 클라이언트 상태 관리 라이브러리는 무엇일까요? 기존 리덕스를 사용하는 프로젝트들을 보면 action에 axios를 사용하여 값을 업데이트했습니다. 이런 식으로 화면에 보여주는 값들을 action을 통해 가져와 관리하는 라이브러리입니다

왜? 서버 상태 관리 라이브러리가 존재해?

 서버에서 가져온 데이터는 서버에 있는 데이터와 동일할까요? 그럴 수도 있고 아닐 수도 있습니다. 그렇기때문에 주기적으로 데이터를 가져와 서버 상태와 동일하게 유지해야합니다. 그런데 리덕스에서는 이렇게 주기적으로 호출하여 가져오려면 따로 코드를 작성해야합니다. 그렇기 때문에 이를 편리하게 해주는 라이브러리가 나왔습니다.

 우리는 서버에서 가져온 값을 Props로 많이 넘기고 있기 때문에  UI에 사용되는 State보다 서버에서 가져온 State를 많이 사용하고 있다고 생각합니다.

혼용해도 될까? 하나만 써야할까?

 처음에는 리액트 쿼리와 리덕스를 같이 사용하려고 했습니다. 하지만 생각해보니 동일한 데이터를 같이 캐시에 담아두면 메모리 낭비가 발생한다고 생각했습니다. 리액트 쿼리를 사용하는 게 서버에서 값을 가져와 관리하기 편하다고 느낍니다. 그래서 리액트쿼리를 사용하고, 클라이언트 간에 UI State 관리가 힘들어진다면 그때 리덕스를 사용할 예정입니다.(사실 리덕스보다 Zustand를 사용할 생각입니다.)

참고


이미지 참조: How to Pass Data between React Class Components

반응형

'코딩 관련 > 자바스크립트' 카테고리의 다른 글

페이지 재로딩 시 상단 이동  (0) 2023.10.19
Console.log 그렇게 쓰지 마요..  (0) 2023.10.19
React-Dropzone 모듈 만들기  (0) 2022.07.08
군 계산기 만들기  (0) 2018.09.26
Moment js 너란 넘은 정말..  (0) 2018.09.26
728x90

 이전 이집트인의 곱셈에서 이진수를 잘 모르고 있다는 느낌을 받았다. 아마 이진수를 잘 몰라도 코딩은 가능하겠지만, 알면 더 잘할 수 있지 않을까? 아님 남들보다 조금 더 간지나는 코딩이 가능하지 않을까? 이후 이 글자색으로 현실의 수에 비유하여 공감시켜드리겠습니다.

 이진수

  논리회로 책에서 이진수를 아래와 같이 설명한다.

2진수에서는 낮은 전압 레벨(0V)을 0, 높은 전압 레벨(5V)을 1로 각각 대응시킨다.
그와 같이 표현되는 0과 1의 값을 가지는 기본 데이터 단위를 2진 숫자고 하며 간략히 비트라고 부른다.

 우리가 과학 시간에 배운 전구를 키는 회로를 생각해보면, 전구가 켜지면 1, 전구가 꺼지면 0이다. 논리회로를 배웠고, 마이크로 프로세서 수업을 들었음에도 불구하고 전구 하나만 생각했고, 그냥 그게 2진수가 비트다. 비트가 여러개 보여서 바이트고 이런 식으로 생각했다. 단순하게 지식만 익힌 것 같다. 

 지금 생각해보니, 전구가 2개면 우리가 가지는 상태는 4개이다. (전구 = 비트) 전구가 3개면 상태가 8개다. 당연한 이야기를 하고 있다. 10진수에서 100을 10으로 나눠보자. 100/10 = 10이다. 1000을 10으로 나누면 100이다. 패턴이 보인다. 10진수에서 10으로 나눈다는 건 10진수에서 뒤에서 하나 자리수를 뺀 것이다. 예를 123에서 10으로 나누면 12이가 된다. 1000을 10^2으로 나눈다면 뒤에서부터 2자리를 뺀 것이다. 

10진수에서 10^n으로 나눈다는 것은 뒤에서부터 n개 뺀 것(Shift)이다.

 2진수 또한 다를까? 2진수도 동일하다. 8진수도 동일하다. 16진수 또한 동일하다. 2진수에서의 Shift 연산이 2진수이기 때문에 왼쪽으로 움직이면 2배가 되고, 오른쪽으로 가면 2로 나누는 것이 된다. 그리고 이집트인의 곱셈에서 이진수로 41을 표현하였을 때 0010 1001이다. 이집트인의 곱셈에서 비트 1이 존재하는 부분에 따라 a의 2배한 값을 더 했었다. (41은 2^6 + 2^4 + 2^1) 각 숫자의 의미를 제곱의 의미로 가진다고 생각하고, 그 의미를 가진 전구가 켜져있다고 생각하니 왜 41이 저렇게 표현이 되고, a의 2배한 값을 더 한다는 의미를 이해했다. 

2진수에 대해서 알고 있다면 Shift 연산이 왜 2배인지 2로 나누는지 알면서 쓸 수 있지 않을까? 또한 이집트인의 곱셈의 경우 또한 방법을 이용만 하는게 아니라 이해하여 사용할 수 있지 않을까 싶다.

반응형
728x90

이집트 알고리즘은 인류 최초로 기록된 알고리즘 중 하나다. 빠른 곱셈 알고리즘, 빠른 나눗셈 알고리즘이다.

먼저 알아둬야하는 상식은 고대 문명의 알고리즘이기 때문에 자릿수 개념0을 표현하는 방법이 없었다.
자릿수 개념이 없었다는 말이 나중에 나올 이진수와 비슷하다고 생각이 든다.

먼저 곱셈은 1) 1로 곱하기, 2) 1보다 큰 수로 곱하기, 2가지로 정의하여 나눌 수 있다.

1) 1a = a

2) (n+1)a = na+a

 먼저 우리가 알고 있는 일반 상식? "곱셈은 덧셈을 여러번한 것이다"를 구현해보자. 
=> 덧셈을 n-1번 반복해보는 알고리즘(n-1번 반복하니 시간 복잡도는 O(n)라고 알 수 있다.)

우리는 덧셈을 배울 때 결합 법칙을 배웠기 때문에 덧셈의 횟수를 줄일 수 있다.

 중학교 때 배우는 간단한 덧셈의 결합 법칙을 코드에 녹여낸다는 사실 자체가 신비롭다.
다음은 4a = ( (a + a) + a) +a 덧셈을 4번을 한다. 여기서 결합 법칙을 응용한다면, 4a = (a+a) + (a+a), a+a를 한 번하고 a+a끼리 한번 더해주면 총 2번을 한다. 다시 씹어 먹어보자. 4a = (a+a) + (a+a) <= a를 2배로 하고 2배한 만큼 더한다.

우리가 곱셈 시에 a 하나만을 이용하여 곱하는 것은 아님으로 일반적인 식을 n*a라고 했을 때, n을 반으로 줄이고, a를 2배로 키워서 2를 거듭제곱한 횟수만큼 더한 값을 만든다고 할 수 있다. (물론 책에서 이렇게 말한다.)

# 왜 n을 반으로 줄일까?

n = 41, a = 59

1      59
2     118
4     236
8     472
16     944 
32   1888

 이 부분이 제일 어려운 것 같다. 일단 먼저 자릿수 개념이 없던 이집트에서 수를 어떻게 표기하였는지 모르겠다. 또한, 이 알고리즘을 소개한 책에서 이집트에서 어떠한 방식으로 수를 표기하였다고 추가 설명이 없다. 그럼으로 자릿수 개념과 0에 대한 개념이 없는 이집트에서 이진수를 이용하여 표기했다고 가정해보고 41을 2진수로 표기한다.

0010 1001

 위에 비트를 보고 41을 2의 거듭제곱으로 표현했다고 말할 수 있다. 41은 2^6 + 2^4 + 2^1이기 때문이다.
# 책을 보고  이렇게 분석한 것뿐이지 실제로 이런 생각을 스스로 할 수 있을지 걱정이다.

위에서 생각한 결합 법칙을 이용한 알고리즘을 코드로 작성한다면 아래와 같이 할 수 있다.

여기서 덧셈 횟수를 구해보면 #+(n) = ceil(log n) + ( v(n) -1 ) 와 같다. log n의 이유는 재귀함수로 함수가 돌아가며, 한번 돌 때마다 n의 수가 반절씩 줄어들기 때문이고, v(n)은 홀수인 경우의 수를 의미한다. 거기에 n이 1일 때를 빼줌으로 -1을 한다.

알고리즘 개선

이진수로 생각한다는 것은 무엇일까?

이진수의 정의는 0과 1을 이용하여

반응형
728x90

이전 글에서 스택의 고정 관념을 파쇄를 위해서 글을 쓴다고 했다.  이전글  

먼저 백준의 괄호란 문제를 분석해보자!

9012 괄호 문제

괄호 문자열 Parenthesis String은 (, )로만 구성되어있는 문자열이다. 괄호의 모양이 올바르게 구성된 괄호문자열을 VPS라고 부르고, ( )은 기본 VPS이다.. 예를 들어 (())))() ((())), (() 은 VPS가 아니다.

 선입후출! 먼저 넣고 빼면서 생각하는 방법으로 코딩을 한다면 어떻게 될까? 일단 위 문자열 사진처럼 읽으면서, 스택에 다 넣어두고 뒤에서부터 빼면서 쓴다면 내가 데이터를 읽는 흐름이 보라색 선과 같다.

 이렇게 넣은 상태에서 넣은 값을 하나씩 빼면서 생각한다면 어떻게 생각하게 될까? 어떻게 코드를 짜야할지 감이 잡히지 않는다. 왜???? 왜냐면 넣고 빼면서 읽는 것은 단순하게 뒤에서부터 읽는 것과 마찬가지이기 때문이다.

흔한 고정 관념에 따른 생각 

나는 스택 문제를 풀고 있어!, 그래서 스택을 이용해야해(고정관념에 묶인 중), 그래서 스택에 담긴 값을 뺀 것을 다시 넣어주기 위해서 스택을 하나 더 사용해야겠어!!란 생각을 할 수 있다. 이런 경험이 있다면, 고정관념에 묶인 노예란 증거..


스택: 거꾸로 읽기 가능!

스택 없이 거꾸로 읽고 싶다면, 배열과 for문을 이용하여 아래와 같이 코드를 작성해야한다.

스택을 이용한다면?

이로써 스택에 넣고 빼면서 읽는 행위는 거꾸로 읽는 행위란 것을 알 수 있다.

이제 데이터를 원래 흐름대로 읽어보자.


우리는 앞에서부터 읽으면서 ( 일 때 스택에 넣고, )일 때 스택에서 뺀다. 모든 데이터를 다 읽은 뒤에 스택에 아무것도 없다면 올바른 괄호 문자열일 것이다.

만약에 )일 때 스택에 (이 없거나, 스택에 (이 남아 있거다면 올바르지 않은 문자열인 것을 알 수 있다.

상대적으로 처음에 배우는 기초 자료구조인 스택은 특별하지 않다. 하지만 익숙하지 않기 때문에 자유자재로 이용하지 못할 뿐! 다음 글에서는 10799 쇠막대기란 문제로 스택을 응용해보려고 한다.

반응형

'코딩 관련 > c++' 카테고리의 다른 글

백준 1, 2, 3 더하기 5 문제 분석  (0) 2022.08.09
이집트인의 곱셈  (0) 2021.08.24
자료구조 Stack 구현하기  (0) 2021.08.23
백준 2581 소수 코드 포함  (0) 2020.10.10
백준 하노이의 탑 분석 설명  (0) 2020.08.06
728x90

"알고리즘 공부를 하면서 자료구조에 대해서 공부를 해야한다"란 말을 많이 듣는다. 자료구조 라이브러리는 이미 구현되어있는데, 실제 구현 방법에 대해 알고 있는 것이 좋다고 한다. 이유로는 어떻게 돌아가는지를 알기 때문에 실제 구현 시에 도움이 된다고 하는데, 조금 기분이 이상하다.

 우리는 이미 배열이란 자료구조를 애용한다.

 "배열 없이 못 살아"란 말을 할 정도로 배열 자료구조를 많이 사용한다. 조금 코딩하다보면 배열을 찾게 된다. 이처럼 다른 자료구조들도 친근해야하는 것이 중요한 게 아닐까? 친근하다의 기준이 무엇일까? 많이 이용해봤기 때문에 그 자료구조를 적재적소로 이용할 수 있다는 뜻일 것이다. -> 적재적소로 이용하기 위해선 무엇이 필요할까? 어떻게 구현되어있는지 알고 있어야 가능하다.

스택의 개념

스택은 유명한 Stack이란 게임과 같다.

아님 롤에서 나오는 나서스 스택과 같다.

여기서 문제가 생긴다. 스택은 단순히 쌓는 친구야! 맞아! 그리고 처음 쌓은 것은 마지막에 나오지! 바로 선입후출, 후입선출이야!!

그리고.. 내 고정 관념은 선입후출로 굳어졌다.

무조건 쌓는다!란 생각때문에 일단 스택에 데이터를 넣고 본다. 그러니까 스택을 쓴다는 것은 데이터를 넣고 본다. 그리고 빼내면서 사용한다. 선입선출 말 덕분에 방법이 하나로 굳어진다. 넣을 때 연산을 할 수 있지 않을까란? 생각을 해보자. 추후 이어지는 글에서 자세하게 다뤄볼 생각이다.

스택 구현 방법론

스택뿐만 아니라 자료구조를 구현할 때 여러가지 생각이 든다. 1. 내 실력이 좋지 않다. 2. 정해진 방법이 없다.이다. 코딩 시에 정해진 방법이 없으면 더 힘들어진다. 맞는 방법이 없으니까!! 물론 나도 정답을 가지고 있지 않지만, 현재 실력에서의 방법을 정해서 정리해보려고 한다.

1. 정적 배열 이용

 C++은 기억이 가물 가물하지만, 일단 시도한 바에 따르면 배열 길이의 초기화를 배열 선언시 해줘야한다.
Java는 배열을 선언하고 나중에 배열의 길이를 초기화가 가능하다.(와.. 부럽다)

컴파일 에러가 없으니 된다고 본다.

Java로 한번 짜봐야 겠다.

타입이 여러가지로 사용이 가능하니, 배열의 이름을 지어주는 것이 제일 애매하다.

C++로 짠다면 아래와 같이 작성한다.

2.  동적 배열(포인터) 이용

 비주얼 스튜디오에서 위에 올린 클래스랑 이름이 동일하다고 에러 발생!을 알려줘서 이름을 Dstack이라고 바꿨다.
코드를 보면 알겠지만, 동적 생성 부분만 다르지 거의 비슷하다.

정적과 동적의 차이

 정적은 이미 내 스택메모리에 메모리를 쌓는 행위이기 때문에 빠르다. 동적은 내 힙 메모리에 쌓기 때문에 느리다. 실제 알고리즘 문제를 풀 때에 정적을 사용할까? 동적을 사용할까? 의문이다. 
 만약에 회사 면접에 가서 알고리즘 문제를 풀 때 스택을 구현해야한다면 정적으로 구현해야할까? 동적으로 구현해야할까? 모르겠다. 아직 레벨이 부족하기 때문에 모르는 것일까? 


어떤 언어로 구현하는지는 중요하지 않다. 이 자료구조를 배열처럼 익숙해지려면 직접 문제에 적용을 해봐야하니 다음 글에서 문제에서 스택을 어떠한 방식으로 이용하였는지 분석해보자!

반응형

'코딩 관련 > c++' 카테고리의 다른 글

이집트인의 곱셈  (0) 2021.08.24
스택 응용 분석해보기 1편  (0) 2021.08.24
백준 2581 소수 코드 포함  (0) 2020.10.10
백준 하노이의 탑 분석 설명  (0) 2020.08.06
백준 1011 Fly me to the Alpha Centauri 분석  (0) 2020.07.22

+ Recent posts