- 2024. 9. 18.
Next.js 주저리
# Next.js 학습기 React만 사용 하다가 Next.js를 학습하며 간단하게 후기를 남김. 참고로 학습 극 초반의 주저리 입니다. ## 1. SSR CSR와는 다른 방식, 사용자의 요청에 서버에서 랜더링을 전달받음. SSR의 단점들도 꾸준하게 개선되어 ssg(빌드 시 미리 페이지 랜더링), isr(빌드시 미리 랜더링, 일정 주기마다 다시 랜더링) 등의 기술 들을 다양하게 활용할 수 있었음. ## 2. 서버 컴포넌트(RSC) 서버 컴포넌트는 서버에서 실행되는 컴포넌트 이다. 데이터 가져오는 로직을 서버로 이동하여 서버 자체에서 페이지가 구성되어 돌아온다. 서버에서 실행되기 때문에 이벤트 핸들러나 React Hook 뿐 아니라 localstorage, 웹 API들도 사용 할 수 없다. 물론 클라이언트 컴포넌트로 변경하면 사용할 수 있긴 하나 ... 리엑트로 개발하던 입장으로 서버 컴포넌트를 최대한 활용하여 클라이언트 사이드에서 했던 작업들을 구현하는게 아직 쉽지가 않다.
- 2024. 8. 28.
TypeORM @JoinColumn vs @JoinTable
# TypeORM @JoinColumn vs @JoinTable TypeORM을 사용하며 joinColumn, joinTable이라는 두가지의 데코레이터가 존재하는데 궁금해서 간단하게 정리 ### @JoinColumn ```js @Entity() class User { @PrimaryGeneratedColumn() id: number; @OneToMany(() => Post, post => post.author) posts: Post[]; } @Entity() class Post { @PrimaryGeneratedColumn() id: number; @ManyToOne(() => User, user => user.posts) @JoinColumn({ name: "authorId" }) author: User; } ``` > 1.주로 One To One orManyToOne 관계에서 사용됨. > >2.외래키를 명시한 테이블에 추가함. > > 3.위의 경우 Post테이블에 외래키가 추가됨. ### @JoinTable ```js @Entity() class Post { @PrimaryGeneratedColumn() id: number; @ManyToMany(() => TagsModel, (tag) => tag.posts) @JoinTable() tags: TagsModel[]; } ``` > 1.주로 ManyToMany관계에서 사용됨. > > 2.별도의 중간 테이블을 생성함. > > 3.위와 같은 경우 post_tags_tag등의 중간 테이블이 생성됨. <a href="https://stackoverflow.com/questions/30288464/when-should-i-use-joincolumn-or-jointable-with-jpa" target="_blank">[참조]</a>
- 2024. 8. 3.
TypeORM forRoot vs forRootAsync
# TypeORM forRoot vs forRootAsync Nest.js에서 TypeORM을 사용하며 차이점이 궁금하여 정리 ### forRoot ```js @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [], synchronize: true, }) ] }) ``` >## 위의 예시와 같이 사용 > >1.정적인 값을 바로 제공할때 사용. > >2.환경 변수나 외부 설정에 의존하지 않을때 사용. ### forRootAsync ```js @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ type: 'mysql', host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_NAME'), entities: [], synchronize: true, }), inject: [ConfigService], }), ], }) ``` > ## 위와 같이 사용 > >1 비동기적으로 설정을 사용할때. > >2.환경 변수나 설정 파일에서 동적으로 값을 가져와야 할때. > >3.다른 서비스에 의존성이 있을때. ### 정리 forRootAsync는 데이터 베이스의 절정 값에 대해 환경에 따라 동적인 값이 필요 할때 사용한다. 잠깐 테스트 할때 말고는 보통 forRootAsync를 사용한다고 보면 된다.
- 2024. 7. 1.
Nest.js service 주입 관련
# Nest.js service 주입 관련 Nest.js를 사용하며 module, service등을 주입하며 헷갈려 기록. ```js // another.module.ts @Module({ imports: [CommonService], providers: [AnotherService, CommonService] }) ``` 위의 방식은 잘못된 사용 방식이다. another모듈 안에서 commonService를 주입 받아 사용하고 싶었음. commonServ ice를 import 해 왔고, anotherModule에서 사용하기 위해 providers에 등록 하였음. (에러남) 바른 방식: ```js @Module({ providers: [CommonService], exports: [CommonService] }) // another.module.ts @Module({ imports: [CommonModule], providers: [AnotherService] ``` ### 포인트 1.providers: 모듈 내부에서 사용할 서비스 정의 2.exports: 다른 모듈에서 사용할 수 있도록 공개하는 서비스 정의 3.다른 모듈에서 서비스를 주입받고 싶을때 모듈만 import한다. 4.서비스는 모듈을 통해서 공유 되어야 함. 5.imports는 모듈만 받을 수 있습니다 (서비스를 직접 임포트 할 수 없음)
- 2024. 6. 17.
Express 에러 처리에 대하여
# Express Error처리 에러 처리는 간단하게 보면 크게 404 오류 처리와 500 오류 처리가 있다. 404 에러는 사용자가 요청을 잘못 보낸 경우, 500은 그외 모든 서버에서 발생할 수 있는 모든 경우를 처리 한다. 권한이 없거나 기타 다른 이유도 500으로 분류한 이유는 위와 같은 이유라도 서버에 도착해(404가 아니기에) 내부에서 처리하기에 크게 404와 500으로 나누었다. > 404 에러 처리 미들웨어는 모든 라우터 처리가 끝난 후 위치 ```js //* 기본적인 404 에러 처리 미들웨어 형태 app.use((req, res, next) => { res.status(404).send(404 page); }); //* 정해진 라우터에서 어떤 요청도 처리하지 못하였다 //* 페이지가 존재하지 않는다 //* 이런 식으로도 처리할 수 있다. app.get("/*", (req, res)=> { app.listen(process.env.PORT, () => { console.log("Server listening on port ${process.env.PORT}"); }) ``` > 서버 내에서 예상하지 못한 오류가 발생 하였을때 ```js //* 기본적인 처리 방법 app.use((err, req, res, next) => { //* 에러 처리 res.status(500 혹은 다른 처리).send(로직들); }); ``` > ### 에러 처리 미들웨어의 첫번째 인자로 전달됨. > > 1.직접 오류를 발생 시키는 방법 > ```js > app.get("/something", (req, res) => { > throw new Errorr("Someting Wrong!"); > }); >``` > 2.next 함수에 명시적으로 전달하는 방법 > ```js > app.get("/", (req, res, next) => { > const err = new Error("Some Error"); > next(err); > }); > ``` > 3.비동기 작업 에러 처리 방법 > ```js > //* 비동기 작업 내에서 발생한 오류는 자동으로 캐치 되지 않는다. > //* 따라서 next 함수에 명시적으로 전달해 줘야 함 > app.get("/", async (req, res, next) => { > try { > await something() > } catch(e) { > next(e); > } > }) >``` > 3.미들웨어에서 에러처리 미들웨어로 넘기는 방법 > ```js >app.use((req, res, next) => { > //* 미들웨어 로직 > //* ex) cookie 검증 등 > if(!cookie) { > next(new Error("No cookie")); > } > next(); >}); ### 에러 처리 미들웨어의 특징 1.별다른 처리가 없다면 가장 가까운 에러 처리 미들웨어가 호출 된다. ```js app.get("/1", (req, res) => { //* 에러 발생 }); app.use((err, req, res, next) => { //* 여기서 처리 할 수 있다. }) app.get('/2', (req, res) => { //* 에러 발생 }); app.use((err, req, res, next) => { //* 여기서 잡을 수 있다. }); ``` 2.직접 연결 ```js app.get("/some-route", (req, res, next) => { ... next(Err); }); .... app.use('/some-route', (err, req, res, next) => { //* 위의 라우터로 요청된 에러는 여기서 잡을 수 있다. //* 여기서 발생한 오류를 조건부로 처리 하고 //* 나머지 해당되지 않는 오류는 //* 다음 에러 처리기로 전달 할 수 도 있다. if(조건) { 처리 ...} next(db Error) }); ``` 3.오류 처리 미들웨어는 네개의 인자를 받는다(err, req, res, next) 4.오류 처리 미들웨어는 하나 이상 정의 할 수 있다.
- 2024. 5. 25.
UseEffect에 대하여
# useEffect Hook에 관하여 부모 컴포넌트의 상태값 open에 따라 animation효과를 적용하기 위해 useEffectf를 사용하며 궁금증과 알게된 점에 대하여 적어봄. ```js useEffect(() => { setA(true); }, []); useEffect(() => { console.log(a); }, []); // console // false // false ``` 위에 정의된 useEffect에서 상태값 변경 >> 아래 훅에서 검지 하지 못함. ```js useEffect(() => { setA(true); }, []); useEffect(() => { console.log(a); }, [a]); // console // false // false // true ``` 의존성 배열에 추가 하면 감지 하나 리 랜더링이 일어난 후이다.
- 2024. 4. 25.
Polymorphic한 컴포넌트
# Polymorphic한 컴포넌트에 대하여 평소 스타일링은 css-in-js 방식을 선호한다. 그 중에서도 styled-components를 자주 사용했었는데 이번에 emotion을 사용해 보았는데, props로 as라는 속성에 html tag이름을 넣으면 tag가 바뀌게 되는 신기한 경험을 하였다. ```js import { Button } from "@shared/Button"; const App = () => { return ( <div> <Button as="a" href="https://example.com"/> </div> ) } /* 위와 같이 버튼 컴포넌트에 as 속성과 href 속성을 넣게되면 */ /* a 태그와 같이 작동 한다 */ export default App; ``` 신기 하지 않은가....만들어 보자... ```js import { forwardRef, ElementType, ComponentPropsWithoutRef, ComponentPropsWithRef, ReactNode } from 'react'; type ChameleonProps<T extends ElementType> = { as?: T; } & ComponentPropsWithoutRef<T>; type ChameleonComponent = <U extends ElementType = 'div'>( props: ChameleonProps<U> & { ref?: ComponentPropsWithRef<U>['ref'] } ) => ReactNode | null; const Chameleon: ChameleonComponent = forwardRef( <T extends ElementType = 'div'>( { as, ...props }: ChameleonProps<T>, ref: ComponentPropsWithRef<T>['ref'] ) => { const Element = as || 'div'; return <Element {...props} ref={ref} />; } ); export default Chameleon; ``` Props의 타입을 선언해 주고, 다형성을 가질 컴포넌트 자체 함수의 타입도 선언해 줘야 한다고 한다. 그리고 참조한 글에서는 함수타입 정의에서 리턴되는 값이 () => ReactElement | null 이었는데 에러가 발생하여 () => ReactNode | null로 바꿔 주니 에러가 잡혔다. 오늘도 새롭고 즐겁게 하나 배웠다... <a href="https://kciter.so/posts/polymorphic-react-component/" target="_blank">[참조]</a>
- 2024. 4. 10.
Vite 정적 파일 세팅
# Vite 정적 파일 세팅 Vite React 프로젝트를 진행중 assets 폴더의 비디오 파일이 불려오지 않았다.. ### vite 프로젝트의 src/assets를 사용 하는 경우 ```js import fileName from "상대경로/fileName"; const App = () => { ... return ( <> <video> <source src={fileName}/> </video> </> ); } ``` 이렇게 사용하고, ### vite 프로젝트의 public 폴더를 사용하는 경우 ⚠️ assets 폴더를 public 밑으로 위치 시키고 절대경로로 접근해야 함. ⚠️ javascript로 mport 할 수 없다. ```js //* public/assets/... const App = () => { ... return ( <> <video> <source src="/assets/경로/fileName.mp4" type="video/mp4"/> </video> </> ); } ``` 이렇게 사용 하면 작동한다. <a href="https://vitejs.dev/guide/assets.html#the-public-directory" target="_blank"> [참조1] </a> <a href="https://www.answeroverflow.com/m/1195197417025445959" target="_blank"> [참조2] </a>
- 2024. 3. 29.
Vite 폴더 alias 설정
# Vite 폴더 alias ### 폴더 alias 란? 개발 하며 임포트 경로를 보면 상당히 지저분한 경우가 많다... ```js // ex) import Components from "../../../../Components" ``` next.js에서 폴더 alias로 @를 붙여 사용하는 것을 보고 방법이 없을까 찾아 보았다. ### step 1 ```ts // tsconfig.json { "compilerOptions": { ... "baseUrl": "src", "paths": { "@/*": ["*"], "@components/*": ["components/*"], "@shared/*": ["components/shared/*"] }, }, ... } ``` ### step 2 ```js // vite.config.ts export default defineConfig({ ... resolve: { alias: [ { find: '@', replacement: path.resolve(__dirname, 'src') }, { find: '@components', replacement: path.resolve(__dirname, 'src/components'), }, { find: '@shared', replacement: path.resolve(__dirname, 'src/components/shared'), }, ], }, }); ``` step 3 ```js import CustomButton from "@shared/CustomButton"; ``` 이렇게 사용하면 된다. ### vite plugin 활용 방법 ```js npm i -D vite-tsconfig-paths //* vite.config.ts export default defineConfig({ ... resolve: { plugins: [react(), tsconfigPaths()], }, }); ``` 참고로 cra에서 folder alias를 사용하기 위해 eject를 하는 등 복잡하다고 함...
- 2024. 3. 21.
SEO에 대하여...
# SEO 에 대한 고찰... 이 글을 쓰는 이유는 부끄럽고 반성을 위해 작성 하게 되었다... 또한 나와 같은 실수를 겪을 수 있는 다른 개발자를 위해 적어본다... 리엑트 개발을 하며 react-helmet, react-snap 같은 모듈들을 사용하여 빈약한 seo에 대하여 일정 부분 보완할 수 있다 ..,,, 하지만 내 블로그는 검색되지 않았다....그래서 아직 내가 리엑트 seo 최적화에 대하여 모르는 부분이 많아 추후 더 검색을 통해 공부를 할 필요가 있겠다는 생각을 하였다.... 물론 지금도 모르는 부분이 더 많긴 하다만... 도메인을 구매해 seo 처리 후 배포한 페이지는 당연히 검색될 것이라고 생각 하였다.... 큰 오산이었다....시간이 조금 걸리나???....이것도 큰 오산이었다.... 검색엔진에 등록을 해 줘야 했던 것이다.....그렇다.....부끄럽다..... 배포된 node.js 서버에서 찍힌 로그를 보니 여기 저기서 방문 한 흔적이 남아 있었다.... 그래서 딱히 해줘야 할 부분은 없을 것이라고 생각 하였는데....검색엔진에 등록을 해 줘야 했던 것이다..... 부끄럽다.....반성해야지.....등록 방법은.....google search console 키워드로 검색 해보길..... google에 등록하고 난 후 네이버에도 검색이 되는 것인 것이다.....
- 2024. 3. 11.
React 폴더 구조
# React 폴더 구조 ### 계기 항상 리액트 개발을 하며 별거 아닌 것 같지만 스트레스를 받는 폴더 구조... 항상 폴더 구조가 일관성 없이 바뀌고 뭔가 명확하게 정립되지 않아 많은 고민을 해왔음. 마침 제로초님의 프론트엔드 폴더 구조에 관한 유투브 영상을 보고 정리해 보고자 함. ### 기존 ```js // src 하위 assets components ㄴ 공통 컴포넌트 폴더 ㄴ ex) 커스텀 버튼 ㄴ ex) 커스텀 인풋 등 ㄴ pages 폴더와 같은 이름을 가진 폴더 ㄴ 페이지의 하위 구성 요소 컴포넌트(프리젠테이션 컴포넌트) lib ㄴ utils ㄴ hooks ㄴ styles(커스텀 스타일, styled components global styles...) ㄴ api pages ㄴ ex) 로그인 폴더 ㄴ ex) 메인 폴터 ㄴ ex) 블로그 메인 store ㄴ actions ㄴ reducers ㄴ index.ts(combine reducer) ㄴ types.ts //* store에서 사용하는 type들은 최대한 가까이 있는 것을 선호 types ㄴ 전역에서 쓰이는 타입 등 ``` assets폴더는 다들 아실 것이라 생각하고, components 폴더에는 어디에서든 사용할 수 있는 공통 컴포넌트들 page의 주제를 딴 폴더에서는 pages에서 쓰이는 하위 컴포넌트들 lib 폴더에서는 유틸성 함수, 훅, 전역 스타일, api 요청 instance 등 store에서는 리덕스 전역 state 정의 types에서는 앱 전역에서 쓰일 수 있는 타입들 각 컴포넌트에서 부모에게 받는 prop 타입은 각 파일에서 선언 프로젝트를 만들 때 마다 항상 폴더의 구조가 다르고 일관성이 없었고, 그나마 위의 패턴이 velopert님 velog 폴더 구조를 조금 참고해 사용하고 있는 패턴 이었다. 그래도 폴더 구조에 대한 갈증은 마르지 않았다. 찰나에 제로초님의 유투브에서 프론트엔드 폴더 구조에 대한 이야기가 올라왔다. 일명 FSD(Feature-Sliced Design) 라고 한다. FSD 는 크게 레이어(layer), 슬라이스(slice), 세그먼트(segment)의 세 가지로 나뉘게 된다. > # Layer > > 최상위 폴더이며 레이어의 수는 최대 7개 > > app: 초기화되는 곳. 프로바이더, 라우터, 전역 스타일, 전역 타입 선언 등이 정의. 애플리케이션의 진입점 역할. > > //* main.tsx와 같이 전역에서 사용할 수 있는 context로 감싸진 *// > > processes?: 여러 페이지에 걸쳐 있는 프로세스를 처리. 더 이상 사용되지 않지만 가끔 사용. deprecated ! > > pages: 애플리케이션의 페이지. > > widgets: 페이지에 사용되는 독립적인 UI 컴포넌트. > > /* 기존의 layout을 담당하는 폴더 */ > > features?: 비즈니스 가치를 전달. 좋아요, 리뷰 작성, 제품 평가 등. > > entities?: 비즈니스 엔티티. 사용자, 리뷰, 댓글 등이 포함될 수 있음 > > //* 기존의 components 역할을 하는 폴더 *// > > shared: 특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티. UI 키트, axis 설정, 애플리케이션 설정, 비즈니스 로직에 묶이지 않은 헬퍼 등. > > ## ** 규칙 ** > > 하위 레이어에서는 상위 레이어를 호출 할 수 없으며, 하위 레이어는 상위 레이어의 곳곳에 사용되기 때문에 > > 하위 레이어의 변경은 많은 상위 레이어의 부수 효과를 발생하기 때문에 조심 해야 함. > > # 슬라이스 > > 슬라이스라는 하위 디렉토리는 특정 비즈니스 엔티티에 대한 것. 슬라이스의 주요 목표는 코드를 값별로 그룹화하는 것. > > 이 디렉토리에 있는 코드는 직접적으로 공유되지 않아야 함. > > # 세그먼트 > > 각 슬라이스는 세그먼트로 구성. 슬라이스 내의 코드를 나누는 데 도움. > > api - 필요한 서버 요청. > > UI - 슬라이스의 UI 컴포넌트. > > model - 비즈니스 로직, 즉 상태와의 상호 작용. actions 및 selectors가 이에 해당 > > lib - 슬라이스 내에서 사용되는 보조 기능. > > config - 세그먼트는 거의 필요하지 않음. > > consts - 상수. > > ## 공개 API > > 각 슬라이스와 세그먼트에는 공개 API가 있고, index.ts파일에서 필요한 api만 외부로 공개 > > 나머지는 내부에서만 사용될 수 있도록 한다. 이 컨셉의 구조가 아직 정확하게 이해가 가지 않는 부분이 많고, 더 공부를 해 봐야 겠지만, 전에 사용하던 구조와는 다르게 기준이 있고, 규제를 통한 일관성을 통해 정말 레고와 같이 컴포넌트를 조립한다 라는 느낌이 강하게 들었다. 정말 잘 이해하고 사용한다면 좋은 컨셉이 될 것 같지만, 진입 장벽이 높아 명확하게 이해하고 활용하지 않는다면 독이 될 수도 있을 거라는 생각이 들었다. 개인 프로젝트나 소규모 프로젝트에는 뭔가 적합하지 않다는 생각이다. 아직 명쾌하게 해소되지는 않았지만, 조금 더 고민하고 공부해 봐야할 명분이 생긴 것 같다. <a href="https://emewjin.github.io/feature-sliced-design/?utm_source=substack&utm_medium=email" target="_blank">[참고]</a>
- 2024. 2. 28.
fetch api Error 처리
# fetch api Error 처리 ### 발단: fetch api를 활용한 요청 에서의 발생한 상황 ### 문제: try catch문에서 에러가 잡히지 않았다. 바닐라 자바스크립트를 활용해 모듈을 만드는 과정에서 일이 생겼다. 웹 브라우저에서 에러가 잘 표시 되었으나 catch문의 로그에 찍히지 않았다. 구글링을 통해 폭풍 검색을 시작 하여 답을 찾아 내었다. ### 원인: api 설계 fetch api 자체 설계가 그렇게 되어 있던 것이다. > 취득 성공 여부 확인 > fetch() 프로미스는 네트워크에 오류가 있었거나, > 서버의 CORS 설정이 잘못된 경우 TypeError로 거부됩니다. > 그러나 이 두 경우는 권한처럼 설정의 문제고, > 404와 같은 응답은 네트워크 오류가 아니므로 거부하지 않습니다. > fetch()가 성공했는지를 정확히 알아내려면 프로미스의 이행 여부를 확인한 후, > Response.ok 속성의 값이 true인지도 확인해야 합니다. > 상태 코드가 200 이상 299 이하인지 간단하게 확인할 수 있는 불리언 값입니다. 그렇다...MDN에 나와 있었다.... 거부됩니다 라는 것이 키워드 인듯 하다. 살짝 실험해 본 결과 cors에러를 인위적으로 발생 시키면 catch블럭에서 잡아 낼 수 있는 것으로 보아 네트워크 에러와 cors 는 일반적으로 catch블럭에서 에러 처리를 하면 되고, 200 ~ 299 외의 에러는 response객체의 ok 불리언 값을 잡아 에러를 처리하면 될 것이다. <a href="https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch#%EC%9D%91%EB%8B%B5_%EA%B0%9D%EC%B2%B4" target="_blank">[참고: MDN]</a>
- 2024. 2. 15.
참조?에 대한 고찰
# 객체 참조 등에 대한 고찰 ### 계기 불필요 하다고 생각되는 컴포넌트 리랜더링을 발견. ### 상황 리덕스 전역 상태 관리 > 채팅 방을 구분하기 위한 값 참조하는 메뉴 > 다른 페이지에서의 객체 내 다른 값 변경 dispatch > 위에서 변경한 값이 아닌 객체의 다른 값을 참조하고 있는 메뉴 리랜더링 ## ex) ```js const initialState = { //* currentRoom 참조 currentRoom: "", ... //* 아래의 msgs 값을 업데이트 하는 action 발생 //* 위의 currentRoom참조 페이지 리 랜더링 발생 msgs: [] }; ``` 리덕스는 상태를 받아와서 사용 할 때 기본적으로 최적화가 잘 되어 있어 필요한 상태가 변경될 때에 리랜더링 될 수 있다고 알고 있었는데...암튼... 일단 구글링한 결과와 찾아보며 조금 공부한 결과 원인은 상태 객체 참조 부분에 있는 것 같았다. ### 원인 ```js //! 타입은 생략 //! rootReducer //! 예시 객체 //! combineReducers({ //user: userReducer, //post: postReducer )}; // 예를 들어 스토어가 이렇다고 치자 { user: { name: "" , age: 0, role: "user" , ... }, post: { .... } } // 상태 참조 하는 컴포넌트 // age의 변경에 따른 리랜더링이 필요한 컴포넌트라 치자 const Age = () => { const { age } = seSelector((store) => store.user); return <span>{age}</span> }; // 다른 컴포넌트에서 name만 업데이트 하는 action dispatch // Age 컴포넌트 리랜더링!!!!! ``` 평소에 위와 같은 방식으로 상태를 참조 하였었다. 눈치가 빠른 분이라면 벌써 눈치 채셨을 수도 있을 것이다. 그렇다....구조 분해 할당 요놈이 문제였던 것이다... > 구조 분해 할당은 구조화된 배열과 같은 이터러블 또는 객체를 비구조화, 파괴하여 1개 이상의 변수에 개별적으로 할! 당! 하는 것을 말한다. > [참고]: 모던자바스크립트 DeepDive 즉 변수를 새로 만들어 값을 할당 하는 것이다. useSelector에서 반환한 값을 새로운 변수에 할당하기에 주소값이 달라진다. 구조 분해 할당으로 인해 새로운 주소에 store의 필요한 값이 할당이 된 것이다. 주소 값이 바뀌었기 때문에 컴포넌트에서 참조하던 값이 변경되지 않았음에도 컴포넌트가 참조하는 상태의 주소값이 다른 곳을 바라 보고 있기 때문에 값이 바뀌었다고 판단되어 다시 랜더링이 일어나는 것이다. 사실 처음 검색 했을때는 딱 아 이거구나 라는 느낌이 있었는데... 글을 점점 쓰면서 정확하게 풀기 위해 검색하고 공부하며 생각하는데 뭔가 조금 혼란스러운 부분이 글을 쓰며 생겼다. 일단은 내가 이해한 부분으로 작성할 것이고, 틀릴 수도 있으니 양해 부탁 드립니다. 오류가 있다면 채팅으로 문의 바랍니다.
- 2024. 2. 9.
React plugin 적용
# React Plugin ### 발단 블로그에 바닐라 js로 제작 중인 plugin을 연결하며 생긴 이슈 1.빌드된 js 파일을 react의 시작인 index.html에 script로 연결.(동작 하지 않음) 처음부터 이런 방식으로 연동할 생각으로 만든 것이라 당황 하였으나, 곰곰히 생각해 보니 결론이 나왔다. React는 빈 html 파일에 동적으로 돔을 생성하여 연결하는 방식이라 html에 연동한 js 파일이 추후에 생성된 돔을 찾지 못하여 발생한 것이었다. 2.useEffect ```js useEffect(() => { const script = document.createElement('script'); script.src = "/src/...."; script.async = true; document.body.appendChild(script); }, []); ``` 돔 생성 및 랜더링 후 동적으로 스크립트 파일 만들어 연결 시키는 방법...역시 실패.. > Uncaught SyntaxError: Unexpected token '<' 라는 오류가 발생하였다...열심히 구글링 결과 js파일을 html구문으로 해석하려고 해 생기는 오류라고 하였다. head에 base경로를 넣어 줘라, 경로를 수정해라, 캐시를 지워라 등등 다양한 방법을 시도하였지만 실패.... 3.cdn 위와 같이 useEffect를 활용하지만 js 파일 src만 외부 cdn에서 가져 와 보아라. 결국 이 방법으로 성공 하였다....참...알다가도 모르겠다....블로그 만들며 홈서버에서 cdn처럼 쓰는 도메인이 있어 다행이지.. cdn연결 하면서도 cors에러를 만났지만 nginx 설정을 통해 해결!
- 2024. 2. 1.
redux 만들어 보기
# 상태관리의 고인물 redux 만들어 보기 갑자기 뭔 redux 만들기???? 현재 자바스크립트 만을 활용해 블로그에 채팅모듈을 만들고 있는 중이다. 순수 자바스크립트 만을 이용해 만들며 많은 고충이 있었는데, 그 중에 하나가 상태 관리였다...ㅋㅋㅋ 리엑트를 사용하며 여태껏 엄청 편하게 상태를 관리하고 있었던 것이다.... 그래서 구글링을 통해 알아 보았고, 생각보다 어렵지 않게 구현 할 수 있었다. ### redux란?? 리덕스는 쉽게 state(상태)를 관리할 수 있는 툴이다. 그냥 상태라고 하기 보다는 전역 상태라고 하는 것이 좋을 것이다. 리엑트로 개발을 해 보았던 분이라면 상태 관리를 하며 느꼈던 부분이 있을 것이다. props로 전달한 state가 props로 또 전달 되고, 전달받은 props가 또 그 자식에게 또 전달되고.... 귀찮은 것을 떠나서...문제는 그 state가 정작 필요하지 않은 자식 컴포넌트에도 전달 되어야 비로소 state가 필요한 마지막 자식에게 전달 된 다는 것이다. 그래서 전역으로 상태를 관리할 수 있는 다양한 툴들이 나왔고, 그 중에 하나가 redux 인 것이다. 전역으로 상태를 관리 할 수 있는 툴들은 redux, zustand, recoil 등 다양하게 존재한다. 자바스크립트 만으로 모듈을 개발하면서 느꼈던 상태관리의 귀찮음과 중요성을 통해 공부할 수 있는 계기가 되었다. ### 왜 rddux???? 1.일단은 사용해 봤던 툴이라 익숙하다. 2.오래된 상태관리 툴이지만, 아직도 많은 프로젝트에서 사용되고 있다. 3.툴을 사용하기 위한 boilerplate code가 많지만, 컨셉이 상당히 마음에 들었다. <<< 이 부분이 상당히 만족 비전공자 출신이고 처음 부트캠프에서 상태관리 툴으로 redux를 배웠었다. 처음엔 그냥 뭔지 모르고 그냥 배웠었고, 그냥 이렇게 사용하는 구나 하고 무작정 따라 하기에 바빳다. 부트캠프 졸업한 이후 개발 업무를 많이 한 것은 아니지만, 조금씩 공부 하면서 redux의 컨셉이 상태관리 함에 있어 상당히 마음에 들었다. ### 흐름 > action의 dispatch >> store update >> subscribe 전달 >> view update or action dispatch 단방향으로 단순하게 흐르는 것이 redux인 것이다. 이러 이러한 action이 발생(dispatch) 하면 나는 store를(reducer에 정의된) 이러 이러하게 업데이트 할거야. store가 업데이트 되면 구독하고 있는곳에 상태가 바뀌었다고 알려 줄거야. 아름답지 않은가????...... 아무튼 redux를 간단하게 구현 해 보자. ### store 만들기 ```js const createStore = (reducer) => { //* initial state는 프로젝트에 필요에 따라 작성 한다. let state = { number: 0 }; const getState = () => ({ ...state }); const dispatch = (action) => { state = reducer(state, action); }; return { getState, dispatch } }; ``` ### reducer 만들기 ```js //* reducer는 이전의 state와 action을 받아 상태를 업데이트 하는 함수이다. //* 상태를 직접 변경하는게 아니라 새로운 객체를 반환해야 한다. //* 이전 객체(원래 state)의 주소값, 업데이트된(새로만든 객체)객체의 주소값이 다르기 때문에 //* 상태가 변경이 되었다고 판단할 수 있다. //* 자바스크립트를 처음 공부할때는 이해하기 힘들 수 있다. import { INCREASE } from "주소"'; const reducer = (state, action) => { switch(action.type) { case INCREASE: //* return {}; 객체 리터럴(새로운 객체 만듦) //* ...state: 원래 state spread를 통해 새로운 객체에 원래의 state 값 복사 //* 원래 객체를 직접 변경한게 아니라 새로운 객체에 값만 붙여 넣은 꼴이라 새로운 객체임 //* 다른 주소를 가리킴 return { ...state, number: state.number + 1 }; ... defualt: return state; } } ``` ### action 만들기 ```js //* 상수 정의(관용적으로 이런 패턴으로 사용하는 것 같음) const INCREASE = "counter_INCREASE"; export const increase = () => ({ type: INCREASE }); ``` ### 상태 업데이트 알리기 ```js const createStore = (reducer) => { //* initial state는 프로젝트에 필요에 따라 작성 한다. let state = { number: 0 }; const listener = []; const getState = () => ({ ...state }); const dispatch = (action) => { state = reducer(state, action); //* 상태를 사용하는 컴포넌트에서 등록한 함수들을 실행 한다. //* 전역 상태가 업데이트 되면 실행될 함수이다. //* 전역 상태가 업데이트 되면 나는 이 컴포넌트를 전역 상태에 따라 이렇게 업데이트 할거야 //* 위의 increase 함수가 실행되면 (type: INCREASE) //* reducer에 의해 전역상태의 number 값이 1 증가 한다. //* subscribe에 등록된 함수가 실행 된다 //* 그 등록된 함수는 html의 number요소를 지금 state number값에 맞게 다시 구조화 할거야. //* 라는 함수를 subscribe에 등록 listener.forEach(fn => fn()); }; //* 각 컴포넌트에서 상태가 업데이트 되면 실행될 함수를 등록한다. const subscribe = (fn) => listener.push(fn); return { getState, dispatch, subscribe } }; ``` ### 사용 하는 곳 ```js //* 리엑트는 보통 main.js??? import reducer from "주소"; export const store = createStore(reducer); //* store의 상태가 필요한 곳 import { store } from "주소"; const state = store.getState(); //* 구독 등록(subscribe) import { store } from "주소"; ... //* html을 만드는 함수 안의 상태 초기화 const state = store.getState(); or //* html을 만드는 class 안의 상태 초기화 class { state = store.getState(); } //* 그 뒤 store.subscribe(함수 혹은 class내 html을 조작하는 함수 등의 로직); //* 위의 store.subscribe의 인자로 넣은 함수는 전역 상태가 업데이트 되면 실행되기에 //* 새롭게 업데이트 된 상태값으로 html을 변경 할 수 있는 함수 등을 넣는다. ... ```
- 2024. 1. 19.
함수의 실행
# execution context 💡 execution context(실행 컨텍스트)란 자바스크립트에서 코드가 실행되는 추상적인 공간 이라고 생각하면 된다. 특정 함수 안에서 실행되지 않는다면, 보통은 Global execution context에서 실행된다. 함수는 호출될때 자신만의 컨텍스트를 가지게 된다. 여기서 중요한 것은 호출될때 이다. context를 만들고 난 뒤에 호출 스택에 쌓게 되고, 쌓인 스택의 가장 맨 위의 함수를 실행 한다. lexical environment 💡 execution context가 생성되고 난 뒤 변수에 할당된 값을 식별할 수 있는 lexcial environment가 생성된다. 실행 컨텍스트 안에서 참조할 수 있는 값들이 들어가 있으며, 정의되지 않았다면 체이닝된 값을 찾아 상위로 올라가 찾는다. Ex) ```js function sum(a) { return function(b) { return a + b; } } const sum5 = sum(5); console.log(sum5(10)); // 15 const sum7 = sum(7); console.log(sum7(7)); // 14 ``` ![image](https://velog.velcdn.com/images%2Fajrfyd%2Fpost%2F1fdc5ee3-7d93-4727-b758-f72997e1626f%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-16%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.56.19.png) Global execution context가 생성되고 lexical environment에는 sum은 function으로 sum5와 sum7은 const로 선언되어 있기 때문에 할당은 되어 있지 않다. 코드를 내려가다 sum5를 선언하며 sum(5)가 호출될때 실행 컨텍스트가 하나 만들어 진다. 렉시컬 환경에는 a 값으로 5가 할당 되어 있고, 비로소 글로벌 컨텍스트에 sum5는 func로 바뀐다. console.log에서 sum5(10)이 실행되면 또 하나의 컨텍스트가 생성된다. 렉시컬 환경의 b값은 10으로 할당되고 return 값으로 15를 반환하고 제거되게 된다. 하지만 중간의 컨텍스트는 const로 선언되었기 때문에 사라지지 않고 a = 5 라는 값을 가지고 계속 존재하게 된다.
- 2024. 1. 12.
Javascript Class 알아보기
# Javascript Class 갑자기 class?? 이유는 javascrip class를 활용한 코딩을 거의 하지 않아 다시 공부해 보려고 한다. 또한, 추후 기회가 된다면 라이브러리를 사용하지 않고 바닐라로 spa 만들기에 대해 포스팅 할 기회가 있다면 class를 활용할 예정. ### 1. 인스턴스 생성해 보기 ``` class Product { //* 인자를 받아 초기화 //* 생략 가능 //* 암묵적으로 this를 반환 한다고 함 constructor(name, price) { this.name = name; this.price = price; }; //* method //* prototype에 등록됨 ProductLog () { ... }; //* prototype에 등록되지 않음 ProductLog2 = () => { ... } //* 메소드를 등록할때 화살표 함수로 등록하게 되면 //* prototype에 등록되지 않기 때문에 //* 상속받은 인스턴스에서는 사용할 수 없음. }; ``` ### 2. field(필드) ``` class Product { //* 필드 //* this.name = name이런 식으로 설정할 필요 없음. name = 'tom'; price = 3000; constructor() { } }; ``` ### 3. 정적 필드와 메소드(static) ``` class Product { //* Class자체에서 접근 가능 //* 정적 메소드는 정적 프로퍼티만 접근 가능 static name = 'tom'; static getPrice {}; constructor() { } }; ``` ### 4. private ``` class Product { #name = "doney; #price = 0; constructor(name, price) { this.#name = name; this.#price = price; }; //* private로 선언되면 접근이나 수정이 불가능 //* 필드에 같이 정의 해야 한다 get getPrice () { return this.#price; //* 클래스 내에서는 접근 가능 } }; ``` ### 5. 상속 ``` class Product { constructor(name, price) { this.name = name; this.price = price; }; intoroduce () { return `${this.name}의 가격은 ${this.price}원 입니다.` }; }; //* extends 키워드로 상속 가능 class Sample extends Product { constructor(name, price, type) { //* 부모의 constructor를 호출 super(name, price); this.type = type; }; // *프로토타입 체이닝으로 introduce라는 메소드를 //* 사용할 수 있음 //* But 재 선언해 오버라이딩 됨. introduce () { return `이것의 타입은 ${this.type}의 한 종류 입니다.` }; //* 부모의 메소드를 호출해 사용 introduce2 () { return super.introduce + ", " + this.introduce(); }; }; class Product { static name = "막걸리"; static price = 5000; static introduce() { console.log(`이것은 ${this.name} 입니다.`); }; }; class Sample() extends Product { static introduce () { super.introduce(); console.log("이것의 종류는 술입니다.") }; }; Sample.introduce(); // 이것은 막걸리 입니다. // 이것의 종류는 술입니다. // 이렇게 사용 할 수 있다. ```
- 2024. 1. 3.
우분투 웹서버 만들기 3
# 우분투 웹서버 만들기 이제 우분투 웹서버 만들기 세번째...⚙️ 오늘은 Let's Encrypt를 활용해 인증서를 발급 받은 뒤 https를 적용해 보자. Let's Encrypt는 https 보급을 위해 무료로 ssl인증서를 발급해 주는 비영리 프로젝트라고 한다. > Let's Encrypt의 인증서 발급 방식은 3가지가 있다. > > 1 Webroot: 배포한 웹서버 내에 인증서를 위치 시키고 nginx에서 읽어오게 하는 방식인듯 하다. > > 2 webserver: Nginx 나 Apach 와 같은 웹서버의 옵션을 설정해 SSL 인증을 실시 > > 3 StandAlone: 가장 간편, 빠르고 안전하지만 서비스를 중단해야 함. > > 4 DNS: DNS의 TXT레코드를 이용해 인증받는 방식 > > 필자는 3번 standalone의 방식을 택했다. > > 서비스를 중단해야 하지만, 서비스의 중단에 별다른 타격이 없고 간편하게 적용 가능해서 선택 하였음. > > ``` > version > ubuntu 20.04.6 LTS > nginx 1.18.0 > certbot 0.40.0 >``` ### 1. certbot && Let's Encrypt 설치 ```bash $ sudo apt-get update $ sudo apt-get install letsencrypt -y //* certbot이 같이 설치 됨 ``` ### 2. 인증서 생성 ```bash //* nginx 서비스 중지 //* service 명령 혹은 systemctl(필자는 service) $ sudo service nginx stop $ sudo service nginx status //* 권한 변경 $ sudo su //* 여러개 도메인 등록 가능 하며 실제 도메인이 존재 해야 한다.(가비아, iteasy 등등에서 구매) $ certbot certonly --standalone -d 도메인주소 -d www.example.me -d dev.example.me -d test.example.me //* 이메일 주소 입력하라면 하고 동의 하라면 한다 //* 이런 문구 나오면 성공 IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem //* 밑에는 기부 어쩌고 //* nginx 재시작 $ sudo service nginx restart ``` ### 3. nginx 설정 ```bash $ sudo vim /etc/nginx/sites-available/default //* 80 port를 https로 redirect server { listen 80; listen [::]:80; server_name _; return 301 https://$host$request_uri; } //* 프로젝트 파일 설정 $ sudo vim /etc/nginx/sites-available/본인서버이름 혹은 프로젝트이름 server { listen 443 ssl; listen [::]:443 ssl; server_name 도메인주소; ssl_certificate "/etc/letsencrypt/live/도메인주소/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/도메인주소/privkey.pem"; ssl_prefer_server_ciphers on; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:설정한포트; } } //* 링크 $ sudo ln -s /etc/nginx/sites-available/프로젝트파일명 /etc/nginx/sites-enabled/프로젝트파일명 //* nginx 설정 오류 검사 $ sudo nginx -t //* nginx 재시작 $ sudo nginx -s reload ``` 필자는 https 적용이 안되어서... 아니 적용은 되었는데 사설 ip 안에서 접속이 되어 외부에서도 접속이 되는 줄 알았는데 접속이 되지 않았다... 정확한 이유는 모르나 허망할 정도로 가까운 곳에 있었다... 필자와 같이 개인 서버 만들때 isp에서 접속을 차단하는 경우도 있다고 한다. 예전에 공인ip 분배기함에서 80 포트는 내부로 포트포워딩 해 놓았는데 443은 안해 놓은 것이 문제였다..... 개인 서버 운영하는 분들은 분배기 포트포워딩 ...신경 써야 한다...
- 2023. 12. 28.
우분투 웹서버 만들기 2
# 우분투 웹서버 만들기 저번 시간에는 ssh 설정 까지 완료 하였습니다 오늘은 nginx 설치 부터 차근 차근 하겠습니다. ### 1. nginx 설치 ```bash $ sudo apt-get update $ sudo apt-get upgrade //* 포트 확인용 $ sudo apt-get install net-tools $ sudo apt-get install nginx ``` ### 2. nginx 실행 ```bash $ service nginx start $ service nginx status // nginx.service - A high performance web server and a reverse proxy server Loaded: loded Active: active (runnng) Active(running) 이라고 나온다면 실행중. //* 이후 사설ip로 접속시 nginx 기본 페이지가 나온다면 성공! ``` ### 3. nginx 설정 nginx 기본 설정 파일 위치는 /etc/nginx/nginx.conf 기본 설정은 놔두고 다른 방법으로 서버 연결 ```bash $ sudo vim /etc/nginx/sites-available/파일이름 //* 설정할 서버 이름 혹은 구분할 수 있도록 파일 이름을 적어 새로운 파일 작성 server { listen 80; listen[::80]; server_name 도메인 주소(있다면) or ubuntu server 사설ip; access_log /var/log/nginx/이름.access.log; error_log /var/log/nginx/이름.error.log; location / { proxy_pass http://192.168.xx.xx:설정한포트(우분투에서 실행되고 있는 서버) //* 위에 설정한 server_name:port로 접속하면 proxy_pass의 주소로 연결 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; proxy_cache_bypass $http_upgrade; } } 저장하면 /etc/nginx/sites-enabled/설정한 파일명으로 자동저장됨 //* 확인 후 적용되어 있지 않으면 아래 명령어 $ ln -s /etc/nginx/sites-available/파일이름 /etc/nginx/sites-enable/파일명 $ sudo nginx -t 명령어를 통해 설정 파일의 오류 여부 확인 $ nginx: the configuration file /etc/nginx/nginx.conf syntax is ok $ nginx: configuration file /etc/nginx/nginx.conf test is successful 이런 메시지 표시되면 ok //* 실패했다면 위의 설정에 브라켓이 짝이 맞는지, 스펠링 틀린게 없는지 확인하고 구글링을 추천 $ sudo nginx -s reload ``` 추후 설정한 사설ip:port로 접속시 개인이 설정한 서버가 작동되면 ok! ⚠️ 페이지가 뜨지 않는 issue ⚠️ 필자는 sk류 인터넷 사용 중이고. 위의 설정 까지 마친 후 로컬 컴퓨터에서 페이지가 접속되지 않는 이슈가 있었다. 여ㄱ이 부분 때문에 한 이틀은 고생한 것 같다. 다양한 원인이 있겠지만은 검색 결과 ISP 측에서 할당받은 공인 ip로의 접속을 사설ip로 접근시 차단 하는 경우도 있다고 한다.(데이터 환경의 휴대폰에서는 접속되며, 로컬 컴퓨터로는 접속 x) * 해결 방법 * 필자 같은 경우 맥북을 사용 중이며 hosts 파일 설정의 변경으로 해결 윈도우는 검색..... ```bash $ sudo vim /etc/hosts /etc/hosts ... 생략 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost //* 중간 아무 곳이나 192.168.xx.xxx 공인ip 혹은 도메인 ... 생략 :wq ``` 이렇게 해결 할 수 있었다. 글이 길어져 다음 편에서..... <a href="https://m.blog.naver.com/pjt3591oo/222242046633" target="_blank">[참고: nginx설정]</a> <a href="https://velog.io/@yomi/Mac-%ED%98%B8%EC%8A%A4%ED%8A%B8hosts-%ED%8C%8C%EC%9D%BC-%ED%84%B0%EB%AF%B8%EB%84%90%EB%A1%9C-%EC%88%98%EC%A0%95-%EB%B0%8F-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0" target="_blank">[참고: hosts파일 설정]</a>
- 2023. 12. 17.
우분투 웹서버 만들기 1
# 우분투 웹서버 만들기 1 남는 컴퓨터를 활용해 개인 서버를 만들고 싶어 시작. 집에 남는 컴퓨터를 활용해 외부에서 접속할 수 있는 서버를 만들고 싶은 분들 참고: 공인ip(isp) => 벽장(H614G) 사설 ip 배분 >> sk 공유기 >> 사설 ip 분배 이런 구조이고 서버를 만든 데스크탑은 벽에서 나온 랜선에 연결되어 있음. 공인 ip - 사설ip(벽) - 사설ip(벽) - 사설ip(데스크탑 웹서버) - 사설 ip(벽) - 사설ip(벽 > 랜 sk 공유기 wifi, iptv...) 사설ip(벽 > 랜 sk 공유기) ㄴ사설ip(macbook wifi) - 기타 사설ip(와파이) 쉽게 이해 될 수 있도록 사진은 나중에 서버 세팅 후 첨부할 수 있도록 할 예정. ### 1. 우분투 설치 우분투 서버를 설치 하고 싶었으나, 설치 과정에 문제가 있고 시간이 오래 걸릴것 같아 데스크탑으로 설치 하였음.(20.0.4) 윈도우 환경에서 우분투 데스크탑 듀얼부팅 등 검색하면 많은 자료를 찾을 수 있기에 생략하겠음. <a href="https://palpit.tistory.com/entry/Ubuntu-%EC%9C%88%EB%8F%84%EC%9A%B0-10%EC%97%90%EC%84%9C-%EC%9A%B0%EB%B6%84%ED%88%AC-%EB%93%80%EC%96%BC%EB%B6%80%ED%8C%85-%ED%95%98%EA%B8%B0%EB%A9%80%ED%8B%B0%EB%B6%80%ED%8C%85-1" target="_blank">참고</a> ### 2. 한글 설정(20.0.4) 우분투 설치시 한글 설정 한 분은 패스. #### 1. Settings > Regions & Language > Input Sources 밑의 + 버튼 클릭 #### 2. Korean 선택 후 add #### 3. 추가 후 English 삭제 후 밑의 Manage installed Languages 버튼 클릭해 설치 #### 4. 터미널에 reboot 명령어로 다시시작(로그오프만 해도 된다고 함) #### 5. 다시 Settings > Regions & Language > Input Sources 밑 + 버튼 클릭 #### 6. Korean(Hangul)선택 후 add #### 7. 기존의 Korean 삭제 #### 8. 키 매핑<a href="https://shanepark.tistory.com/231" target="_blank"> [참조]</a> ### 3. ssh 서버로 원격 접속해 작업 할 수 있도록 ssh 설정 ```bash $ sudo apt-get update # openssh-server 설치 $ sudo apt-get install openssh-server 설치 할래? y # net-tools 설치 $ sudo apt-get install net-tools $ ifconfig - en0 또는 비슷한 키를 가진곳에 - inet 뒷부분에 사설ip주소 나옴(192.168.ㅇㅇ, ㅇㅇ , 127.0.0.1 아님 주의!) - ether 뒷 부분은 기기 mac addr(그렇다고요...) # vim 설치 $ sudo apt-get install vim # ssh 접속 port 설정 $ sudo vim /etc/ssh/sshd_config - Port 22 << 모든 ssh기본 포트는 22이기 때문에 보안을 위해 나만의 port 번호로 수정 - 다른 사람이 접속할 수 없도록(바꿔도 완벽한건 아님) # 변경한 설정 적용 $ sudo systemctl restart sshd # 원격 접속 $ ssh (우분투 설치시 입력한 사용자아이디)@192.168.ㅇㅇ.ㅇㅇ -p (위에서 설정한 포트 번호) - 우분투 설치시 설정한 비밀번호 입력 ``` 다음번에는 nginx 설치 후 본격적으로 서버를 연결해 봅시다.
- 2023. 12. 13.
Web Server vs WAS
# Web Server vs WAS ### Web Server > client web으로 부터 http 요청을 받아들이고 > html 같은 웹페이지를 반환하는 프로그램 > > ex) Aphache, Nginx... > > ### 역할 > 보통 client와 WAS 사이에 위치함. > client에 정적 파일을 제공(html, image, css, js ...) > client로 부터 들어온 동적 요청을 WAS로 전달. > WAS에서 처리한 부분을 다시 client에 전달. ### WAS(Web Application Server) > WAS는 동적 서버 콘텐츠를 수행하는 것으로 일반적인 웹 서버와 구별이 되며, > 주로 데이터베이스 서버와 같이 수행이 된다. > 웹서버와 웹컨테이너가 합쳐진 형태, > > 웹컨테이너: 웹서버에서 요청한 파일들의 수행 결과물을 다시 웹서버로 보내줌. > > ex) Tomcat, Jboss... > > ### 역할 > 다양한 비지니스 로직의 처리가 가능하기 때문에 DB와 연동해 > 동적 컨텐츠를 제공할 수 있다. > 단순한 정적 컨텐츠도 제공 가능함. > WAS는 웹서버의 기능까지 할 수 있는데 왜?? - WAS는 DB조회, 각종 비지니스 로직 처리 등에 집중 하는게 좋다. - WAS가 정적 컨텐츠 요청까지 처리 하면 효율성이 떨어짐.(동적 컨텐츠 처리 지연) * 단순한 정적 컨텐츠 제공은 웹서버에 기능 분리시켜 서버 부하를 방지한다. <a href="https://story.pxd.co.kr/1647" target="_blank">[참고]</a>
- 2023. 12. 10.
React-Select onChange option(isMulti)
# React select ### React-select isMulti onChange 옵션에 대하여... 발단은 onChange옵션으로 들어오는 타입이 정확함에도 > property label does not exist on type MultiValue<>... 라는 오류가 계속 발생 하였다. IDE에 타입 유추로 들어오는 개별 타입도 정확한데 오류가 계속 발생함. 구글링과 래퍼런스가 없어서 고민을 하다 문뜻 떠오른 생각에 해결 하였다, ```js args: MultiValue<{ label: string; value: string}> ``` 위에 MultiValue<{}>가 해결의 열쇠가 되었다. 그렇다 들어오는 값은 개별 객체가 아닌 배열 이었던 것이다.... MultiValue라는 단어에 알아 차렸어야 했는데.... 계속 배열에 속한 객체의 개별 속성에 접근해 오류를 뿜었는데... 차라리 타입[] 형식으로 알려 줬다면 알았을 것을... 탓해 뭐하니...이제라도 알아 다행...
- 2023. 12. 8.
clientWidth & offsetWidth & scrollWidth
# 너비에 대하여... 블로그 페이지를 수정하며 태그에 대한 슬라이드 기능을 개발하며 궁금증이 생겼다. 사용할 때 마다 까먹어 이번 기회에 짧게나마 정리를 해 두려도 한다. ### clientWidth > clientWidth란? > > 쉽게 target box의 내부 길이라고 보면 좋을것 같다. > > border를 포함하지 않은 너비 > > contents + padding ### offsetWidth > offsetWidth란? > > 쉽게 target box의 내외부 길이라고 보면 좋을 겉 같다. > > border를 포함한 너비 > > contents + padding + border ### scrollWidth > scrollWidth란? > > 쉽게 스크롤 할 수 있는 영역 까지의 총 너비 > > 부모 박스 overflow: hidden으로 숨겨진 자식 박스의 총 너비 <a href="https://jsfiddle.net/y8Y32/25/" target="_blank">[참조]</a>
- 2023. 11. 28.
Intl Api
# Intl Api ### 다국어 지원 Api ### * 동기 전 회사에서 글로벌한 서비스를 위해 다양한 언어 및 날짜 숫자 등에 대해 번환이 필요 하였다. 기본은 node 환경에서 i18next모듈로 번역 및 변환을 할 수 있었다. 하지만 i18next 모듈은 한글.json파일을 기준으로 번역된 json파일이 있어야 매칭 번역이 가능하였다. 개발 환경에서는 번역된 다른나라 언어 json 파일을 받기까지 시간이 꾀 걸려 불편하기에 간단하게 변환할 수 있는 방법을 찾게 된 것이 Intl Api 이다. ### * 기본 사용방법 ```js //* Datetime format new Intl.DateTimeFormat().format(); //* Number format new Intl.NumberFormat().format(); //* RelativeTime Format new Intl.RelativeTimeFormat().format(); ``` ### * DateTimeFormat ```js new Intl.DateTimeFormat("ko", { dateStyle: 'full' }).format(new Date()); //* 2023년 1월 19일 목요일 new Intl.DateTimeFormat("ko", { dateStyle: 'long' }).format(new Date()); //* 2023년 1월 19일 new Intl.DateTimeFormat("ko", { dateStyle: 'medium' }).format(new Date()); //* 2023.01.19. new Intl.DateTimeFormat("ko", { dateStyle: 'short' }).format(new Date()); //* 23.01.19. new Intl.DateTimeFormat("ko", { timeStyle: 'full' }).format(new Date()); //* 오전 12시 4분 54초 대한민국 표준시 new Intl.DateTimeFormat("ko", { timeStyle: 'long' }).format(new Date()); //* 오전 12시 5분 39초 GMT+9 new Intl.DateTimeFormat("ko", { timeStyle: 'medium' }).format(new Date()); //* 오전 12:06:02 new Intl.DateTimeFormat("ko", { timeStyle: 'short' }).format(new Date()); //* 오전 12:06 ``` ### * NumberFormat ```js new Intl.NumberFormat('ko', { style: 'percent' }).format(0.5); //* 50% new Intl.NumberFormat('ko', { style: 'currency', currency: 'KRW' }).format(1238214); //* ₩1,238,214 new Intl.NumberFormat('ko', { style: 'currency', currency: 'EUR' }).format(32.12); //* €32.12 new Intl.NumberFormat('ko', { style: 'unit', unit: 'kilogram' }).format(53); //* '53kg' ``` ### * RelativeTimeFormat ```js const rTf = new Intl.RelativeTimeFormat("ko"); rtf.format(1, "day"); //* 1일 후 rTf.format(-1, "quarter"); //* 1분기 전 //* 시간은 직접 계산해 줘야 함. ```
- 2023. 11. 19.
React select library
# React-select library ### 설치 방법 ```js npm i react-select ``` ### 사용방법 ```js //* 사용자가 옵션을 추가 해야 하는 경우 import CreatableSelect from "react-select/creatable"; <> <CreatableSelect //* 다중 셀렉트 옵션 isMulti //* 옵션을 새로 추가 할때 입력한 value값이 들어온다 //* 여기서 서버에 맞게 상태 업데이트 해줌 onCreatebleOption={(value) => setState(value)} value={state => state.map(value => ({label: option 이름으로 사용될 state의 값, value: input value 속정으로 사용될 값}))} //* 옵션을 삭제 하거나 변경할때 객체 값이 들어온다 onChange={(obj) => 여기서 state 값 업데이트 해준다} /> </> //* 옵션을 선택만 해야 하는 경우 import ReactSelect from "react-select"; <> <ReactSelect isMulti onCreateOption value //* 사용자들이 선택할 수 있는 옵션 options={(state) => state.map(option => ({label: option.label, value: option.value}))} onChange /> </> ``` ### 연구 필요⚠️ - 타입스크립트 스타일 오버라이트시 onChange 값으로 들어오는 타입에 문제 생김. - Type Assertion으로 어찌어찌 동작은 하나 연구가 필요함. <a href="https://react-select.com/home" target="_blank">[참조]</a>
- 2023. 11. 9.
Vite 환경변수
# Vite 환경변수 ### 기존의 React 환경변수 ```js //* .env REACT_APP_ENVNAME=value //* 사용처 const env1 = process.env.REACT_APP_ENVNAME; ``` ### * Vite 에서는 ```js //* .env 네이밍은 딱히 관계 없는듯하다 //* 사용처 const env1 = import.meta.env.VITE_ENVNAME; ```
- 2023. 10. 20.
Typescript와 객체
# Typescript와 객체 타입스크립트에서 보통 객체의 타입을 ```js type SomeObjType = { name: string; age: number; gender: "male" | "female"; hobby: Array<string>; }; ``` 보통 이런 식으로 정의한다. 하지만 위와 같은 방법으로 타입을 정의하게 된다면 항상 객체 안에는 name, age, gender, hobby 라는 프로퍼티는 꼭 존재해야 한다. 물론 프로퍼티를 옵셔널(?)로 설정하면 괜찮다. 자바스크립트 프로그래밍을 하다 보면 정의하지 않은 프로퍼티가 필요한 경우가 있을 것이다.. 필요할때 마다 기존 타입을 상속받아 사용할 수 있지만... ### 이렇게 객체에 어떤 프로퍼티가 있을지 명확하게 모르는 경우 사용한다. ``` type Obj = { [key: string]: string | number; }; ``` 이런 식으로 사용하면 OK!(프로퍼티에는 리터럴타입만 허용된다고 함) [주절주절] 동적 타입이 단점이라고 이야기 하지만 그로 인해 유연한 프로그래밍과 재미가 더해 지는 것이라 생각 타입스크립트로 인해 얻는 장점이 많긴 하지만 객체를 다루며 놀이 하는 소소한 재미가 감소 아직 부족한 내가 타입스크립트를 사용하면서도 객체를 결론은 다루는 재미를 느낄 수 있도록 노력해야 겠음
- 2023. 10. 14.
which is not functionally dependent on columns in group by clause
# SQL ONLY_FULL_GROUP_BY ### * 상황 회사에서는 Sequelize ORM 환경에서 로우쿼리와 Op, 프로시져를 통해 쿼리를 날리며 group을 할때는 전혀 발생하지 않았는데 쿼리를 알맞게 작성 하였음에도 오류가 발생 하였다. > which is not functionally dependent on columns in group by clause... > >오류내용 ### * 원인 mysql 5.7 버전 이후 sql_mode에 추가된 설정으로 바른 쿼리를 작성할 수 있도록 추가 된 것 같다. group_by에 명시되지 않은 컬럼을 선택하는 것이 의미적으로 정확하지 않고 표준 쿼리 작성에 어긋난다는 것이다. 여태 바르지 못한 쿼리를 작성 하고 학습해 왔던 것이다... ### * 해결 방법 구글링으로 많은 해결 방안이 나와 따로 적지는 않겠다. 1. 그룹바이에 명시하지 않은 컬럼을 명시한다. 2. sql_mode를 수정한다. 3. 서브쿼리에서 그루핑한 컬럼을 선택한다. 4. 그룹바이에 명시되지 않은 컬럼에 집계함수를 사용한다. 이 정도의 해결 방법이 있는것 같다.
- 2023. 5. 20.
[object object]
# [object object] ### [object object]???? 회사 프로젝트 중 서버에서 받은 중첩 배열 객체를 ejs템플릿에서 사용하려는 와중에 생긴문제 [object object] 가 콘솔에 찍히며 for문 등으로 접근시 [ o b j e c t o b j e c t ] 이런 식으로 스트링으로 출력 되었다 ### * 해결 방법 ```js //* 1. input type hidden 값에 stringify화 해서 저장한 뒤 <input type="hidden" value="JSON.stringify(서버에서 받은 데이터 변수명)" name="serverData" /> //* 2. 자바스크립트로 값을 가져올 떄 parse 해서 가져옴 const data = JSON.parse(document.querySelector("input[name='serverData']").value); ```
- 2022. 12. 11.
Js Reduce
# reduce 💡 고차함수 중 하나인 reduce 잘 사용하고 있었으나, 오랜만에 쓰려고 하면 자꾸 헷갈려 간단하게 정리해 보려고 한다. ### 고차함수란? >- 어떤 프로그래밍 언어의 함수 구현에서 함수를 인자로 넘길 수 있거나 반환할 수 있을 때 함수를 일급 객체(언어 내부에서 값으로 표현되고 전달될 수 있는 자료형)로 취급 하고, 함수를 인자로 받거나 결과로 반환하는 함수를 고차 함수라 한다. [[참고]: 나무위키](https://namu.wiki/w/%EA%B3%A0%EC%B0%A8%20%ED%95%A8%EC%88%98) ### 사용방법 arr.reduce(Callback(1️⃣ accumulator, 2️⃣ currentValue, 3️⃣ index, 4️⃣ array), 5️⃣ -initialValue-) 1️⃣: 콜백의 반환값을 누적 2️⃣: 현재 처리할 값(요소) 3️⃣: 처리할 요소의 index값(optional) 4️⃣: reduce를 호출한 배열(optional) 5️⃣: 누적을 시작할 초기 값(optional: 입력하지 않으면 배열의 첫번째 원소) ```js const arr = [1, 2, 3, 4, 5]; const result = arr.reduce((acc, cur, -idx, arr-) => acc + cur, -적지 않으면 1-) console.log(result) // 15 ``` 값을 합산하는 것 말고도 다른 용도로 사용될 수 있다. > #### 중첩된 배열 평탄화 ```js const arr = [[1,2,3,4],[5,6,7,8]]; console.log(arr.reduce((acc, cur) => acc.concat(cur))) // [1,2,3,4,5,6,7,8] // initialValue 입력 x 따라서 배열의 첫 원소인 [1,2,3,4].concat([5,6,7,8])의 결과가 나온다. ``` #### 배열 내의 원소 갯수 파악 ```js const arr = ['apple', 'banana', 'apple', 'mango', 'watermelon', 'apple']; const fruit = arr.reduce((acc, cur) => { if(cur in acc) { acc[cur]++ } else { acc[cur] = 1 } return acc; }, {}) console.log(fruit); // {apple: 3, banana: 1, mango: 1, watermelon: 1} // 초기값으로 빈 객체 {}를 놓은 뒤 객체의 키값이 존재하지 않는다면 등록, 아니면 증가 ``` #### 배열 내 특정 값 혹은 속성으로 정렬 ```js const info = [ { name: '스타크', age: 52, union: 'avengers' }, { name: '배너', age: 50, union: 'avengers' }, { name: '피터', age: 22, union: 'avengers' }, { name: '스티브', age: 48, union: 'free' }, { name: 'vision', age: '???', union: 'free' }, { name: '완다', age: 32, union: 'free' } ] > const sorted = info.reduce((acc, cur) => { const key = cur['union'] // free or avengers if(!acc[key]) { acc[key] = [] } acc[key].push(cur); return acc; // return 잊지 말자 !! }, {}) ``` ![](https://images.velog.io/images/ajrfyd/post/920b4030-a33a-443a-8f67-2a02791f7ea9/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-18%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.45.31.png) 주의사항❗️ 위 코드에서 if 다음 else에 push하게 된다면 원하는 결과가 나오지 않는다 !! #### 중복 배열 원소 제거 ```js const arr = [1,1,1,22,2,2,3,3,3,34,5,5,]; const newArr = arr.sort((a, b) => a - b).reduce((acc, cur) => { const leng = acc.length; if(leng === 0 || acc[leng-1] !== cur) { acc.push(cur) } return acc; }, []) console.log(newArr) // [1, 2, 3, 5, 22, 34] ``` 이와 같이 다양하게 쓰일 수 있다. 유용한 reduce 사용 방법을 발견하게 되면 추가