水合比较器
한국어 원문으로 표시합니다.
목표
서버가 그린 트리와 클라이언트가 그린 트리를 견줘 어긋난 자리를 찾아내고, 그것을 어느 경계까지 다시 그려야 하는지로 옮기는 비교기를 만듭니다.
왜 중요한가
하이드레이션은 이미 있는 DOM 에 처리기를 붙이는 일이라, 두 쪽이 다른 것을 그리면 화면은 조용히 틀린 채로 남습니다. 그래서 프레임워크들은 어긋난 자리를 찾아 알려 주는 일에 공을 들이는데, 그 알림이 쓸모 있으려면 두 가지가 필요합니다.
첫째, 모양이 어긋난 자리에서 멈춰야 합니다. 목록에 항목 하나가 앞으로 끼어들면 그 아래가 통째로 밀리는데, 계속 내려가면 불일치 수백 건이 쏟아지고 진짜 원인이 묻힙니다. 둘째, 경계를 알아야 합니다. 같은 불일치라도 경계 안쪽이면 그 하위만 다시 그리면 되지만, 경계 밖이면 페이지 전체를 다시 그립니다.
트리와 경로의 모양
노드는 둘 중 하나입니다.
- 문자열 — 텍스트 노드
{ tag, props, children }— 요소. 경계를 여는 노드는boundary: "이름"을 더 가집니다
경로는 뿌리가 "$" 이고, i 번째 자식은 부모 경로 + "/" + i 입니다.
예: 뿌리의 두 번째 자식의 첫 자식은 "$/1/0".
불일치 하나는 { path, kind } 이고, kind 는 text · attr · tag ·
children 중 하나입니다. attr 이면 name 에 속성 이름을 더 담습니다.
단계
- 텍스트 비교와 경로
- 속성 비교 — 순서는 불일치가 아니다
- 모양이 어긋나면 멈춘다
- 억제는 한 겹만
- 가장 가까운 경계 붙이기
- 다시 그릴 곳 계산 —
repairPlan - 직접 재서 적기 —
/root/work/hydrate/07-report.txt - 정리 —
/root/work/hydrate/08-notes.md
참고
- 7단계가 읽는 트리 한 쌍은 이미지에 들어 있습니다:
/opt/fixtures/ssr-hydrate/pair.json(server와client두 열쇠). - 채점기는 여러분의
/root/work/hydrate/hydrate.mjs를 import 해서 계약만 확인합니다. 브라우저도 프레임워크도 쓰지 않습니다. - 흔한 실수 둘: 속성 순서를 불일치로 세는 것, 그리고 자식 수가 다른데도 계속 내려가 불일치를 부풀리는 것.
텍스트 비교와 경로
/root/work/hydrate/hydrate.mjs 에서 diff(server, client) 를 export 하세요. 어긋난 자리마다 {path, kind} 를 담은 배열을 돌려줍니다. 텍스트가 다르면 kind 는 "text", 한쪽만 텍스트이면 "tag" 입니다. 같은 트리면 빈 배열입니다.
mkdir -p /root/work/hydrate. 경로는 뿌리가 "$" 이고 i 번째 자식은 부모 경로에 "/" + i 를 붙인 것입니다. 재귀로 내려가면서 경로를 인자로 들고 다니세요. 이 단계에서는 두 트리의 모양이 같다고 봐도 됩니다.
속성 비교 — 순서는 불일치가 아니다
요소 노드의 props 를 견주세요. 값이 다르거나 한쪽에만 있으면 {path, kind: "attr", name} 을 담습니다. 속성의 순서는 불일치가 아닙니다.
양쪽 열쇠를 모아 하나의 집합으로 만든 뒤 이름별로 값을 견주면 순서가 자연히 빠집니다. 한쪽에만 있는 속성은 반대쪽 값이 undefined 라 자동으로 걸립니다. 브라우저는 속성을 순서로 구분하지 않으므로, 순서를 불일치로 세면 멀쩡한 페이지가 온통 빨개집니다.
모양이 어긋나면 멈춘다
태그가 다르면 {path, kind: "tag"} 한 건을 담고 그 아래로 내려가지 마세요. 자식 수가 다르면 {path, kind: "children"} 한 건을 담고 역시 멈춥니다.
목록에 항목 하나가 앞으로 끼어들면 그 아래가 전부 밀립니다. 계속 내려가면 짝이 아닌 노드끼리 견주게 되어 불일치가 수백 건 쏟아지고, 진짜 원인 하나가 그 더미에 묻힙니다. 채점기가 한 칸 밀린 목록으로 그 점을 봅니다.
억제는 한 겹만
diff(server, client, options) 의 options.suppress 에 경로 배열을 받으세요. 그 경로 자체의 text 와 attr 불일치만 빼고, 그 아래 경로는 그대로 보고합니다. tag 와 children 은 억제 대상이 아닙니다.
세 번째 인자가 없거나 빈 객체여도 그대로 동작해야 합니다. 한 겹만 허용하는 이유는 React 의 suppressHydrationWarning 과 같습니다 — 시각 한 곳을 봐주자고 그 아래까지 눈을 감으면 그 안의 진짜 불일치가 영영 안 보입니다.
가장 가까운 경계 붙이기
불일치마다 boundary 를 더 담으세요. 서버 트리에서 그 경로를 감싸는 가장 가까운 boundary 값이고, 감싸는 것이 없으면 null 입니다. 노드 자신이 경계를 열면 그 노드의 불일치도 자기 경계에 속합니다.
트리를 한 번 훑으며 경로마다 '지금 열려 있는 경계' 를 기록해 두면 나중에 찾아보기만 하면 됩니다. 중첩된 경계에서는 안쪽이 이깁니다. null 이 나오는지가 곧 설계의 성적표입니다 — 그 하나가 페이지 전체를 다시 그리게 만듭니다.
다시 그릴 곳 계산
repairPlan(mismatches) 를 export 하세요. {boundaries, full} 를 돌려줍니다. boundaries 는 다시 그려야 할 경계 id 를 중복 없이 처음 나온 순서대로 담은 배열이고, full 은 경계 밖 불일치가 하나라도 있으면 참입니다.
같은 경계 안에서 불일치가 열 개 나도 다시 그리는 일은 한 번입니다. 그래서 목록이 아니라 집합이 답이지만, 사람이 읽을 순서는 지켜 주는 편이 낫습니다. 경계 밖 불일치는 boundaries 에 넣지 말고 full 로만 표시하세요.
직접 재서 적는다
/opt/fixtures/ssr-hydrate/pair.json 의 server 와 client 를 여러분의 diff 와 repairPlan 에 넣고, 나온 값을 /root/work/hydrate/07-report.txt 에 이름=값 세 줄로 적으세요. mismatches — 불일치 수. boundaries — 다시 그릴 경계 수. full — 경계 밖 불일치가 있었으면 yes, 없으면 no.
짧은 .mjs 를 하나 만들어 돌려 보고 나온 값을 옮겨 적으면 됩니다. 채점기가 같은 파일을 여러분의 비교기로 다시 견줘 적힌 값과 대조하므로 눈으로 어림하지 마세요. 자식 수가 다른 자리에서 멈추지 않으면 불일치 수가 늘어납니다.
무엇이 불일치를 가뒀나
/root/work/hydrate/08-notes.md 에 세 줄 이상. 서버와 클라이언트가 다른 것을 그렸을 때 화면에 무엇이 보이는지, 경계 밖의 불일치가 무엇을 뜻하는지, 억제를 한 겹만 허용하는 이유가 무엇인지.
본문에 불일치, 경계, 억제 가 들어가야 합니다. 이 셋이 SSR 을 쓰는 팀이 실제로 며칠씩 쓰는 세 가지입니다.