녕후킴

10월 3주차

0 views

📌 소프트웨어에서 resolve의 의미는 대체 무엇일까?

수능 영어를 공부할 때 이 녀석의 뜻은 분명 ‘해결하다’였다. 그런데 개발 분야를 공부하면서 관련 영어 문서에 이녀석의 단어를 ‘해결하다’로 해석하려고 하면 잘 해석되지 않는다.

이번에 GraphQL에서 argument를 공부하면서 또 마주쳤다.

These are passed into the server-side execution of this field, and affect how it’s resolved

resolve의 뜻을 “해결하다”라고 생각하고 위 문장을 해석해보면, argument들은 해당 필드의 서버 실행시 전달되며, 이것이 어떻게 해결될지에 영향을 끼칩니다. 정도인데, 해석이 깔끔하지 않다. 대체 뭘 해결한다는거지 ㅡ,.ㅡ 🤯

이에 대한 궁금증을 What does ‘Resolving’ mean in programming를 통해서 어느정도 해소할 수 있었다. resolve는 의존성 주입(resolve an implementation to an interface), 패키지 관리자(resolve packages dependencies), 웹(resolve a hostname)등 다양한 곳에서 사용되고 있다. 그렇다면 resolve의 의미와 통하면서 쉬운 단어이는 Convert나 Transform, Get을 두고 resolve가 많이 쓰여지는 것일까? 라는 질문의 글이다.

답변은 다음과 같다.

(오의역 매우 주의 🙇🏻‍♂️) resolve a hostname은 hostname과 정보를 연결해주는 repository에 대한 질의가 포함되어 있다. repository 없이는 이 정보를 알수가 없다. google.come의 경우 특정한 IP로 resolve되지만, 이 IP는 할당될 당시에 우연히 할당된 숫자일 뿐이다. 이 IP는 현재와는 전혀 다른 숫자를 가지고 있었을 수도 있고, 쿼리나 IP 등록과 관련된 모든 정보를 저장하고 있지 않는 이상 이러한 google.com에서 IP로의 변환이 불가능하다. 유사하게 resolve pacakge dependencies는 설치되지 않은 패키지를 설치하는 것을 요구한다. 하지만 설치되지 않은 부분의 이름을 알기에는 충분하지 않고, 실제로는 설치되지 않은 패키지가 갖고 있는 내용물을 얻어야 하는데, 내용물의 이름으로부터 어떤 컨텐츠인지 예측할수가 없다. 예를들면, QMail은 메일 프로그램처럼 들리지만, 설치 없이는 이 프로그램의 특성을 이름으로부터 예측할 수 없다는 것이다. 왜냐하면 이름은 컴파일된 프로그램에 비해서 너무나도 적은 정보를 전달하기 때문이다.

그러므로 문맥이 필요없는 단순 변형 상황에서 “resolve”라는 단어를 쓸 수가 없다. “resolve”는 추가적인 정보가 필요한 상황에서 사용하려고 예약된 언어이다.

그러니까… resolve는 변환과 획득의 의미가 맞지만, 단순 변환이 아닌 hostname에서 ip로의 변환, 단순 획득이 아닌 package를 설치하는 상황처럼, 문맥이 필요한 상황에서만 resolve를 쓰는 것 같다.


📌 nx에 husky와 lint-staged 셋업하기

nx를 팀장님께서 셋업해주시고, 이어서 내가 husky와 lint-staged를 셋업하기로 했다. 근데 토나온다 🤯. husky, lint-staged, nx 어디서도 각 라이브러리를 동시에 쓸때 셋업하는 방법에 대한 가이드가 나오지 않는다. 다행히 nx 레포 issue에 nx에서의 husky와 lint-staged 셋업에 관한 내용이 파편처럼 흩어져 있다. 그중에 #869 issue가 도움이 됐다.

하지만 이게 끝이 아니다. 위 링크대로 셋업을 하면 commit시 린팅하는 시간이 굉장히 오래 걸리는 문제에 부딪히게 된다. 여기서 첫번째 동영상이 문제 상황이다.

원인이 support-color와 colorette가 tty.isatty(1)을 실행하는데, 이게 git hook으로 실행하면 false가 나오서 실행이 안되고, lint-staged를 직접 실행하면 true가 나와서 실행이 된다고 한다. 어찌됐건 이 문제에 대한 해결법도 존재했다. 다만 내 프로젝트에서는 아래와 같은 콘솔과 함께 동작하지 않는다. 어이x…

.husky/pre-commit: line 4: /dev/tty: No such device or address

결국 exec 1> /dev/tty의 정체를 해석하기 위해서 linux 명령어도 찾아보고 이래저래 삽질하다가, Why “No such device or address” when open /dev/tty in the first process?에서 영감을 얻어 tty를 console로 바꾸니 정상적으로 동작하는 것을 확인했다. (무친…)

원인이 프로세스를 위한 제어 터미널을 갖기 위해서는 real terminal을 가져야 하는데, /dev/tty는 real terminal이 아니다…라고 하는데, 시간상 더 딥하게 들어가진 못했다. 🏳️gg

나랑 동일한 문제를 겪고 계신분들을 위해서 영어로 답변을 달아놓긴 했는데, 과연 따봉이 달릴지 리버스 따봉이 달릴지 ㅋ.ㅋ;


📌 nx에 husky와 lint-staged 셋업하기 (이어서)

이제 husky와 lint-staged가 잘 동작하는 것 확인했다. commit 잘 되겠지? 하는 순간 다음 에러에 또 부딪힌다. (하늘이시어… 🤯)

Failed to load plugin ‘react-native’ declared in ‘apps\project\react-native-multiple-image-picker-main\package.json » @react-native-community/eslint-config’: Cannot find module ‘eslint-plugin-react-native’

요 문제는 eslint-plugin-react-native를 설치함으로써 해결했고, 그 다음으로 등장하는 다음 에러는(이제는 에러가 안나오는게 이상한 수준)

Failed to load plugin ‘flowtype’ declared in ‘apps\project\react-native-multiple-image-picker-main\package.json » @react-native-community/eslint-config#overrides[0]’: Package subpath ‘./lib/rules/no-unused-expressions’ is not defined by “exports” in D:\Source\Naranggo\node_modules\eslint\package.json

여기에서는 버전 문제라고 말하지만, 버전 문제가 아니었다. 문제의 정확한 원인을 모르겠어서 이리저리 뒤져보다가, react-native-multiple-image-picker-main 경로의 package.json에 eslintConfig 설정이 root:true와 함께 설정되어 있는 것을 발견했다. 이전까지의 지식으로는 하위 폴더에 eslint 설정이 존재하는 경우, 적용 우선 순위에 문제가 있을 뿐 이와 같은 에러가 발생하지 않는 것으로 알고있었는데, 왠지 찝찝해서 eslintConfig를 삭제하니까 정상적으로 동작했다. 라이브러리라서 직접 건들일 일도 없기 때문에 그냥 최상위 eslint 파일에 의존하는 것으로…


📌 The ref value ‘intersectionObserverRef.current’ will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy ‘intersectionObserverRef.current’ to a variable inside the effect, and use that variable in the cleanup function.

intersectionObserver 훅을 만들던 중 아래와 같은 경고에 부딪혔다.

처음부터 위와 같은 코드를 작성했던 것은 아니다. 처음 코드는 원래 아래와 같았다.

const useIntersectionObserver = (callback, options) => {
  const intersectionObserverRef = useRef(null)

  useEffect(() => {
    intersectionObserverRef.current = new IntersectionObserver(
      callback,
      options,
    )

    return () => intersectionObserverRef.current?.disconnect()
  }, [callback, options])

  return intersectionObserverRef.current
}

위에 인자로 전달되는 callback과 options는 따로 메모된게 아니라 useIntersectionObserver 호출시에 생성되어 전달되고 있다. 그렇기 때문에 리렌더링이 발생하여 callback과 options가 다시 만들어지면 useEffect 내부 로직이 다시 돌아가는 문제가 발생한다. 그래서 앞선 경고 상황처럼, useRef를 초기화할때 다음과 같이 intersectionObserver 인스턴스를 만들려고 했다.

const useIntersectionObserver = (callback, options) => {
  const intersectionObserverRef = useRef(
    new IntersectionObserver(callback, options),
  )

  useEffect(() => {
    return () => intersectionObserverRef.current?.disconnect()
  }, [])

  return intersectionObserverRef.current
}

그리고 발생한 경고인데, 경고의 의미가 잘 납득되지 않아서 리서치해봤다.

ref가 가리키는 대상이 DOM 요소와 관련이 없다면 이 경고를 크게 신경쓸 필요 없다. 경고가 의미하는 바는, ref가 가리키는 대상이 DOM 요소라면 useEffect 내부의 변수에 저장하고 사용하라는 의미이다. 왜냐하면 뒷처리 함수가 실행될 때 컴포넌트의 변화가 있을 수 있기 때문이다.

codesandbox를 보면, 1초 전에는 첫번째 useEffect 내부의 mount1이 잘 출력되는 것을 볼수 있으나, 1초 후에 Box가 unmount 됐을 때는 ref 요소를 찾을 수 없게된다. 하지만 두번째 useEffect처럼, 내부의 변수에 저장하고 사용한다면, 정상적으로 DOM 요소가 나오는 것을 알수 있다.


📌 Cannot assign to ‘current’ because it is read-only property

다음과 같은 코드에서 에러가 발생한다.

const ComponentFunction = () => {
  const stringRef = useRef<string>(null);

  useEffect(() => {
    ref.current = 'hello';
  }, [])
}

이유는 null로 ref를 초기화할때, 제네릭타입으로 null 타입을 전달하지 않아서, 불변의 ref 객체가 만들어졌기 때문이라고 한다. ref 타입과 관련해서는 별도의 문서로 정리해야겠다. 🤔