2024년 2월 2일 금요일

REACT - JSX

회사에서 리액트 스터디를 하게되서..

책을 한권 구매해서 순번을 정해 한 챕터씩 맡아 내용을 정리하기로 했다.

난 챕터 3의 JSX를 정리하게 되서 짧막하게 정리해본다.


배경은?

최근의 웹은 더욱 상호 작용적으로 변하면서 점점 JavaScript 코드가 컨텐츠를 결정하게 되었다.

React가 렌더링 로직과 마크업을 같은 장소, 즉 컴포넌트에 위치시키게 된 것은 흐름을 반영한 자연스러운 결과다. 로직과 마크업을 합치기로 했다면, 이왕에 HTML 구문 그대로를 JavaScript 파일 안에 넣을 수 있으면 더 직관적일 것이다.


JSX : Javascript Syntax eXtension

한줄 정리 : JSX는 JavaScript 파일 안에 HTML과 유사한 마크업을 작성할 수 있게 해주는 JavaScript 확장 문법이다. (HTML과 살짝 다르다.)


그럼 왜 사용하는가?

JSX를 사용하면 코드가 간결해져 보기에 편하다. createElement 함수를 이용해 엘리먼트를 만드는 방법과 비교하면 차이가 극명한데, JSX 방식을 사용하면 JSX 내용을 보고 결과물을 예상하기에도 좋다.


JSX는 HTML보다 조금 더 엄격하게 동작하는데 아래의 3가지 룰이 추가된다.

1. 단일 루트 요소를 반환해야 한다.

 : 여러 개의 요소를 반환하고 싶은 경우 <div> 태그와 같은 것으로 감싸줄 필요가 있다. HTML 트리에 흔적을 남기고 싶지 않은 경우라면 <>와 </>의 빈 태그를 활용해도 된다. (이런 태그를 Fragment라 부른다.) 


2. 태그는 반드시 받아야 한다.

 : <img>, <li>와 같은 태그도 예외가 없다. 반드시 <img />, <li> ~~~ </li>와 같이 닫아주어야 한다.


3. 낙타 표기법으로 작성한다.

 : JSX도 결국 JavaScript 코드로 변환하는 과정을 거치게 된다. JSX로 작성된 속성은 JavaScript 객체의 키가 되는데 JavaScript에는 변수 이름에 제한이 있다. 예를 들면 대시를 포함하거나 class와 같은 예약어를 사용할 수 없는데, 이에 대한 방안으로 React에서는 낙타 표기법으로 속성을 표기하기로 하였다.

stroke-width는 strokeWidth, class는 className 식으로 작성해야 한다.


다행스러운 것은 HTML을 JSX로 변환해주는 온라인 컨버터가 존재한다는 것인데 주소는 다음과 같다.

https://transform.tools/html-to-jsx


누군가는 마크업에 간단한 로직을 포함하거나 동적으로 변하는 프로퍼티를 넣고 싶을 수 있다. JSX에선 중괄호를 이용해 이러한 작업을 할 수 있게 지원한다. (값의 대입 뿐만 아니라 함수의 호출도 지원한다.)

+ CSS나 오브젝트를 전달하고 싶은 경우 '{{ ... }}'의 이중 중괄호로 표현해야 동작한다.



2023년 3월 11일 토요일

Protobuf-net 서버-클라이언트 데이터 송수신 예제

성능 신경쓰지 않고 단순히 데이터 주고 받는데 집중하면 동기 방식의 TCP/IP에 protobuf-net 조합이 가장 간편한 것 같다. 예제는 ChatGPT에서 찾은 것으로 단순하게 메시지 하나를 전달한다. 이 정도 예제만 바로 찾을 수 있어도 접근하기가 얼마나 쉬워지는지. 아쉬운 부분은 회사에서는 ChatGPT가 막혀있다.


아래는 서버 코드. 클라이언트 네트워크 스트림을 대상으로 Serialize를 호출하면 데이터가 클라이언트 쪽으로 전송된다. Protobuf-net을 사용하는 경우 *.proto 파일을 별도로 작성하고, 관리하지 않아도 되기 때문에 사용하기 간편하다.


using System;
using System.Net;
using System.Net.Sockets;
using ProtoBuf;

public class Program
{
    private const int Port = 12345;
    private const int BufferSize = 1024;

    [ProtoContract]
    public class Message
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Text { get; set; }
    }

    static void Main(string[] args)
    {
        var listener = new TcpListener(IPAddress.Any, Port);
        listener.Start();
        Console.WriteLine($"Listening on port {Port}");

        while (true)
        {
            using (var client = listener.AcceptTcpClient())
            {
                var stream = client.GetStream();
                var buffer = new byte[BufferSize];
                var bytesRead = stream.Read(buffer, 0, BufferSize);
                var message = Serializer.Deserialize<Message>(new ArraySegment<byte>(buffer, 0, bytesRead));

                Console.WriteLine($"Received message from client: {message.Text}");

                var response = new Message { Id = 1, Text = "Hello from server!" };
                Serializer.Serialize(stream, response);
                Console.WriteLine($"Sent response to client: {response.Text}");
            }
        }
    }
}

다음은 클라이언트. ProtoContract 클래스는 서로 일치해야 한다.

using System;
using System.Net.Sockets;
using ProtoBuf;

public class Program
{
    private const string Host = "localhost";
    private const int Port = 12345;
    private const int BufferSize = 1024;

    [ProtoContract]
    public class Message
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Text { get; set; }
    }

    static void Main(string[] args)
    {
        using (var client = new TcpClient(Host, Port))
        {
            var stream = client.GetStream();

            var message = new Message { Id = 1, Text = "Hello from client!" };
            Serializer.Serialize(stream, message);
            Console.WriteLine($"Sent message to server: {message.Text}");

            var buffer = new byte[BufferSize];
            var bytesRead = stream.Read(buffer, 0, BufferSize);
            var response = Serializer.Deserialize<Message>(new ArraySegment<byte>(buffer, 0, bytesRead));

            Console.WriteLine($"Received response from server: {response.Text}");
        }
    }
}


2021년 9월 26일 일요일

Visual Studio 2019+ D(서브) 드라이브 설치

Visual Studio 2019 버전을 메인 드라이브인 C 드라이브가 아닌 다른 드라이브에 설치하는 방법에 대해 정리해 본다.


SSD 128 GB 정도의 작은 저장 장치를 메인 드라이브로 사용하는 경우 용량 부족으로 Visual Studio 2019 설치가 어려운 경우가 있는데 윈도우 시스템의 Junction(리눅스 시스템의 Symbolic Link) 기능을 이용해 일부 설치 용량을 서브 드라이브에 할당시킬 수 있다.


어느 정도의 공간 절약이 되냐면 Professional 버전 기준 .NET 데스크탑 개발 환경과 C++ 데스크탑 개발 환경을 설치할 때 12GB 가량 공간이 필요한데 Junction을 이용하면 4GB 정도 공간 절약이 가능하다.


원문 링크 : https://eventhorizon.tistory.com/110


방법>

1. 관리자 권한으로 cmd.exe를 실행한다.

2. 실제 설치가 이루어질 디렉토리를 D 드라이브에 생성한다.

3. 링크를 배치할 디렉토리를 C 드라이브에 생성한다.

4. mklink 명령어를 이용해 링크를 생성한다.

5. Visual Studio 설치를 진행한다.


ex) Professional 기본 설치 경로를 이용하는 경우 예시. 2단계 메인 드라이브에 디렉터리를 생성할 때 'Professional' 디렉토리는 제외해서 생성해야 한다.

1. mkdir -p "D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional"

2. mkdir -p "C:\Program Files (x86)\Microsoft Visual Studio\2019"

3. mklink /j Professional "D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional"




2021년 1월 26일 화요일

[Protocol Buffers] optimize_for 옵션

프로토콜 버퍼 컴파일러(protoc) 옵션으로 사용 목적에 따라 SPEED, CODE_SIZE, 또는 LITE_RUNTIME 값을 지정할 수 있다.


기본 값은 SPEED이다.


• SPEED (기본) : 기본 값인 만큼 프로토콜 버퍼의 모든 기능(Descriptor, Reflection)이 포함되며 최적화된 코드를 생성해 준다.


• CODE_SIZE : SPEED 옵션 대비 작은 코드 사이즈를 갖는 결과 파일을 만들어 주지만 SPEED 보다 동작 속도가 느려진다. 매우 많은 수의 .proto 파일을 포함하고(메시지 개수에 코드 사이즈가 비례하므로) 있거나 속도가 중요하지 않은 앱에서 유용한 옵션이다.


• LITE_RUNTIME : 'lite' 런타임 라이브러리에만 의존하는 클래스를 생성한다. (libprotobuf 대신 libprotobuf-lite) 전체 라이브러리보다 10배 작은 라이브러리로 동작이 가능하기 때문에 휴대폰과 같이 제한된 플랫폼에서 실행되는 경우 유용하다. 생성된 클래스는 Message 대신 MessageLite 인터페이스를 구현하며 이 옵션으로 컴파일 하는 경우 Descriptor, Reflection 등의 기능은 제외된다.


* 동일한 proto 파일을 여러 번 로드할 때 Descriptor로 부터 에러가 발생하는 경우가 있는데 이러한 경우엔 Descriptor 기능이 제외된 LITE_RUNTIME 옵션을 사용하는 것을 고려해볼 수 있다.

https://github.com/protocolbuffers/protobuf/issues/4126


일반적인 타겟 환경을 가진 프로그램이라면 기본 값을 그대로 쓰면 될 것 같고, 바이너리 사이즈가 중요한 프로그램이라면 CODE_SIZE, LITE_RUNTIME 등의 옵션을 고려해 볼만 한 것 같다. 사실 protoc에 의해 생성된 클래스를 그대로 사용하는 것이 일반적이라 Descriptor 같은 기능은 고급 사용자가 아니면 잘 쓰이지 않는다.


2021년 1월 11일 월요일

[Protocol Buffers] Base 128 Varints

프로토콜 버퍼의 인코딩 방식을 이해하려면 우선 Varint를 이해해야 한다. Varint는 하나 이상의 바이트를 사용해 정수를 직렬화 하는 방법으로 숫자가 작을수록 적은 바이트 수를 차지하게 된다. (아주 큰 숫자가 아니면 데이터 크기가 절약된다.)


Varint에서 마지막 바이트를 제외한 각 바이트의 최상위 비트(msb)는 1로 하기로 약속되어 있다. 이는 앞으로 올 바이트가 더 있음을 나타내기 위함이다. 

반대로 msb가 0이라면 Varint의 끝을 의미하게 된다. 따라서 1바이트를 차지하는 Varint로 표현할 수 있는 가장 큰 숫자는 127(0111 1111)이 된다. 또 하나의 룰로 Varint는 최하위 그룹을 먼저 위치시키는 방식을 택한다. (least significant group first).


표현 예시.

웹 페이지 설명에 나온 것처럼 숫자 1은 1바이트로 표현이 가능하므로 msb는 0이 되고 표현은 '0000 0001'이 된다.


그렇다면 숫자 300은 어떻게 표현될까? 앞서 언급된 것처럼 숫자 300은 하나의 바이트로 표현이 불가능 하므로 첫 바이트의 msb는 1로 셋 되어야 한다.

결과적으로 숫자 300은 아래와 같이 표현된다.


 1010 1100 0000 0010


여기서 msb를 떼면 010 1100 000 0010이 되는데, 앞서 적혀있는 대로 최하위 그룹이 먼저 위치하게 되므로 우리가 이해하는 숫자로 인식하기 위해선 7비트 단위 그룹의 위치를 서로 바꾸어 주어야 한다.


따라서 010 1100 000 0010은, 000 0010 010 1100 되어야 하고, 의미 없는 비트를 버린 뒤 두 그룹을 연결하면

→ 100101100 = 300 (256 + 32 + 8 + 4)이 된다.


https://developers.google.com/protocol-buffers/docs/encoding#varints


2020년 8월 12일 수요일

matplotlib 한글 깨지지 않게..

rc를 임포트한 다음에 한글이 지원되는 폰트의 이름을 지정해 주면 된다.
한글 윈도우 사용자라면 malgun.ttf는 있을거란 가정이 깔린다.

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="C:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

x = list(range(1,11))
y = [a * 2 for a in range(0, 10)]

l = plt.plot(x, y, 'ro', label='빨강 닷')
plt.setp(l, markersize=4)
#plt.setp(l, markerfacecolor='C0')
plt.xlabel('X-축')
plt.ylabel('Y-축')
plt.title('차트 제목')
plt.legend(loc='best')
# plt.savefig('fig.pdf') # to pdf
plt.show()


https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.rc.html


log4jdbc-remix 사용법

https://code.google.com/archive/p/log4jdbc-remix/


프로젝트는 2013년 11월 중단된 상태고, 앞으론 log4jdbc-log4j2가 log4jdbc-remix(이건 2012년 4월에 시작된 프로젝트)를 대체한다고 한다. 


log4jdbc-remix의 주 기능은 쿼리문과 쿼리 결과를 보기 좋게 로그로 기록해 주는 것이다. 데이터베이스에 요청한 쿼리문이 의도와 맞게 작성되었는지 확인하는 용도로 활용할 때 좋다. (콘솔에 찍어보는 용도)


이건 Java 6 이상 버전에서 사용 가능하고, 제공되는 Proxy에서 기존에 정의해둔 DataSource를 참조하는 설정 방식을 따르기 때문에 사용하기 편리하다.


안정화 릴리즈 버전은 0.2.7이고, maven 기준 다음과 같이 끌어다 쓸 수 있다.

<dependency>

    <groupId>org.lazyluke</groupId>

    <artifactId>log4jdbc-remix</artifactId>

    <version>0.2.7</version>

</dependency>


log4j.xml엔 jdbc.sqlonly, jdbc.resultset 이름으로 로거 설정을 등록해주면 된다.

jdbc.sqlonly는 쿼리문을 기록하는 용도이고, jdbc.resultset은 쿼리 결과를 기록하는 용도이다.


프록시 빈 등록은 인터넷에 많이 돌아다니는 걸 그대로 쓰면 된다.

<bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">

        <constructor-arg ref="dataSourceSpied" />

        <property name="logFormatter">

            <bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">

                <property name="loggingType" value="MULTI_LINE" />

                <property name="sqlPrefix" value="SQL         :  "/>

            </bean>

        </property>

 </bean>