기억의 실마리
2024. 1. 10. 23:01

React

Facebook에서 개발된 오픈소스 라이브러리이며

CSR 웹페이지를 개발할 때 주로 사용된다. 컴포넌트 단위로

개발할 수 있어 유지보수, UI 재사용성을 높여 효과적으로 웹페이지를

개발, 운영할 수 있다.

 

React의 주요 특징

1. Virtual DOM, 동적인 웹페이지 구성

React는 메모리에 Virtual DOM(가상돔)을 생성하고 이를 활용하여 이벤트가 발생할 때 마다 실제 DOM과 상태를 비교하여 변경이 필요한 최소한의 변경 사항만 실제 DOM에 반영하여 렌더링되기 때문에 렌더링 효율성이 좋다. 이러한 특징으로 유저와 상호작용하는 동적인 웹페이지 개발 시 자주 채택된다.

 

2. Data Flow

Data Flow(데이터 방향)는 한 방향으로 흐르는 단방향이다. 데이터흐름이 양방향인 경우 어플리케이션의 규모가 커질 수록 데이터의 흐름을 추적하기 힘들고 복잡해지는 경우가 발생한다. 이와 달리 단방향으로 개발된 어플리케이션은 데이터를 추적하기 수월하고 변화에 대한 예측이 가능하여 디버깅이 쉬운 장점이 있다.

 

3. JSX를 활용한 컴포넌트 개발

React를 사용할 때 필수적인 요소는 아니지만 보편적으로 필수적 요소처럼 사용되고 있다. 개인적인 견해로 JSX를 활용하였을 때 가장 큰 장점으로는 "개발의 생산성의 증대"라고 생각한다. JS코드내에서 즉시 UI구현 작업이 가능하고 가독성 또한 좋아지기 때문이다.

 

4. Props & State

Props는 부모 컴포넌트에서 자식 컴포넌트로 전달해 주는 데이터를 의미하며 readonly 타입의 데이터이다. props는 변경이 불가능한 불변데이터이고 props를 전달해 준 최상위 부모 컴포넌트에서만 props를 변경할 수 있다.

State는 컴포넌트 내부에서 선언하여 값을 변경할 수 있어 동적인 데이터를 다룰 때 주로 사용된다. 사용자와 상호작용을 통해 데이터를 동적으로 변경할 때 사용되며 각각의 state는 독립적인 데이터이다.

 

5. Hooks

React가 함수형 컴포넌트를 표준으로 제공하면서 생긴 개념으로, React state와 Life cycle을 연동할 수 있게 해주는 함수를 뜻한다. 대표적으로 useState, useEffect, useCallback, useMemo 등이 있다.

 

마치며...

지금 껏 개발에 대한 포스팅을 해오며 돌이켜 생각해보면 react훅을 제외하면 나의 주력인 react에 관한 포스팅이 단 하나도 없다는 것이 조금 놀라웠다. 기술에 대한 이론적 역량을 보완하기 위해 react의 개념을 복기하며 포스팅하였고 알고 있더라도 포스팅을 하고 안하고의 차이는 꽤 크니 앞으로 꾸준히 노력하며 포스팅해야겠다.

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

[ React - navigate ] useNavigate로 state 전달하기  (0) 2024.04.03
2024. 1. 1. 21:44

변수 값을 어떻게 서로 바꿀 수 있을까?

가장 단순한 방법으로 새로운 변수를 만들고 값을 복사해서 두 변수의 값을

서로 교환 할당하는 방식이 있을 것이다.

쉽게 떠올릴 수 있는 가장 확실한 방법이기도 하다.

 

  • 새로운 변수를 활용한 예제 코드
let data1 = "It's String";
let data2 = 10;

console.log(data1) // "It's String"
console.log(data2) // 10

let copyData1 = data1;
data1 = data2;
data2 = copyData1;

console.log(data1) // 10
console.log(data2) // "It's String"

 

이러한 올드한 방식 말고 또 하나의 값을 교환하는 현대적인 방법이 있다.

바로 구조 분해 할당을 활용하여 값을 교환하는 방법이다.

 

 

구조 분해 할당

ES6부터 사용 가능하며 배열을 변수로 분해할 수 있도록 하는

특별한 문법이다. 몇가지 예제코드를 통해 쉽게 이해할 수 있다.

 

  • 구조 분해 할당 예제 코드
// 기본적으로 활용하는 방식
let arr = ["Ze", "Riong"];
let [firstName, surname] = arr;

console.log(firstName); // Ze
console.log(surname);  // Riong

// 필요하지 않은 데이터는 , 를 사용해서 건너뛰기가 가능하다.
let array = ["A", "B", "C", "D", "E"];
let [a, , c, ,e] = array;

console.log(a) // "A"
console.log(c) // "C"
console.log(e) // "E"

// 할당 연산자 우측에 배열 뿐 아니라 모든 이터러블(반복 가능한 객체)이 올 수 있다.
let [a, b, c] = "all";
let [on, tw, th] = new Set([1, 2, 3]);

console.log(a, b, c) // a l l
console.log(on, tw, th) // 1 2 3

 

이렇게 구조 분해 할당에 대해 이해했다면 대충 감이 올 수도 있다.

아래 예제 코드를 통해 어떻게 변수 값을 교환할 수 있는지 알아보자.

 

 

구조 분해 할당을 활용한 변수 값 교환

변수는 이터러블 구조가 아닌데 어떻게 구조 분해 할당을 활용할까?

답은 간단하다. 변수를 배열에 넣어 이터러블하게 만들고 재할당하는 방식이다.

백문이 불여일견!

 

let str = "string";
let num = 230;

console.log(str) // "string";
console.log(num) // 230;

// 배열 내부에 변수를 넣어 이터러블 구조를 만들고 swap
[str, num] = [num, str];

console.log(str) // 230;
console.log(num) // "string";

 

 

마치며...

운이 좋게 서류합격을 하고 기술 면접을 보았는데 두 변수의 값을 서로 할당해주기 위한 방법을 서술해보라는 질문이 있었다. 나는 올드한 방식으로 새로운 변수를 선언하고 값을 할당하는 방식으로 설명하였고 면접이 끝나고 찾아보니 구조분해할당을 활용하는 트렌드한 방법이 존재했었다... 분명 당시의 질문 의도는 이러한 방식을 활용한 변수 값 교환에 대해서 설명하길 바랐을 것이다. 앞으로도 좀 더 다양한 방식에 대한 자료를 찾고 이해하기 위해 노력해야겠다.

2023. 11. 21. 00:31

Webpack

webpack은 Node.js 런타임 기반의 웹어플리케이션을 구성하는 자원을 모듈

단위로 번들링 해주는 도구이다. 프론트엔드 개발 트렌드로 자주 사용되는

라이브러리, 프레임워크(React, Vue 등)는 프로젝트 파일의 크기가 큰 경우

대다수이기 때문에 webpack을 통하여 파일들간의 의존성 관계를 정리하고

코드를 최적화하여 하나의 스크립트 파일로 만들어 주로 최적화된 서비스를

제공하기 위해 사용된다.

 

Webpack의 장점

  • 코드를 축소하여 최적화하고 사용하지 않는 코드를 제거하여 빌드하는 방식으로 성능을 최적화할 수 있다.
  • CSS가 아닌 SASS 혹은 stylus, Typescript 사용 시 컴파일 과정에서 필요 플러그인을 추가하고 번들러를 실행해준다.
  • 종속성 문제의 해결

 

Webpack의 단점

  • 일부 패키지들을 loader를 통해 매번 추가해주어야 하며 추가하고자 하는 자원의 타입에 따라 추가해 주어야 하는 loader가 다를 수 있기 때문에 러닝커브가 다소 있는 편이다.

 

Install

# 웹팩, 개발자 서버 인스톨
npm i -D webpack webpack-dev-server

# 필요한 플러그인 및 로더 설치 (css, sass, static copy plugin, env 등)
npm i -D html-webpack-plugin dotenv-webpack css-loader copy-webpack-plugin cross-env source-map-loader sass-loader clean-webpack-plugin

웹팩(개발자 서버 포함)과 필요한 로더와 플러그인을 설치한다.

 

webpack.config.js 분리

html / css / vanillaJS 구성으로 웹팩 적용하였으며 config 작성할 때 common, dev, prod로

나누어 빌드환경에 맞춰 별도로 관리해주었다.

 

webpack.common.js

공통으로 적용 될 config를 작성한다.

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const DotEnv = require('dotenv-webpack');

module.exports = {
	entry: {
		main: path.resolve(__dirname, '../src/script.js'),
	},
	output: {
		path: path.resolve(__dirname, '../public'),
		filename: '[name].min.js'
	},
	// target: ['web', 'es5'], // es5 환경 작업 시 사용
	module: {
		rules: [
			{
				test: /\.js$/,
				loader: 'babel-loader',
				exclude: /node_modules/
			},
			{
				test: /\.js$/,
				enforce: 'pre',
				use: ['source-map-loader'],
			},
			{
				test: /\.s?css$/,
				use: [
					'style-loader',
					'css-loader',
					'sass-loader'
				]
			},
			{
				test: /\.(glb|gltf)$/,
				use: ['file-loader']
			},
		]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: path.resolve(__dirname, '../src/index.html'),
			filename: 'index.html',
			minify: true,
		}),
		// origin 그대로 복사할 파일을 지정
		new CopyWebpackPlugin({
			// 빌드 시 public 폴더에 자동 생성(patterns 경로에 해당 파일이 없으면 에러발생)
			patterns: [
				{ from: path.resolve(__dirname, '../static') },
				{ from: path.resolve(__dirname, '../src/style.css') },
			]
		}),
		// dotenv-webpack패키지를 통한 환경변수(env) 사용
		new DotEnv({ path: '.env' }),
	]
};

 

 

webpack.dev.js

webpack.common.js에 추가적으로 개발자 서버에서만 적용될 config를 작성한다.

const path = require('path')
const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')

module.exports = merge(
    commonConfiguration,
    {
        mode: 'development',
        devServer: {
            liveReload: true,
            static: {
                directory: path.resolve(__dirname, '../public')
            },
            // hot: true,
            watchFiles: ['src/**/*'],
            port: 3000,
        },
    }
)

 

webpack.prod.js

webpack.common.js에 추가적으로 빌드 시 적용될 config를 작성한다.

const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');

module.exports = merge(
    commonConfiguration,
    {
        mode: 'production',
        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    exclude: /node_modules/,
                    use: ['babel-loader']
                },
                {
                    test: /\.s?css$/,
                    use: [MiniCssExtractPlugin.loader, 'style-loader', 'css-loader', 'sass-loader']
                }
            ]
        },
        optimization: {
            minimizer: [
                new TerserPlugin({
                    terserOptions: {
                        compress: {
                            // 빌드 시 콘솔로그 drop
                            drop_console: true
                        }
                    }
                })
            ],
            splitChunks: { chunks: 'all' }
        },
        plugins: [
            new CleanWebpackPlugin(),
            new MiniCssExtractPlugin(),
            // build 과정에서 환경변수 사용 가능하도록 설정
            new webpack.DefinePlugin({
                'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), // 환경변수 접근 필수 지정!
                'process.env.FIREBASE_API_KEY': JSON.stringify(process.env.FIREBASE_API_KEY),
                'process.env.FIREBASE_AUTH_DOMAIN': JSON.stringify(process.env.FIREBASE_AUTH_DOMAIN),
                'process.env.FIREBASE_PROJECT_ID': JSON.stringify(process.env.FIREBASE_PROJECT_ID),
                'process.env.FIREBASE_SENDER_ID': JSON.stringify(process.env.FIREBASE_SENDER_ID),
                'process.env.FIREBASE_APP_ID': JSON.stringify(process.env.FIREBASE_APP_ID),
                'process.env.FIREBASE_MEASUREMENT_ID': JSON.stringify(process.env.FIREBASE_MEASUREMENT_ID),
            })
        ]
    }
)

 

package.json

scripts를 수정하여 webpack을 적용한다.

{
  
  "scripts": {
    "start": "webpack serve --open --config ./bundler/webpack.dev.js",
    "build": "webpack --config ./bundler/webpack.prod.js"
  },
 
}

 

 

마치며...

주로 react를 create-react-app을 통해 보일러플레이트를 설치하여 개발을 진행하였고 webpack은 create-react-app을 통해 함께 설치되며 최적화에 도움을 준다는 기본적인 개념만 알고있었다.

 

이렇게 웹팩을 직접 프로젝트에 도입하며 경험해보니 그 동안 create-react-app을 통해 react 보일러플레이트를 설치하고 이미 구성되어있는 편리한 환경속에서 webpack을 사용하고 있었다는 것을 깨달았다.

 

생각했던 것 보다 높은 자유도 때문인지 러닝커브를 느꼈던 것 같고 특히나 환경변수를 적용하는 것이 가장 어려웠다...

 

항상 그렇듯 알고나면 별거 아니지만 그 과정은 험난했고, 이 후에 문제를 극복하며 따라오는 만족감은 이로 말할 수 없는 것 같다. 아직도 갈 길이 멀었음을 깨달았고 다시 한 번 더 열심히 정진해야겠다는 다짐을 굳히는 시간들이었다.

2023. 11. 8. 00:07

1. Three.js

WebGL엔진을 기반으로 브라우저에서 3D 그래픽 구현

보다 쉽고 직관적으로 생성, 관리할 수 있도록 제공해주는

라이브러리 이다. 기본적인 WebGL은 점, 선, 삼각형을 그리는

단순한 시스템을 가지고 있으며 Three.js를 사용하지 않아도

모든 그래픽 구현이 가능하지만 코드의 복잡도는 비교 조차

안될만큼 복잡해지고 관리 또한 난이도가 올라가기 때문

아직 시도할 엄두가 나지 않지만 추 후 시간을 내어 다뤄봐도

좋은 경험이 될 것 같다.

 

2. Three.js 구조

 

Renderer

Scene, Camera 객체 데이터를 전달 받아 화면 내부 3D Scene의

일부 객체들을 2차원 평면 이미지로 렌더링해주는 핵심기능을 수행한다.

 

 

Scene

다수의 Mesh, Light, Group, Object3D, Camera로 이루어진 트리 구조이다.

배경색, 안개, 다수의 오브젝트, 빛, 질감 등을 표현하는 화면이다.

Scene은 최상위 노드이기 때문에 객체의 위치와 방향이 부모(Scene)기준이다.

 

 

Camera

Camera는 다른 구성 객체와는 달리 Scene 그래프에 포함되지 않으며

Scene객체를 촬영하여 어떻게 보여줄 것인가를 결정하는 역할을 한다.

 

 

Light

여러 종류의 광원을 말하며 이는 즉 조명을 뜻한다.

AmbientLight, SpotLight, DirectionalLight 등 다양한 광원을 활용하여

Scene에 존재하는 다양한 3D요소들을 3D 공간 공간에서 볼 수 있다.

 

 

Geometry

3D요소의 모양을 정의할 수 있으며 내장객체 또는 파일을 통해 형상을 만들 수 있다.

 

 

Material

질감을 뜻하며 표면의 색상, 투명도, 질감을 나타낼 수 있다.

 

 

Mesh(Object3D)

Mesh는 특정 Material의 속성을 가진 Geometry를 그리는 객체이다.

Mesh는 3D공간 상의 위치와 특정 기준 축 회전 등을 결정할 수 있으며

Material과 Geometry는 재사용이 가능하여 여러 Mesh가 특정

Material, Geometry를 동시 참조 가능한 특징이 있다.

 

 

마치며...

오늘은 Three.js의 기본 구성과 개념에 대한 포스팅만을 진행했지만 현재 Three.js를 사용한 프로젝트가 완성에 가까워졌고 완성된 이후에 Three.js에 대한 추가 포스팅을 진행할 예정이다. 언제나 개발을 통해 기능을 구현하는 것도 중요하지만 가장 중요한 것은 사용한 기술에 대한 개념, 원리를 명확히 이해하고 있는 것이라 생각하고 있고 나에게는 하나의 원칙이기도 하다. 앞으로도 근거 있는 기술의 사용과 검증 가능한 코드를 지속하기 위해 더 나은 노력할 것이다.

2023. 7. 9. 18:18

getServerSideProps

주요 기능은 특정 페이지의 index에서 해당 컴포넌트가 렌더링 되기 전에

Pre-Rendering(SSR)에 필요한 데이터를 해당 컴포넌트의 props를 통해서

데이터를 전달해 줄 수 있는 기능이다.

 

언제 사용하면 좋을까?

많은 경우가 있겠지만 가장 적합하다고 생각하는 상황은 응답받은 데이터를

html에 모두 포함된 상태로 seo최적화되어 렌더링되어야하고 동적으로 데이터가

자주 변경될 여지가 있고 반드시 최신화된 데이터를 보여주어야 하는 경우이다.

 

예시 소스코드

// pages/video/index.tsx

export interface VideoList {
    id: string;
    title: string;
    publishedAt: string;
    thumbnail: string;
    channelId: string;
    channelTitle: string;
    channelThumbnail: string;
    free: boolean;
    category: string;
}

export const getServerSideProps: GetServerSideProps<{ data: VideoList[] }> = async () => {
    const data: VideoList[] = [];

    const res = await getFirebaseData(); // 파이어베이스 데이터로드
    for (const row of res.data) {
        const youtube: any = await getYoutubeVideoData(row.id); // argument = videoId in FB
        const channelData: any = await getYoutubeChannelData(youtube.snippet.channelId);
        const newData = {
            id: youtube.id,
            title: youtube.snippet.title,
            publishedAt: youtube.snippet.publishedAt,
            thumbnail: youtube.snippet.thumbnails?.medium.url, // data = youtube "video" api
            channelId: youtube.snippet.channelId,
            channelTitle: youtube.snippet.channelTitle,
            channelThumbnail: channelData?.snippet.thumbnails.default?.url, // channelData = youtube "channel" api
            free: row.free, // from firebase
            category: row.category, // from firebase
        }
        data.push(newData);
    }

    return { props: { data } }
}

export default function Home({ data } : InferGetServerSidePropsType<typeof getServerSideProps>) {
    const element = useRef<CustomScroller>(null);

    return (
        <div className='relative w-full h-full overflow-hidden box-border bg-primary-dark-400 '>
            <header className='w-[375px] max-mobile-md:w-full fixed max-mobile-md:top-0 bg-primary-dark-400 z-10'>
                <NavBar />
                <CategoriesList />
            </header>
            <main className='absolute bottom-0 w-full h-[calc(100%-135px)] overflow-hidden'>
                <div className="relative w-full h-full">
                    <CustomScroller ref={element} universal={true}>
                        <MediaList data={data}/>
                        <button
                            type='button'
                            className="fixed bottom-16px right-16px"
                            onClick={() => {
                                if (!element.current) return
                                element.current.scrollTop();
                            }}
                        >
                            <ScrollTopIcon/>
                        </button>
                    </CustomScroller>
                </div>
            </main>
        </div>
    )
}

직접 진행했던 프로젝트에서 사용했던 코드이다. 통신해야 할 부분을 getServerSideProps에서 통신을 모두 마친 후에 메인 컴포넌트인 Home컴포넌트의 props로 데이터를 보내주었고 MediaList컴포넌트의 props로 통신완료된 데이터를 전달해주고있다.

 

Github: https://github.com/zeriong/side-project

 

유의할 점

getServerSideProps를 사용할땐 반드시 해당페이지의 메인 컴포넌트 파일(index.tsx)에서만 사용가능하다.

 

 

마치며...

처음에 ssr기능이 그저 next.js프레임워크를 사용하기만하면 되는 줄 알았는데 전혀 아니였다. 항상 새로운 프레임워크를 사용할땐 공식페이지에서 Document를 꼭 정독하고 프레임워크가 지향하는 개발의 자연스러운 방향과 기능들에 대해 인지하고 적재적소에 사용할 수 있도록 준비해두는 것이 개발자로서 더 올바른 자세라는 것을 깨달았다.

 

2023. 3. 25. 18:24

Tailwind CSS

Utility First를 지향하는 CSS프레임워크다.

Utility First란 미리 세팅된 유틸리티 클래스를 활용하여 HTML코드 내에서 CSS를 적용시키는 것을 뜻 한다.

CSS의 각 속성들을 직관적인 className으로 표현하여 효율적으로 사용할 수 있게 된다.

일반적으로 필요한 대부분의 CSS속성을 className으로 제공한다.

 

Tailwind CSS의 장점

내가 생각한 가장 큰 장점은 개발을 진행함에 있어 스타일에 대한 CSS className을 고민하지 않아도 되고

Style Sheet를 들여다보지않고 적용되어있는 className으로 보면서 변경, 적용시키기가 손쉬운 점

뽑고 싶다. 이 외에도 여러 장점이 있다.

  • Break Point가 존재하여 반응형 디자인을 쉽게 적용할 수 있다.
  • 기본으로 제공되는 class외에도 직접적으로 커스터마이징을 하여 새로운 CSS속성을 만들어 적용할 수 있다.
  • 반복되는 스타일은 component, class를 추상화 하여 재사용이 가능하다.
  • TailwindCSS Document는 비교적 친절하게 기능을 서술하고 있으며 러닝커브가 낮다.
  • 다크모드를 지원하기 때문에 따로 CSS속성을 지정하는 수고를 덜 수 있다.

 

Tailwind CSS의 단점

내가 생각한 가장 큰 단점은 코드의 가시성이 떨어지는 것과 속성들의 우선수위문제이다.

직관성이 좋은 반면에 가시성이 떨어지는 것이 실수하기 쉬운 요소로 다가오기도 한다.

또한 속성의 우선순위가 출현빈도나 순서에 상관없이 정의한 순서에 따라 적용되기 때문에

유의하며 className을 적용시켜야한다.

  • 정의된 CSS파일의 용량이 매우 커진다. 하지만 반대로 component가 늘어나도 CSS파일 용량이 상대적으로 늘어나지 않는다는 장점이 존재한다. 규모가 큰 프로젝트일수록 용량에 대한 혜택을 볼 수 있다.
  • 특정 prefix는 모든 CSS속성을 지원하지 않는다. hover: 또는 focus: 등 특정 prefix는 일부속성만 사용가능하다.

 

install & init

npm install -D tailwindcss
npx tailwindcss init

Tailwind CSS를 설치 후 적용시킨다.

 

 tailwind.config.js

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

모든 템플릿파일에 대한 경로를 추가하여 config를 적용시킨다.

tailwind.config.js파일은 기본적인 설정 외에도 설정을 커스텀하여 적용시킬 수 있는 config파일이다.

 

아래의 예시와 같이 적용시킬 수 있다.

/** @type {import('tailwindcss').Config} */

// tailwindCSS에서 px정의가 따로 되어있지 않기 때문에 px로 적용하는 경우를 대비한 함수
const createPxEntries = (size) => {
    return {...Array.from(Array(size + 1)).reduce((accumulator, _, i) => {
            return {...accumulator, [`${i}px`]: `${i}px` }
        })
    };
}
// tailwindCSS에서 보다 직관적으로 rem단위를 사용할 수 있도록 font-size: 16px기준으로  px과 동일한 숫자로 통일한 함수
const createRemEntries = (size) => {
    return {...Array.from(Array(size + 1)).reduce((accumulator, _, i) => {
            return {...accumulator, [i]: `${i * 0.0625}rem` }
        })
    };
}

module.exports = {
    content: [
        './src/**/*.{js,jsx,ts,tsx}',
    ],
    theme: {
        extend: {
            screens: {
            
            	// 다양한 mobile-size에 대응하기 위해 추가적인 media-query 생성
                'mobile-md' : '450px',
            },
            fontSize: {...createPxEntries(50), ...createRemEntries(50)},
            
            // spacing은 길이에 대한 모든 className에 적용시킨다.
            spacing: {...createPxEntries(100), ...createRemEntries(100)},
            colors:{
                'primary-300': '#EB5500', // 자주쓰이는 컬러를 지정
                'primary-200': '#FF7A00',
                'primary-100': '#FFE0B0',
            },
        },
    },
    plugins: [
        require('@tailwindcss/line-clamp'), // plug-in 적용도 가능하다.
        require('@tailwindcss/aspect-ratio'),
    ],
}

파일 자체가 js파일이기 때문에 추가적으로 함수를 만들어 적용시킬 수도 있다.

함수를 만들어 적용시켜 줄 때 주의사항은 용량이 커지기 때문에 코드작성시 검색기에 부하가 걸려 느려질 수 있다.

위에서 spacing의 경우 특히나 많은 속성에 관여하기 때문에 필요한 만큼만, 가능하면 적은 수를 적용하는 것이 좋다.

 

globals.css

tailwindCSS 만으로 style속성을 주기 난해한 경우에 쓰일 CSS파일을 만든 후

tailwindCSS의 속성을 같이 사용할 수 있도록 만들 수 있다.

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

 

아래 예시처럼 애매하거나 속성지정이 난해한 경우 @apply를 사용하여 CSS에 tailwindCSS속성을 같이 지정 할 수 있다.

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

input[name=tradeOrder]:checked + label{
    @apply bg-crypto-pale-grey text-crypto-dark transition-all duration-300
}

 

사용예시

import {useNavigate} from "react-router-dom";
import rocket from "../assets/rocket.svg";
import React from "react";

export const HomeIntroContents = (props: {img:string, title:string, summary:string, opt?:string}) => {
    const navigate = useNavigate();
    return (
        <li
            className="cursor-pointer flex w-full p-12 rounded-[16px] bg-crypto-pale-grey text-start text-crypto-dark"
            onClick={() => {return props.img === rocket ? navigate('trades') : navigate('wallets')}}
        >
            <figure className="relative w-52 h-52 rocket-bg rounded-[12px] rocket-gradient mr-16">
                <img src={props.img} className={`${props.opt}`} alt=""/>
            </figure>
            <div className="relative flex flex-col justify-center">
                <h1 className="text-16 font-normal">
                    {props.title}
                </h1>
                <p className="font-normal text-14 text-crypto-cool-grey">
                    {props.summary}
                </p>
            </div>
        </li>
    )
}

className내에서 백틱과 중괄호를 사용하여 `${isShow ? 'visible' : 'invisible'}` 와 같은 조건문을 넣거나 함수를 포함시켜 사용할 수도 있으며 tailwindCSS에 지정된 className이 없는 경우에는 w-[16vh] 와 같이 대괄호를 사용하여 사용자지정 CSS를 적용시킬 수도 있다.

 

마치며...

나는 tailwindCSS의 존재를 알게 된 이후로 사실상 style적용은 모두 tailwindCSS로 하고 있다. 처음엔 낯설게 느껴졌지만 프로젝트를 진행하면서 지속적으로 사용하고 사용성에 대해 저울질을 해본 결과 tailwindCSS의 압승이었다. 앞으로도 채택할 수 있다면 tailwindCSS를 채용하여 프로젝트를 진행할 예정이다.

2023. 2. 15. 22:11

Typescript

Javascript를 기반으로 하는 오픈소스이며 Type을 추가하여 올바른 입출력값의 Type을

미리 가이드할 수 있고 해당 Type이 아닌경우 ide가 에러를 일으키기 때문에

문제를 원천적으로 방지할 수 있다.

 

 

Typescript에 대한 견해

분명 대부분의 개발자들이 코딩을 입문한지 얼마 안됐을때 나와 비슷한 생각을 했을 것이다. "그냥 코드간결하고 실행 잘되고, 가독성 좋으면 그만이지 코드 난잡해지게 굳이...?"하지만 이 생각은 단 하나의 Toy프로젝트를 만들고 달라지게 되었다.

 

가장큰 이유는 뜻밖의 에러, 의도치 않은 결과가 실행되는 경우가 적지 않기 때문이었다.

이러한 경우 나는 코드의 Type 문제가 90%였고 내가 혼자서 만드는 Toy프로젝트만 해도 이런데

만약 현업을 하게된다면...? 이건 엄청난 기여를 하겠구나 하고 생각하게 되었다.

특히나 코드의 양이 방대해진다면 나라면 고민할 것도 없이 채택해야겠다! 라고 생각했다.

 

타입스크립트를 사용하지 않는 가정하에 현업을 하여 방대한 코드를 여럿이서 공유하며

개발을 한다고 가정해보면 내가 직접 의도하여 만든 함수나 변수가 아니기 때문에

하나하나 추론을 해 나아가며 return되는 값의 Type이나 함수에 매개변수로는

어떤 Type이 들어가야하는지 등을 알아내기 위한 시간비용은 무시할 수 없다.

 

이런 문제는 Typescript의 사용만으로 크게 개선될 수 있고 엄청난 시간을 절약할 수 있게 된다.

나에게 이것 만으로도 Typescript를 사용해야하는 이유로 충분해졌다.

 

 

 

Typescript Install

 

1. npm install

npm install -g typescript

기본적인 타입스크립트의 인스톨 방식

 

 

2. tsconfig.json

//tsconfig.json 파일을 만들고 아래 코드입력

{
  "compilerOptions" : {
    "target": "es5",
    "module": "commonjs", 
  }
}

Typescript를 사용하기 위해서는 반드시 tsconfig.json파일이 존재해야한다.

 

 

3. ts -> js 변환

//터미널에 입력 (basic javascript의 경우만)
tsc -w

 

 

4. React Typescript 생성

//react와 마찬가지로 node 버전이 14, 혹은 그 이상인 경우만 가능.
npx create-react-app@latest my-app --template typescript

 

 

5. Next Typescript 생성

npx create-next-app --ts
//이 후 "What is your project named?" 메시지가 뜨면 프로젝트 이름을 입력하면 된다.

 

 

 

사용예시

let name :string = "문자만"
//string, boolean, number, null, undefined, bigint, [], {} 등 있다

let name :string[] = ["kim", "park"]
//name변수엔 array에 string인 값만 할당 할 수 있다.

let NAME :{ name : string } = { name : 'kim' }
//NAME변수에 할당된 key(name)속성에 값은 string만 올 수 있다.

let 이름 :{ name? : string } = { name : 'kim' }
//이름변수에 name이란 속성이 올지말지 확실하지 않을땐 물음표를 사용해서
//있을수도 있고 없을 수도 있는 옵션인 타입으로 지정해줄 수 있다.

let name :string | number = 'kim';
let name :string[] | number = [ 'kim', 123 ]

//이런식으로 or기호를 넣어주면 타입 중 하나만 들어가도
//에러가 뜨지 않는다.
//Union type 이라고도 한다.


type myType = string | number;
let name :myType = 123;
//이렇게 타입변수를 만들어줘서 사용도 가능하다.


function FUC (x :number) :number {
    return  x * 2
}
FUC( "123" );
//이런 경우 매개변수타입은 number이고 리턴값도 number여야 하기때문에
//에러가 뜬다. 반드시 number Type으로 적어줘야 제대로 작동한다.

type Member = [number, boolean];
let john:Member = [ 123, false ];
//array타입을 만들때 예시이다.
//tuple타입이라고 한다.


type Member = { name : string };
let john :Member = { name : 'kim' };
//이렇게 name이라는 key엔 스트링이 들어가야한다.

//만약 name속성 외에도 age, address 등등 여러 속성이 필요할 경우
//아래와 같이 대표적으로 지정이 가능하다.
type Member = { [key :string] : string };
let john :Member = { name : 'kim', age : '123', address };


class User {
    name :string;
    constructor(name :string){
        this.name = name;
    }
}
class 타입지정도 할수 있다.

 

 

마치며...

사용예시의 경우 간단하고 대표적인 예만 명시해 두었지만 실제로 프로젝트를 진행하면서 더욱 다양하고 많은 Typescript를 사용하고 있다. 다행이 document에서 서술이 잘 되어있고 여러 사람들이 사용하는 만큼 타 기술블로그에서도 이해하기 쉽게 서술하고 있어 러닝커브가 크지 않았던 것 같다. 앞으로도 잊지말고 typescript 해야겠다.

2023. 2. 5. 22:23

자바스크립트 연산자의 우선순위

연산자의 우선순위란?

간단하게 사칙연산을 예로 우선순위를 생각해보면 10 + 10 + 5 * 5 = 이 경우

우리는 곱하기를 우선적으로 계산하고 나머지 계산식을 차례대로 더하게 될 것이다.

만약 위의 식을 (10 + 10) + 5 * 5 = 이렇게 바꾸게 된다면 5 * 5와 10 + 10의 우선 계산순위가

동일하게 바뀌듯이 자바스크립트 연산자에서도 연산자 간에 우선순위라는 것이 있다는 것이다.

 

 

※ 연산자 타입, 연산자 순으로 정렬해두었다.

  • 멤버
    .[ ]

  • 객체생성과 호출
    ()new

  • 거짓(부정),증가와 감소
    !,  ~,  -,  +,  ++,  --,  typeof,  void,  delete

  • 곱셉, 나눗셈, 나머지
    *,  /,  %

  • 덧셈,뺄셈
    +, -

  • 비트 시프트
    <<, >>, >>>

  • 관계
    <, <=, >, >=, in, instanceof

  • 등호
    ==, ===, !=, !==

  • 비트논리곱
    &

  • 비트 배타적 논리합
    ^

  • 논리 곱
    &&

  • 논리 합
    ||

  • 조건
    ?:

  • 할당
    =, +=, -=, *=, /=, %=, <<=, >>=, >>>=, <<<=, &=, ^=, |=

  • 콤마
    ,

 

대입 연산자 :  =

우변의 계산 값을 좌변에 대입할때 사용한다.

const a = 1, b = 5;
a = b; //a에 b값인 5를 대입
console.log(a); // 5

 

(연산기호) 대입 연산자 :  +=, -=, *=, /=, %=, **=

이해하기가 난해할 수 있지만 쉽게 생각하면 a += b 는  a = a + b 와 같은 식이다.

조금더 간단하게 만든 단축식(short-cut)이라고 생각하면 된다.

// += 더하기 대입연산자
const a = 4, b = 5;
a += b; //a에 a + b의 값을 대입한다.
console.log(a); // 9

// -= 빼기 대입연산자
const a = 4, b = 5;
a -= b; //a에 a - b의 값을 대입한다.
console.log(a); // -1

// *= 곱하기 대입연산자
const a = 4, b = 5;
a *= b; //a에 a * b의 값을 대입한다.
console.log(a); // 20

// /= 나누기 대입연산자
const a = 4, b = 5;
a /= b; //a에 a / b의 값을 대입한다.
console.log(a); // 0.8

// %= 나머지 대입 연산자
const a = 4, b = 5;
a %= b; //a에 a / b의 나머지를 대입한다.
console.log(a); // 4

// **= 지수 대입연산자
//지수승을 말하며 현재값으로는 4의 5승 = 4 ** 5 이다.
let a = 4, b = 5;
a **= b; //a에 a의 b승의 값을 대입한다.
console.log(a); // 1024

//모든 비트연산자도 마찬가지로 작동한다.

 

 

비트연산자 - Bitwise Operator

 

1. 시프트 연산자

시프트 연산자란?

비트를 이동시키는 연산자이다. 즉 10진수를 2진수로 변환하고 2진수로서 연산을 하는 것이다.

시프트연산자는 피연산자가 2개인 2항연산자이며 기호는 <<, >>, <<<, >>> 이 있다.

 

  • ※ 8bit 기준 예시입니다.
0 0 0 0 1 0 1 0

10진수의 정수 10을 2진수로 변환 해줬다.

 

 

<<, >> 설명

우선 >><<에 대해서 설명하자면 방향대로 이진수를 이동시켜준다고 생각하면

이해하기 수월하다.

 

10 >> 2의 경우 1010을 오른쪽으로 두칸 움직이는 것이다. 그렇게된다면 10이 되는 것이다.

오른쪽으로 움직이면서 버려진 2진수의 값은 버려진다.(데이터손실)

0 0 0 0 0 0 1 0

그리고 이를 다시 10진수로 바꿔주면 값은 2가 된다.

10 >> 2 = 2

 

그리고 반대로  10 << 2 의 경우는

0 0 1 0 1 0 0 0

이를 10진수로 바꾸어주면 값은 40이 된다.

10 << 2 = 40

 

이를 통해서 10 * 2의 제곱과 같다는 것을 유추할 수 있다. 즉  a << b = a * 2 ^ b인 것이다.

 

 

 

<<<, >>> 설명

이 연산자는 <<, >>과 다른 것은 음수의 표현이 없다는 것.

즉 시프트연산된 2진수의 값을 절대값으로만 반환한다는 것이다.

 

  • 절대값 시프트연산 예시
let a = -100, b = 2;

console.log(a >> b); //  -25

console.log(a >>> b); //  1073741799

 

2. 그 외 비트연산자

  •  &
    대응되는 비트가 모두 1이면 1을 반환 (비트 AND 연산)

  •  |
    대응되는 비트중에 하나라도 1이면 1을 반환 (비트 OR 연산)

  •  ^
    대응되는 비트가 서로 다르면 1을 반환 (비트 XOR 연산)

  •  ~
    비트가 1이면 0, 0이면 1로 반전시킴 (비트 NOT 연산)

 

&  AND 비트연산 예시

  • 31 & 41  ( 비교대상이 모두 1이면 1 )
0 0 0 1 1 1 1 1
& & & & & & & &
0 0 1 0 1 0 0 1
  • = 9
0 0 0 0 1 0 0 1

 

|  OR 비트연산 예시

  • 31 | 41  ( 비교대상이 하나라도 1이면 1 )
0 0 0 1 1 1 1 1
| | | | | | | |
0 0 1 0 1 0 0 1
  • = 63
0 0 1 1 1 1 1 1

 

^  XOR 비트연산 예시

  • 31 ^ 41  ( 비교대상이 다르면 1 )
0 0 0 1 1 1 1 1
| | | | | | | |
0 0 1 0 1 0 0 1
  • = 54
0 0 1 1 0 1 1 0

 

~  NOT 비트연산 예시

  • ~31  ( 비트를 반전시킴 )
0 0 0 1 1 1 1 1
  • = 224
1 1 1 0 0 0 0 0

 

 

 

논리 연산자

논리연산자란?

연산자에 '논리’라는 수식어가 붙는다. 논리 연산자는 피연산자로 boolean type뿐 아니라

모든 타입의 값을 받을 수 있으며. 연산 결과도 어떠한 타입의 값을 받을 수 있다.

 

|| OR 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자중 하나라도 true인 경우 true를 반환한다.
console.log( true || true );  // true
console.log( true || false );  // true
console.log( false || false );  // false
console.log( false || true || false );  // true

console.log( 1 || 0 );  // true
console.log( 1 || 1 || 0 );  // true
console.log( 0 || 1 );  // true
console.log( 0 || 0 );  // false

피연산자가 불린형이 아닌경우 평가를 불린형으로 변환된다.

예시에서 0과 1같은 경우

 

&&  AND 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자 모두 true인 경우에만 true를 반환한다.
console.log( true && true );  // true
console.log( true && false );  // false
console.log( true && true && true );  // true
console.log( true && false && true );  // false

console.log( 1 && 0 );  // false
console.log( 1 && 1 );  // true
console.log( 1 && 0 && 1 );  // false
console.log( 1 && 1 && 1 );  // true

 

!  NOT 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자를 반전시킨다.
console.log( !true )  // false
console.log( !false )  // true

console.log( !0 )  // true
console.log( !1 )  // false

 

 

증감연산자

증감연산자란?

변수의 값을 1씩 증가시키거나 1씩 감소시키는 연산자이다. 증감연산자는

1씩 증가시키는 Increment연산자1씩 감소시키는 Decrement 연산자가 있다.

 

Increment ++

let num = 0;
num++;
console.log(num);  // 1

/*
num++ 를 풀어쓴다면
num = num + 1;  이다.
*/

Decrement --

let num = 1;
num--;
console.log(num);  // 0

/*
num-- 를 풀어쓴다면
num = num - 1;  이다.
*/

 

 

 

전위 연산자, 후위 연산자

연산자 위치에 따라서 전위(prefix), 후위(postfix)연산자로 나뉜다.

 

전위연산자

let a = 3;
const b = ++a

console.log(a, b);  // 4, 4

연산자가 앞에 붙어있기 때문에 전위 연산자이다.

전위연산자의 특징을 위에 예시로 설명하면 ++a가 b에 할당되기 전에 a가 증가연산이된 후

a자신에게 먼저 할당이 된 이후에  b에 할당된다.

 

전위연산자 풀이

let a = 3;
const b;

a = a + 1;
b = a

console.log(a, b);  // 4, 4

 

 

후위연산자

let a = 3;
const b = a++;

console.log(a, b);  // 4, 3

연산자가 뒤에 붙어있어 후위연산자이다.

후위연산자의 특징을 위에 예시로 설명하면 우선적으로 b에 a가 할당된다.

그 이후에 a = a + 1연산이 이루어지게된다.

 

후위연산자 풀이

let a = 3;
const b;

b = a;  // 먼저 할당되어 b = 3; 인 상태
a = a + 1;  // a = 4

console.log(a, b);  // 4, 3

 

 

증감연산자 대신 복합 대입연산자?

eslint에서는 증감연산자는 자동으로 세미콜론이 추가되는 대상이기 때문에

예상치 못하게 코드의 흐름을 변경시켜 오류를 발생시킬 가능성이 있다고 한다.

그렇기 때문에 eslint에서는 복합대입연산자 사용을 권하고 있는 모양이다.

 

 

 

마치며...

이번엔 자바스크립트 연산자에 대해서 쭉 포스팅을 해보았다. 처음 접해보는 비트연산식이나 전위, 후위 연산자에 대해서 작동형태만 알고 있을 뿐 정확한 작동방식은 모르고 있었는데 이번 포스팅을 통해 더 자세한 로직의 형태를 이해할 수 있게 되었다. 연산자는 코딩을 함에 있어 아주 중요한 내용이니 반드시 index를 기억해두어야겠다.