import와 export 키워드)을 확장하여 개발자가 프론트엔드/백엔드 분할을 제어할 수 있는 새로운 의미론을 제공합니다.'use client'와 'use server' 지시문에 대해 다루었습니다. 이 글에서는 이러한 지시문이 import와 export 키워드와 어떻게 상호작용하는지에 초점을 맞추고 싶습니다.import와 export 키워드를 통해 노출됩니다.a.js와 b.js라고 부를 두 파일을 생각해봅시다:index.js라고 불리는 이 파일을 생각해봅시다:import와 export 키워드는 복사-붙여넣기를 연상시키는 방식으로 작동하도록 설계되었습니다—궁극적으로, 결국 프로그램은 JS 엔진에 의해 프로세스의 메모리에서 "펼쳐져야" 하기 때문입니다.#include 지시문으로 시간 여행을 떠나는 것이 도움이 됩니다.#include 지시문은 위의 파일에 a.h와 b.h의 전체 내용을 문자 그대로 포함시킵니다. 이 동작은 간단하지만 두 가지 큰 단점이 있습니다:#include의 한 가지 문제는 서로 다른 파일의 관련 없는 함수들이 이름이 같으면 충돌한다는 것입니다. 이것은 모든 식별자가 파일에 로컬인 최신 모듈 시스템에서는 당연하게 여겨지는 것입니다.#include의 또 다른 문제는 같은 파일이 여러 곳에서 "포함"될 수 있다는 것입니다—따라서 출력 프로그램에서 여러 번 반복됩니다! 이를 해결하기 위해 모범 사례는 "포함 가능"하기를 원하는 각 파일의 내용을 빌드 타임 "이미 포함했으면 나를 건너뛰세요" 가드로 감싸는 것이었습니다. import와 같은 최신 모듈 시스템은 이를 자동으로 수행합니다.c.js라고 불리는 새로운 모듈을 추가했다고 가정합시다:a.js와 b.js 모두를 다시 작성했다고 가정합시다. 그래서 각각이 c.js 파일에서 c 함수를 import하고 그것으로 뭔가를 합니다:import가 문자 그대로 복사-붙여넣기였다면(#include처럼), 우리는 프로그램에서 c 함수의 두 복사본을 끝내게 될 것입니다. 하지만 다행히, 그것은 일어나지 않습니다!index.js 파일과 함께 아래의 단일 파일 프로그램과 의미론적으로 동등하다는 것을 보장합니다. c 함수가 두 번 import되었음에도 불구하고 한 번만 정의되는 방식에 주목하세요:Map을 유지함으로써 이를 수행합니다. 모든 JS import 구현은 이 로직을 어딘가에 가지고 있습니다. 예를 들어: Node.js 소스, webpack 소스, Metro (RN) 소스.index.js였습니다. 이것이 JavaScript 엔진이 시작하는 곳입니다.a.js 또는 b.js와 같은 다른 모듈을 import할 수 있으며, 이들 자체가 더 많은 모듈을 import할 수 있습니다. JavaScript 엔진은 그 모듈들의 코드를 실행합니다. 또한 각 모듈의 내보낸 값을 나중을 위해 메모리 내 캐시에 저장합니다.import를 보면(c.js에 대한 두 번째 import와 같이), 그것은 모듈을 다시 실행하지 않을 것입니다. 모듈은 싱글톤입니다! 대신, 메모리 내 캐시에서 그 모듈의 내보낸 값을 읽을 것입니다.import할 때 어떤 코드를 프로그램에 가져옵니다.backend/index.js에 있을 수 있습니다:frontend/index.js에 있을 수 있습니다:backend/index.js에서 a.js와 b.js를 import한다고 가정합시다:frontend/index.js에서도 그들을 import한다고 가정합시다:a.js, b.js, 그리고 c.js 구현을 양쪽 간에 재사용하고 있지만, 백엔드 코드와 프론트엔드 쪽이 a.js, b.js, 그리고 c.js 모듈의 "자신의 버전"을 가지고 있다고 생각하는 것이 더 정확할 것입니다.c.js를 편집하여 오직 백엔드에서만 의미가 있는 코드를 포함한다고 가정합시다. 예를 들어, 서버에서 파일을 읽기 위해 fs를 사용한다고 상상해봅시다:fs가 그곳에 존재하지 않기 때문에 프론트엔드 빌드에 실패할 것입니다:fs는 백엔드에서만 의미가 있음), 우리는 빌드가 조기에 실패하기를 원합니다. 그래서 우리는 코드를 어떻게 수정할지 결정할 수 있습니다:fs 호출을 c.js 이외의 다른 곳으로 이동하도록 선택할 수 있습니다.a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.frontend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.c.js를 편집하여 서버 측 비밀을 import한다고 가정합시다.secret은 백엔드 그리고 프론트엔드 코드의 일부가 될 것입니다:fs를 사용하면 프론트엔드 빌드에 실패했다는 것을 보았습니다. 이것은 우리가 여기서도 일어나기를 정확히 원하는 것입니다!server-only라고 부를 특별한 패키지를 만든다고 가정합시다. 이것은 프론트엔드에 절대 도달해서는 안 되는 코드의 마커 역할을 합니다. 그 자체로, 그 패키지는 실제 코드를 포함하지 않을 것입니다. 이것은 "독 알약"입니다. 우리는 프론트엔드 번들러에게 이 모듈이 프론트엔드 번들에 들어가면 빌드에 실패하도록 가르칠 것입니다.secrets.js를 서버 전용으로 표시할 수 있습니다:secrets.js를 번들에 끌어들이면 프론트엔드 빌드에 실패합니다. 구체적으로, a.js와 b.js 모두 c.js를 가져올 것이고, 이것은 secrets.js를 가져올 것이고, 이것은 server-only를 가져올 것입니다—그리고 그것이 빌드에 실패하는 독 알약입니다:fs import처럼, 우리는 그것을 수정하기 위한 다양한 옵션을 가질 것입니다:secrets.js import를 c.js 이외의 다른 곳으로 이동하도록 선택할 수 있습니다.a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.frontend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.a.js, b.js, 그리고 c.js와 같은 개별 파일을 서버 전용으로 표시할 필요가 없습니다. 그들에 로컬한 어떤 특정한 이유가 있지 않는 한 말입니다. 절대 포함되어서는 안 되는 파일(예: secrets.js)을 표시하고, "독 알약"이 import 체인을 따라 전파되도록 하는 것으로 충분합니다.server-only "독 알약"과 유사하게, 우리는 미러 트윈 client-only "독 알약"을 만들 수 있습니다. 이것은 서버 측 빌드에 실패합니다. (서버를 번들하지 않으면, TypeScript를 실행하는 것과 유사하게 대신 이 검사를 별도로 실행할 수 있습니다.)c.js에서 브라우저 특정 API를 사용했다고 가정합시다. 이것은 백엔드 코드에 절대 끌어들이는 것이 유효하지 않다고 결정하기 위한 좋은 이유일 수 있습니다:c.js를 리팩토링하여 백엔드에서 작동하도록 할 수 있습니다(그리고 독 알약을 제거합니다).a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.backend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.client-only와 server-only의 더 세분화된 버전을 더 상상할 수 있습니다. 이것은 개별 패키지 import에 적용됩니다. 예를 들어, React 패키지는 useState와 useEffect와 같은 API를 client-only로 선언할 수 있습니다. 그래서 그들을 백엔드 코드에 끌어들이면 즉시 빌드에 실패합니다. (힌트: React는 실제로 package.json 조건부 내보내기 메커니즘을 통해 그렇게 합니다.)a.js와 b.js는 그들이 c.js의 구현 세부사항을 알지 못하기 때문에 한쪽에만 존재해야 한다고 규정하지 않습니다. 하지만 어떤 모듈이 배타적이기를 원한다면, 이제 server-only 또는 client-only로 "로컬하게" 이것을 표현할 수 있습니다. 선언된 비호환성은 그 후 모든 importing 모듈에 "감염"됩니다.server-only와 client-only "독 알약"이 코드가 어디로 가는지를 제어하지 않는다는 것을 이해하는 것이 중요합니다. 그들이 코드를 "백엔드에 놓거나" "프론트엔드에 놓지" 않습니다. 이러한 어설션이 하는 유일한 것은 코드가 지원되지 않는 환경으로 끌어들여지는 것을 방지하는 것입니다. 그들은 독 알약 뿐입니다.server-only와 client-only 독 알약을 제공합니다. 이것들은 그 모듈 내부의 어떤 코드 때문에 특정 쪽으로 절대 가져와져서는 안 되는 모듈에서 사용되어야 합니다. 이것은 코드가 어디서 실행되는지를 변경하지 않지만, 우리에게 조기 빌드 오류를 줍니다.sayHello 함수를 참조하고 싶지만, 구문적으로 그렇게 할 방법이 없어서 다른 쪽에 존재할 것이라고 가정해야 합니다:sayHello를 단순히 import할 수 없습니다. 왜냐하면—관찰력 있는 독자가 이미 깨달았을 수 있듯이—그것은 단순히 그것을 백엔드 코드에 가져올 것이기 때문입니다.'use client'는 이 동작을 "프론트엔드 환경으로의 문을 열기"로 변경합니다.'use client'를 추가할 때, 당신은 말하고 있습니다: "백엔드 세계에서 나를 import하면, 실제로 내 코드를 백엔드에 가져오지 말고—대신, React가 결국 <script> 태그로 바꾸고 프론트엔드에서 부활시킬 수 있는 참조를 제공하세요."'use server'는 프론트엔드 코드의 일부가 백엔드에 "문을 열고" 백엔드 모듈을 참조할 수 있게 해줍니다. 이것은 그것을 프론트엔드 세계에 가져오지 않고 말입니다.'use client'를 넣거나 모든 백엔드 모듈에 'use server'를 넣으면 안 됩니다—그것은 무의미합니다! 그들이 하는 모든 것은 두 모듈 시스템 간에 "문"을 만들 수 있게 해주는 것입니다. 그들은 당신이 다른 세계를 참조할 수 있게 해줍니다.<script> 태그로), 'use client'가 필요합니다. 프론트엔드에서 백엔드로 데이터를 전달하고 싶다면(API 호출로), 'use server'가 필요합니다. 그렇지 않으면, 지시문이 필요 없습니다—당신은 import를 평소처럼 사용하고 현재 세계에 머물러 있습니다.import 'client-only'와 import 'server-only' 독 알약. 이것들은 개별 모듈이 다른 세계로 가져와져서는 안 된다고 선언할 수 있게 해줍니다.'use client'와 'use server' 지시문. 이것들은 당신이 다른 세계의 모듈을 참조하고 그들에게 데이터를 전달할 수 있게 해줍니다. 그들을 가져오지 않고 말입니다.frontend/와 backend/ 디렉토리가 불필요하고 심지어 오도할 수 있다는 것을 깨달을 것입니다. 왜냐하면 정보가 이미 모듈에 포함되어 있기 때문입니다. 하지만 그것은 로컬하게 포함되어 있어서 경계가 코드를 진화시키면서 자동으로 이동합니다.아직 댓글이 없습니다.
첫 번째 댓글을 작성해보세요!
setState는 어떻게 무엇을 해야 할지 알까?
Inkyu Oh • Front-End
"Bug-O" 표기법
Inkyu Oh • Front-End
RSC가 번들러와 통합되는 이유
Inkyu Oh • Front-End

Lean 문법 입문서
Inkyu Oh • AI & ML-ops