카드 확장 애니메이션 시리즈 — 1편 에서 만든 걸 실제로 여러 화면에 붙여보다가 만난 버그 기록. 증상 목록 화면에서 스크롤을 한참 내린 뒤, 아래쪽에 있는 카드를 클릭해서 확대 애니메이션을 시작했다. 그런데 확대된 결과물이 스크롤로 옮겨놓은 그 위치가 아니라, 스크롤하지 않았을 때 카드가 있었을 "원래 자리"에서 튀어나왔다. 1차 가설 (틀림) 처음엔 position: fixed 가 transform 이 걸린 조상 요소 때문에 뷰포트가 아닌 엉뚱한 기준점을 잡은 게 아닐까 의심했다. 클릭 시점에 캡처한 좌표, URL로 넘겨받은 좌표, 실제로 렌더링된 위치 — 이 세 값을 디버그 로그로 찍어 비교했더니 셋 다 정확히 일치 했다. 좌표 계산 자체는 문제가 없었다는 뜻이다. 2차 가설 (맞음) 좌표 문제가 아니라면 남은 건 애니메이션 자체가 "어디서 시작하는지"를 다르게 알고 있다는 것. 여기서 원인을 찾았다. 카드에 붙인 layoutId 가 항목의 id만으로 만들어져 있었다. 즉 목록 페이지의 진짜 카드와, 상세 페이지로 넘어가면서 만든 애니메이션용 카드가 같은 layoutId 문자열 을 쓰고 있었던 것이다. Next.js 같은 프레임워크의 클라이언트 사이드 네비게이션은 브라우저를 새로고침하지 않고, 같은 JS 런타임 안에서 화면 트리만 바꾼다. 그런데 Motion(구 Framer Motion)의 layoutId 는 그 요소가 화면에서 사라진 뒤에도 한동안 "마지막으로 어디 있었는지"를 전역으로 기억 한다. 문제는 목록의 카드는 스크롤한다고 해서 다시 측정되지 않는다는 점이다(스크롤은 레이아웃 변경이 아니라서 Motion이 재계산할 이유가 없다). 그래서 Motion이 기억하고 있는 그 카드의 위치는 스크롤하기 전, 페이지를 처음 열었을 때의 자연스러운 리스트 순서상 위치 였다. 우리가 넘긴 좌표는 무시되고, Motion은 그 "오래된 기억" 위치에서부터 애니메이션을 시작해버린 것이다. 해결 카드 컴포넌트에 layoutId 를 조합할 때 접두어(prefix)를 옵션으로 받을 수 있게 만들고, 다른 화면에서 애니메이션을 시작할 땐 anchor-${item.id} 처럼 목록 페이지가 쓰는 layoutId 와 절대 겹치지 않는 문자열 을 붙였다. 이렇게 하면 Motion 입장에서는 완전히 별개의 요소로 인식해서, 엉뚱한 과거 위치를 기억해올 일이 없다. // 목록 페이지의 카드 <motion.div layoutId={`card-${item.id}`}>...</motion.div> // 다른 페이지에서 시작하는 애니메이션 (접두어로 완전히 분리) <motion.div layoutId={`anchor-${item.id}`}>...</motion.div> 왜 처음엔 안 보였나 같은 페이지 안에서만 카드를 펼쳤다 접었다 하는 시나리오에서는 이 문제가 전혀 드러나지 않는다. layoutId 가 겹치는 두 요소가 "같은 화면 안에서, 한쪽이 사라지고 다른 쪽이 나타나는" 상황에서는 오히려 그게 의도된 동작(모핑)이기 때문이다. 문제는 페이지 이동을 낀 채로 같은 layoutId 를 다시 쓸 때만 나타난다 — 그래서 단일 페이지 안의 테스트만으로는 잡히지 않고, 실제로 목록 → 상세를 오가며 확인해야 드러나는 종류의 버그였다. 일반화하면 layoutId 를 쓰는 shared layout 애니메이션을 여러 화면에 걸쳐 쓸 계획이라면, "이 id가 다른 화면의 다른 요소와 우연히 겹치지 않는가" 를 미리 점검해볼 가치가 있다. Motion 공식 문서도 비슷한 이유로 LayoutGroup 에 id 를 줘서 네임스페이스를 분리하는 방법을 안내하고 있다 — 같은 페이지 안에 같은 컴포넌트가 여러 번 렌더링될 때 layoutId 가 서로 충돌하지 않게 하기 위한 것인데, 여기서 겪은 "페이지를 넘나드는 충돌"도 결국 같은 종류의 문제(의도치 않은 layoutId 겹침)라는 점에서 원리는 같다. 출처 Motion 공식 문서, <motion /> 컴포넌트: https://motion.dev/docs/react-motion-component Motion 공식 문서, LayoutGroup (id로 layoutId 네임스페이스 분리하는 공식 패턴): https://framer.com/docs/layout-group/
ArrayList란? 패키지에 있는 크기가 자동으로 조절되는 배열 기반 리스트이다. ArrayList는 요소를 추가 시 자동으로 크기를 늘린다. ArrayList<Movie> movies = new ArrayLisr<>(); // Movie 객체를 여러개 담아두는 리스트 값 넣기 add() 값 꺼내기 get() 개수 확인 size() 삭제 remove()
지난 실습에서는 ROS 2 Service의 기본적인 개념과 명령어를 확인했다. 이번에는 앞서 확인한 Service를 실제 TurtleSim에서 호출하면서 거북이 생성, 삭제, 펜 설정 변경, 위치 이동 등의 기능을 직접 실습해 보았다. 1. /clear Service 호출 먼저 TurtleSim에 기존에 그려져 있던 선을 지우기 위해 /clear Service를 호출했다. ros2 service call /clear std_srvs/srv/Empty 실행 결과: requester: making request: std_srvs.srv.Empty_Request() response: std_srvs.srv.Empty_Response() Service 호출이 정상적으로 처리되면서 TurtleSim 화면에 그려져 있던 선이 모두 지워지는 것을 확인했다. 2. /spawn Service로 새로운 거북이 생성 다음으로 /spawn Service를 이용해 새로운 거북이를 생성했다. 이번 실습에서는 turtle3 이라는 이름의 거북이를 (8.0, 8.0) 위치에 생성했다. ros2 service call /spawn turtlesim/srv/Spawn "{x: 8.0, y: 8.0, theta: 0.0, name: 'turtle3'}" 실행 결과: requester: making request: turtlesim.srv.Spawn_Request(x=8.0, y=8.0, theta=0.0, name='turtle3') response: turtlesim.srv.Spawn_Response(name='turtle3') TurtleSim 화면에 turtle3 이 새롭게 생성되는 것을 확인할 수 있었다. 3. /kill Service로 거북이 삭제 생성한 turtle3 을 삭제하기 위해 /kill Service를 호출했다. ros2 service call /kill turtlesim/srv/Kill "{name: 'turtle3'}" Service 호출 후 TurtleSim 화면에서 turtle3 이 사라지는 것을 확인했다. 이를 통해 실행 중인 TurtleSim의 특정 거북이를 Service를 이용해 삭제할 수 있었다. 4. /turtle1/set_pen Service 실습 다음으로 turtle1 이 그리는 선의 색상과 굵기를 변경해 보았다. ros2 service call /turtle1/set_pen turtlesim/srv/SetPen "{r: 0, g: 0, b: 255, width: 8}" 여기서는 RGB 값을 이용해 펜 색상을 설정하고 width 값으로 선의 굵기를 지정했다. 이후 turtle1 을 움직여 변경된 펜 설정이 실제로 적용되는 것을 확인했다. 5. /turtle1/teleport_absolute Service 실습 이번에는 turtle1 의 위치를 직접 변경해 보았다. ros2 service call /turtle1/teleport_absolute turtlesim/srv/TeleportAbsolute "{x: 5.0, y: 5.0, theta: 0.0}" 명령어를 실행하면 turtle1 이 지정한 좌표로 바로 이동한다. 실습을 통해 거북이를 직접 조작하지 않고도 Service를 이용해 특정 위치와 방향으로 순간이동시킬 수 있다는 것을 확인했다. 6. 여러 Service를 이용한 TurtleSim 조작 이번 실습에서는 하나의 Service만 사용하는 것이 아니라 여러 Service를 직접 호출해 보았다. /clear ↓ 화면의 이동 경로 초기화 /spawn ↓ 새로운 거북이 생성 /kill ↓ 거북이 삭제 /turtle1/set_pen ↓ 펜 색상 및 굵기 변경 /turtle1/teleport_absolute ↓ 거북이 위치 및 방향 변경 각각의 Service를 호출할 때마다 TurtleSim의 상태가 실제로 변경되는 것을 확인할 수 있었다. 마무리 이번 실습에서는 ROS 2에서 제공되는 Service를 직접 호출하여 TurtleSim을 제어해 보았다. 특히 새로운 거북이를 생성하거나 삭제하고, 펜의 색상과 굵기를 변경하고, 거북이를 원하는 위치로 이동시키는 등 Service를 이용해 실행 중인 노드의 기능을 직접 요청하는 과정을 확인할 수 있었다. 이번 실습을 통해 앞서 학습한 Service가 실제 ROS 2 환경에서 어떻게 활용되는지 확인할 수 있었다.
React 왜 씀 single page application 을 편하게 만들기 위해 React 라는 자바스크립트 라이브러리 를 설치해 사용하는 것 이다. 비슷한 js라이브러리들이 많긴 하지만 가장 오래되었고 유저가 가장 많기에 리엑트를 쓴다(근-본). 장단점 장점 HTML 재사용의 편리함 컴포넌트 단위로 개발하기 때문에 여러 개발자들이 분업하기 좋음 React Native를 쓰면 리액트 비슷한 문법으로 모바일 앱개발도 가능 프론트엔드와 백엔드 파트를 완전히 분리해서 각각 개발가능 단점 많은 자바스크립트로 인해 웹페이지 크기가 커짐 웹페이지 로드시 HTML 내용물이 천천히 채워지기 때문에 SEO(Search Engine Optimization)에 악영향이 있을 수 있음 (hydration 작업 : html을 먼저보내고 자바스크립트를 연결한다) 간단한 사이트도 코드가 쓸데없이 복잡해짐 Single Page Application 말 그대로 서버에서 전체 페이지를 다시 불러오지 않고 html 한 페이지 만 써서 자바스크립트 로 현재 페이지를 동적으로 갱신한다. html 파일을 1개만 사용 다른 페이지를 보여주고 싶을 때 html 부분만 갈아치워서 보여준다 예) instagram 환경구성 nodejs 설치하면 npm 패키지 매니저가 같이 설치된다. => React 받기 편해짐 설치 후 프로젝트 경로에서 npm create vite@latest 하면 생성된다. npm run dev 개발 서버 실행한다. React 기초 문법 기초 jsx 문법 3개 프로젝트 생성시 주는 App.jsx에 작성해 보았다. function App(){ return ( <div className="App"> <div className="black-nav"> <h4>블로그임</h4> </div> </div> ) } 이런 식으로 div에 때려 넣는다 하지만 function App(){ return ( <div className="App"> <div className="black-nav"> <h4>블로그임</h4> </div> </div> <div> 안된다 </div> ) } 이건 안된다. return()안에는 하나의 div가 있어야 한다. 반환 할 때 div tag 하나만 반환하기 때문이다. import './App.css' 이런 식으로 css도 작성해서 적용이 가능하다. className 평소에는 class를 그냥 class로 넣었는데 className으로 넣어야 한다. => js에서 class를 써가지고 우리가 jsx에서 작성하고 있다는 사실을 망각하지 말자. 데이터 바인딩 문법 {} 거지같은 DOM에서 하나하나 queryseletor나 getElementById로 찾아서 수정하던 것은 잊어라 이건 jsx다. function App(){ let post = 'swap'; return ( <div className="App"> <div className="black-nav"> <div>swap</div> <div>{ post }</div> </div> </div> ) } 긴 말 필요없다. return() 밖에서 선언하고 안에 변수 명을 {}중괄호 안에 꼬라 박으면 값이 들어간다. function App(){ let post = 'swap'; return ( <div className="App"> <div className="{ post }"> <div>swap</div> <div>{ post }</div> </div> </div> ) } className에도 가능이 html에 style 속성 넣기 <div style="color : blue"> 이렇게 넣을 안일한 생각 하지마라 <div style={ {color : 'blue', fontSize : '30px'} }> 글씨 </div> 일단 - 이 기호도 못 쓰고 js에서 변수 선언할 때 마냥 대문자로 써서 붙여야 한다. 그리고 저렇게 오브젝트 형식으로 써줘야 하는데 그 이유는 JSX는 HTML이 아니라 JavaScript이기 때문이다. JSX는 빌드 시점에 결국 React.createElement() 같은 순수 JavaScript 코드로 변환된다. state 왜 씀 중요한 변수는 state에 담는다. state는 변동사항이 생기면 state쓰는 html도 자동으로 재렌더링해준다. function App(){ let post = 'swap' return ( <h4>{ post }</h4> ) } 이렇게 쓰면 코드를 더 짜야지 변경 내용이 보이는데 function App(){ let [글제목, b] = useState('swap'); return ( <h4>{ 글제목 }</h4> ) } state는 바뀌면 바로 적용 해준다. 선언 let [name, age] = ['swap', 18] array 안에 왼쪽 오른쪽 형식을 똑같이 하면 알아서 딱딱 들어가는 destructuring 문법을 사용해 선언한다. let [a,b] = useState('hello') useState()를 쓰면 [데이터1, 데이터2] 가 생기는데 데이터1 : 값 데이터2 : state변경을 도와주는 함수 가 남게 된다. state 변경 <div onclick="자바스크립트"> 일반 html 파일에선 이렇게 하면 된다. <div onClick={함수}> JSX에서는 이렇게 한다. function App(){ let [ 따봉, 따봉변경 ] = useState(0); return ( <h4> swap <span onClick={ ()=>{따봉변경(따봉+1)} } >👍</span> { 따봉 }</h4> ) } 이런 식으로 state를 변경 할 수 있다. 아까 데이터2에는 state 변경을 도와주는 함수가 들어간다 한 것을 기억한다면 이해가 어렵지 않을 것이다. function App(){ let [글제목, 글제목변경] = useState( ['남자코트 추천', '강남 우동맛집', '파이썬 독학'] ); return ( <button onClick={ ()=>{ let copy = [...글제목]; copy[0] = '여자코트 추천'; 글제목변경(copy) } }> 수정버튼 </button> ) } 또 다른 문자열 array를 갈아 끼우는 방식이다. state 자체를 변경하기에는 쓸모가 있을 수 있으니 보통 복사해서 많이 쓴다. 근데 잠깐 [...글제목] 너 뭐냐 let data1 = [1,2,3]; let data2 = data1; data2[0] = 1000; console.log(data2 === data1) true ??? 결론 부터 말하자면 data1과 data는 똑같은 값을 공유한다. 그래서 저런 괴랄한 결과가 나오는 것 이다. ... 이게 뭐냐 spread operator 라는 것 인데 ..[1,2,3] 이렇게 쓰면 그 자리에 1,2,3 이 남는다. 이렇게 하면 다른 복사본을 만들 수 있다. component 긴 HTML을 한 단어로 깔끔하게 치환해서 넣을 수 있는 문법 함수 만들듯, 변수 만들듯 HTML을 깔끔하게 한 단어로 치환해서 원하는 곳에 꽂아넣을 수 있다. 줄이는 법 function을 이용해서 함수를 하나 만들어주고 작명한다. 그 함수 안에 return () 안에 축약을 원하는 HTML을 담는다 그럼 원하는 곳에서 <함수명></함수명> 사용하면 아까 축약한 HTML이 나타난다. function App (){ return ( <div> (생략) <Modal></Modal> </div> ) } function Modal(){ return ( <div className="modal"> <h4>제목</h4> <p>날짜</p> <p>상세내용</p> </div> ) } 이런 식으로 function App (){ return ( <div> (생략) <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> <Modal></Modal> </div> ) } function Modal(){ return ( <div className="modal"> <h4>제목</h4> <p>날짜</p> <p>상세내용</p> </div> ) } 으하하 function Modal(){ return ( <div></div> ) } let Modal = () => { return ( <div></div>) } 이런 방법으로도 만들 수 있다. 주의사항 component 작명할 땐 영어대문자로 보통 작명 return () 안엔 html 태그들이 평행하게 여러개 들어갈 수 없다. function App(){} 내부에서 만들면 안된다. function App(){} 이것도 다시보니 컴포넌트 생성문법, component 안에 component 를 만들진 않는다. <컴포넌트></컴포넌트> 이렇게 써도 되고 <컴포넌트/> 이렇게 써도 된다 뭐 할때 씀 사이트에 반복해서 출현하는 HTML 덩어리들은 Component로 만들면 좋다. 내용이 매우 자주 변경될 것 같은 HTML 부분을 잘라서 Component로 만들면 좋다. 다른 페이지를 만들고 싶다면 그 페이지의 HTML 내용을 하나의 Component로 만드는게 좋다. 또는 다른 팀원과 협업할 때 웹페이지를 Component 단위로 나눠서 작업을 분배하기도 한다. 함수문법도 긴 코드 축약할 때 다른 곳에서 코드 재사용할 때 복잡한 코드를 작은 기능으로 나눌 때 컴포넌트는 그냥 함수 문법이랑 똑같아서 용도도 똑같다. 단점 관리가 힘들어질 수도 있고 안에 state 쓰기가 불편하니 필요할 때만 사용하자. 동적 UI 만들기 3step 예시로 글자 같은거 클릭시 글이 사라졌다 나타났다 하게 만들어 보자. 1. html css로 미리 UI 디자인 function Modal(){ return( <div className='modal'> <h4>제목</h4> <p>날짜</p> <p>상세내용</p> </div> ) } 예를 들어 이딴걸 만들었다 치자 2. UI의 현재 상태를 state로 저장 state 하나 만들고 거기에 현재 UI의 상태정보를 저장 let [modal, setModal] = useState(false); let [modal, setModal] = useState('닫힘'); let [modal, setModal] = useState(0); // 이런 식으로 아무렇게나 지정해도 상관없다. 3. state에 따라서 UI가 어떻게 보일지 조건문 등으로 작성 let [modal, setModal] = useState(false) <div className='list'> <h4 onClick={()=>{setModal(modal == true? false:true)}}>{글제목[2]}<span>👍</span>0</h4> <p>2월 17일 발행</p> </div> { modal == true ? <Modal/>:null } 나의 경우 조건식을 사용하여 true 일때 모달이 보이고 false라면 null로 비워두게 만들었다. map .map 이런 식으로 array 옆에 붙이는 js 기본 함수다 기능 array에 들어있는 자료갯수만큼 그 안에 있는 코드를 반복실행한다. var 어레이 = [2,3,4]; 어레이.map(function(){ console.log(1) }); 콜백함수에 파라미터 아무렇게나 작명하면 그 파라미터는 어레이 안에 있던 모든 자료를 하나씩 출력해준다. var 어레이 = [2,3,4]; 어레이.map(function(a){ console.log(a) }); return 오른쪽에 뭐 적으면 array로 담아주고 map() 쓴 자리에 남겨준다 var 어레이 = [2,3,4]; var newArray = 어레이.map(function(a){ return a * 10 }); console.log(newArray) html복사 이를 이용하면 이런 식으로 html 복사 가능하다 function App (){ return ( <div> { [1,2,3].map(function(){ return ( <div>말하면500만원</div> ) }) } </div> ) } map()의 기능들을 이용하면 다음과 같은 것들도 가능해진다 function App (){ return ( <div> (생략) { 글제목.map(function(a){ return ( <div className="list"> <h4>{ a }</h4> <p>2월 18일 발행</p> </div> ) }) } </div> ) } function App (){ return ( <div> (생략) { 글제목.map(function(a, i){ return ( <div className="list"> <h4>{ 글제목[i] }</h4> <p>2월 18일 발행</p> </div> ) }) } </div> ) } 이런 식으로 function에는 인자들을 넣어서 리스트 값들을 복사해와 하나씩 증가해 따로따로 바꿀 수도 있다. { 글제목.map(function(a, i){ return( <div className='list' key={i}> <h4>{글제목[i]}<span onClick={() => {엄지변경((이전엄지)=>{const 새엄지 = [...이전엄지];새엄지[i] += 1; return 새엄지;})}}>👍</span>{엄지[i]}</h4> <p>2월 17일 발행</p> </div> ) }) }
불변 객체 (Immutable Object) 말 그대로 한 번 만들어지면 절대 바뀌지 않는 객체이다 Primitive vs Reference Type Primityive Type(원시형) : 변수에 실제 값이 들어간다. -> 객체가 아니다 Reference Type(참조형) : 변수에 객체의 주소 (참조값)이 들어간다 레퍼런스가 레퍼런스를 불러오는경우에 값이 마음대로 바뀔 수 있음 final을추가하고 , setter를 없앰으로써 immutable 만일 final 속성을 setter로 건드리려고하면 에러가 발생한다 final 키워드 붙은걸 조작하려고 하기때문임.
이정표를 찾고 싶어서 회사를 다니면서 실제 업무를 하면서 생각보다 내 실력이 크게 늘지 않는다고 생각이 들었다. 실제로 코딩을 그리 많이 하지 않고 있고, 여러가지 지식은 생겼지만 그렇다고 그게 깊은 지식은 아니다. 내 스스로 물경력만 쌓이는 걸 방지하기 위해서 퇴근 후에 내가 하는 공부에 대해서 간단하게 기록을 하려고 한다. 목표 프로그래머스를 통한 꾸준한 문제 푸는 사고 방식을 기르자. CS 공부를 다시 시작하려고 한다. Git 내가 한 번 다뤄보고 싶었던 프로젝트를 다뤄보려고 한다. 현재까지 생각은 ROS2, DDS 등 과정 어떻게 공부를 시작할까 고민하다가 친구한테 얘기를 했는데, 서로 뜻이 맞아 친한 친구랑 스터디 모임을 만들었다. 모임이라고 해서 거창한게 아닌 그냥 퇴근하고, 집중해서 공부한 시간이 얼마나 되는지 서로 공유와 꾸준하게 목표를 지니고 해보자는 취지다. 평일 퇴근 후에 공부할 수 있는 시간은 끽해야 1~2시간이고, 많으면 4일 적어도 3일을 공부할 예정이다. 우선 이번 연도 목표에 대해서 적어보자 프로그래머스를 통한 문제 푸는 방식 기르기 소프트웨어 개발자의 사고 기르기 CS 공부 및 언어 중급 회독 (1. 운영체제 2. 전문가를 위한 C) 주저리주저리 지금 내가 하는 흐름이 날 어디까지 데려다 줄지 모른다. 하지만 지금 가만히 있으면 분명 도태되고 말 것이다. 1년도 안 된 회사 생활을 이어가면서 LLM을 통한 vLLM을 만들면서, LLM이 완벽하지 않지만 정말 무서운 도구이다. 그래서 공부를 시작하려고 한다. 지금 내가 이렇게 무지한 상태인데 더 아는게 많아면 이 도구가 성능이 얼마나 좋은건지 다른 시선에서 체감해보고 싶다. 아직 갈 길은 멀지만 시작은 미약하지만 끝은 창대하리라고 스스로 다짐한다.