이번 포스팅에서는 React.js 의 라이브러리 중 하나인React-Query를 사용해보려고 합니다.
소스를 구현하고 동작시켜보는데, tailwindcss + cursor ai 의 많은 도움을 받았답니다..
일단 결과물 먼저 보고 오시죠!
Result
1. React-Query 설치
당연하게 npm i react-query 했는데, 오류나면서 안된다길래. 뭐지..? 싶어서 열심히 검색해왔다.
"@tanstack/react-query": "^5.67.2" //해당 버전을 다운로드 받으면 된다.
//기존의 react-query는 삭제해준다.
npm uninstall react-query
//tanstack의 버전으로 다운로드 해준다.
npm install @tanstack/react-query
noopener: 새 탭/창에서 열리는 페이지가 window.opener 속성을 통해 원래 페이지에 접근하는 것을 방지합니다. 이것은 탭 내빙(tab nabbing)이라는 보안 취약점을 막아줍니다. 탭 내빙은 새 탭에서 열린 페이지가 원래 페이지의 location을 변경하여 피싱 공격을 시도할 수 있는 방법입니다.
noreferrer: 새 페이지로 이동할 때 HTTP 리퍼러(Referer) 헤더를 전송하지 않도록 합니다. 이는 방문자의 개인정보 보호에 도움이 되며, 새 페이지가 어디에서 방문자가 왔는지 알 수 없게 합니다.
이 속성들은 외부 링크를 사용할 때 보안과 개인정보 보호를 강화하기 위한 웹 개발 모범 사례입니다. 특히 target="_blank" 를 사용할 때는 항상 rel="noopener noreferrer"를 함께 사용하는 것이 좋습니다.
지난 포스팅에서 Streaming 을 통해서 데이터를 노출하는 것까지 예시를 통해서 완료하였다.
이번에는 ai text 챗. Chatgpt 처럼 중간에 "정지"할 수 있는 기능을 추가하려고 한다.
1. AbortController
// 새로운 AbortController 인스턴스 생성
const abortController = new AbortController()
바로 abortController 기능을 사용하는건데, 해당 기능은 node15 이상부터 사용할 수 있으니 저버전의 서버를 사용하고 계신다면 주의 바랍니다. (그 이하를 쓰는거면 얼마나 오래된겁니까..)
이렇게 abrotController 를 선언하였다면,
// API 요청에 signal 추가
const response = await fetch(`/api/streaming`, {
method: 'POST',
headers: Config.headers,
body: JSON.stringify(requestBody),
signal: abortController.signal, // AbortController의 signal 추가
})
내려온 데이터들을 지속적으로 store에 저장. (while문을 사용하여단어로 끊어져서 내려오는 데이터들을useRetrieveStore에set)
React.js4번화면에서 사용 하도록 한다
2. utils.js - 데이터 전처리
데이터의 포맷이 정상적으로 내려오지 않아서 1.5일 동안 개고생했다.
이 부분인데,
removeLeadingData(String): 데이터의 가장 앞에"data: "로 내려오는 경우가 있어 해당 로직을 추가하였다. (",data:"도 있었다..)
decodeUnicodeString(String):Unicode로 된 데이터를 변환하는 작업.
removeFirstAndLastQuotes(String): Escape 문자열을 처리하는 방법.(진짜 할 때 속터져)
str
.replace(/"{/g, '{') // 문자열 시작의 '"{' 를 '{' 로 변경 (JSON 객체 시작 부분 정리)
.replace(/}"/g, '}') // 문자열 끝의 '}"' 를 '}' 로 변경 (JSON 객체 끝 부분 정리)
.replace(/\\/g, '\\\\') // 단일 백슬래시를 이중 백슬래시로 변경 (이스케이프 처리)
.replace(/"/g, '\\"') // 따옴표를 이스케이프 처리된 따옴표로 변경
.replace(/\t/g, '\\t') // 탭 문자를 이스케이프 처리된 탭으로 변경
4. parseNestedJSON(String|Object): 결국 받아온 값들을 사용하려면JSON.parse(String)이 필요한데, 재귀함수처럼 사용하였다 (사실은 while 반복문) 한 번 파싱했을 때 한개의 요소만 JSON 파싱이 되는 경우가 있어서 만약 String 형태면 다시JSON.parse()해주는 작업을 추가하였다.
open class Country ( var fullName: String, var capital: String, var language: String) {
fun printFullName () {
println("fullName: $fullName")
}
fun printCapital () {
println("capital: $capital")
}
fun printLanguage () {
println("language: $language")
}
open fun singNationalAnthem () {
println("singNationalAnthem")
}
}
class Korea (fullName: String, capital: String, language: String) : Country( fullName, capital, language) {
override fun singNationalAnthem () {
super.singNationalAnthem()
println("sing Korea")
}
}
class USA ( fullName: String, capital: String, language: String) : Country( fullName, capital, language) {
override fun singNationalAnthem () {
super.singNationalAnthem()
println("sing USA")
}
}
Country 라는 클래스를 Korea, USA 라는 클래스에서 상속.
자식 클래스에서는 super.METHOD 를 통해 부모 클래스의 메서드를 호출 할 수 있다.
+커스터마이징이 가능하다 (== 다형성)
fun main (args: Array<String>) {
val korea = Korea("대한민국", "서울", "한국어")
korea.singNationalAnthem()
val usa = USA("미국", "워싱턴", "영어")
usa.singNationalAnthem()
}
/** output
singNationalAnthem
sing Korea
singNationalAnthem
sing USA
*/
이렇게 출력이 된다.
Java의 문법과는 또 달라서 [ Java(public) / Kotlin(open) ] 신기하다. 뭔가 public 공공의 라는 뜻보다는 open 이 더 직관적이기도 하고
2. Abstract. 추상매서드
다음으로 추상메서드에 대해 메모한다.
//추상클래스
abstract class Game {
//일반메서드
fun startGame () {
println("Game Start")
}
//추상메서드
abstract fun printGameName ()
}
class OverWatch: Game() {
//추상메서드는 하위 클래스에서 반드시 구현해야함
override fun printGameName() {
println("This game is OverWatch.")
}
}
Game이라는 클래스를 만들었다. Game > 메서드로 printGameName 이라는 메서드를 만들어놓았다. : 추상메서드는 반드시!!! 오버라이딩 한 메서드에서 만들어야한다.
fun main (args: Array<String>) {
val overwatch = OverWatch() //새로운 인스턴스 생성
overwatch.startGame()
overwatch.printGameName()
}
새로운 인스턴스 overWatch 를 생성. 해당 클래스에 선언된 메서드를 실행하였다. printGameName 메서드는 추상메서드로 선언(abstract)
예전에는 이게 무슨소린지 전혀 이해를 못했었는데, 다시 보니 또 바로 이해되는게 신기하다.
//Java
String[] javaArray = {"1", "2", "3"};
//Kotlin : Array<String> 부분은 생략이 가능하다.
val kotlinArray: Array<String> = arrayOf("1", "2", "3");
val kotlinArray arrayOf("1", "2", "3");
문법이 너무 신기하다.
2. 배열의 사용법
//Until 끝 값을 포함하지 않는 범위를 생성 (시작값 ≤ i < 끝값).
for (i in 1 until 10) {
println(i) // Prints numbers from 1 to 9 (10 is excluded)
}
//DownTo 값을 감소시키며 반복.
for (i in 10 downTo 1) {
println(i) // Prints numbers from 10 to 1
}
//Step 반복 간격을 설정 (기본값은 1).
for (i in 1..10 step 2) {
println(i) // Prints 1, 3, 5, 7, 9
}
꽤나 많은 것들이 확장 되는 것 같다.
3. print
여러 가지가 있지만, 그 중에JavaScript의template literal처럼 사용하는게 너무 신기했다.
//JavaScript
const literalWord = 'value of Literal'
console.log(`literalWord : ${literalWord}`)
//Kotlin
val literalWord: String = "value of Literal"
println("literalWord : $literalWord");