누구의 컴퓨터에서 실행될 것인가?
컴포넌트는 결국 코드입니다. 이 코드는 ‘누구의 컴퓨터에서 실행될 것인가?’가 중요합니다. 여기서 실행 가능한 컴퓨터는 2대입니다. 나의 컴퓨터와 당신의 컴퓨터, 이 둘 중 한 컴퓨터에서 실행될 것입니다.
당신의 컴퓨터에서 실행된다면
const Couter = ()=>{ const [count, setCount ] = useState(); return <div> <button onClick={()=>{setCount(prev => prev++)}}> Count: {count}</button> </div> }
이 컴포넌트에서 당신이 만약 버튼을 클릭한다면, 지연없이 즉시 카운트가 증가될 것입니다. 서버의 응답을 기다리거나, 추가로 데이터를 다운로드 받을 필요가 없습니다. 왜냐하면 당신의 컴퓨터에서 실행 중이기 때문이죠!
- 버튼을 클릭하고 서버에 다음 출력을 요청하고 싶다면요?
- 너무 느립니다. 즉각적인 피드백이 이루어지지 않아 사용자 경험을 떨어뜨릴 수도 있습니다
- 예를 들어 링크 이동과 같이, 사용자가 앱의 다른 위치로 이동하고 있음을 알고 있어, 지연을 예상하는 경우에는 서버에 새 UI를 요청하는 것이 낫습니다. 유저는 기다릴 수 있으니까요.
그러나 만약, 슬라이더, 드래그, 탭 전환, 글 입력, 좋아요 클릭, 스와이프, 메뉴에 마우스 올리기, 차트 드래그 등 즉각적인 피드백이 최소한으로라도 있어야 하는 경우는 어떨까요? 서버에 다음 출력을 요청할 건가요?
- User Interface 설계 측면에서 본다면
- 지연시간이 짧고, 네트워크 왕복이 일어나지 않는 응답을 제공해야 합니다.
나의 컴퓨터에서 실행된다면
아래 PostPreview 컴포넌트는 어떻게 해당 게시글의 단어를 미리 알 수 있었을까요? 게시글의 단어 수를 요청한 내역도 없습니다. 바로 나의 컴퓨터, 서버에서 이미 데이터 전처리를 진행하였기 때문입니다. 그래서 당신은 정말 렌더링에 필요한 wordCount만 받아서 렌더링한 것이죠.
import { readFile } from "fs/promises"; import matter from "gray-matter"; export async function PostPreview({ slug }) { const fileContent = await readFile("./public/" + slug + "/index.md", "utf8"); const { data, content } = matter(fileContent); const wordCount = content.split(" ").filter(Boolean).length; return ( <section className="rounded-md bg-black/5 p-2"> <h5 className="font-bold"> <a href={"/" + slug} target="_blank"> {data.title} </a> </h5> <i>{wordCount} words</i> </section> ); }
UI = f(state)
현재 상태가 UI를 결정합니다. 상태가 변화하면 UI도 변경되어야 합니다. 이때의 상태는 클라이언트의 것이고, 코드 또한 클라이언트의 컴퓨터에서 실행됩니다. 예를 들면 <Couter />와 같이 사용자와 즉각적인 상호작용을 해야 하는 컴포넌트가 해당됩니다.
- 물론! ‘초기 상태’를 활용하여 HTML을 생성하기 위해서, 서버에서도 해당 코드가 실행되긴 합니다. 그러나 서버는 count = 0만 알 뿐, 현재 상태를 모릅니다. 서버와 클라이언트가 서로 상태를 전달한다고 하면 너무 느리고, 항상 가능할 것이라는 보장도 없죠
UI = f(data)
data는 서버의 것이고, 함수 또한 서버에서만 실행됩니다. readFile과 같은 서버 전용 API는 클라이언트에서 사용할 수도 없습니다. <PostPreview />와 같이 데이터 전처리를 수행하는 컴포넌트가 해당됩니다.
현실은 UI = f(data,state)
현실의 개발에서는 state만, data만 활용하지 않습니다. 이 둘은 서로 섞이어 현재 상태를 나타내는 UI를 제공합니다.
⇒ 이것이 어떻게 펼쳐지고 있을지는 다음 아티클에서 계속하겠습니다!