solid.js 와 WebAssembly
마지막 업데이트:
solid-start
특징
- next.js 와 유사한 파일 기반 라우팅
- CSR, SSR, Streaming SSR, SSG 지원
- API 라우트
- 타입스크립트 기본 지원
solid.js 애플리케이션
개요
solid-start
를 사용해 애플리케이션 생성- 추첨시 Go로 구현된 wasm 함수 호출
- 추첨 결과를 DB에 저장 (미구현)
빌드
- 먼저
wasm
모듈을 빌드한 후solid-start
디렉토리에 배포합니다.$ cd wasm $ task dist-solid-start
solid-start
디렉토리에서 빌드합니다.$ yarn install $ yarn build
실행
$ cd solid-start
$ yarn start
소스코드
solid-start Routing
- 참고 페이지: Routing & pages
- next.js 와 유사한 방식으로 디렉토리 구성
src/routes/study/[id]/members.tsx
파일은/study/:id/members
경로에 매핑됨- route 파라미터 접근시에는
useParams()
사용
export default function Members() {
const params = useParams();
return (
<main>
<h3>{params.id} Members</h3>
</main>
);
}
solid.js store
- solid.js 에서 스토어를 생성하려면 createStore 함수를 사용합니다.
export const [presentationState, setPresentationState] = createStore<PresentationState>({ lastPresentationId: 0, presentations: [], }); export function findPresentation(id: number): Presentation | null { return presentationState.presentations.find((p) => p.id === id); }
- react 와는 달리 컴포넌트 함수 내에 있을 필요가 없으며, 원하는 곳에 생성할 수 있습니다.
- 스토어는 원하는 수만큼 만들 수 있으며, JSX 컴포넌트 내에서 사용되는 경우 반응성을 제공합니다.
- immer.js 와 유사한
produce
함수를 제공하며, 이를 사용해 스토어 객체에 대한 로컬라이즈된 변경을 허용합니다.export function removeMember(id: number) { setMemberState( produce((s: MemberState) => { delete s.members[id]; }) ); }
- 스토어 사용시 함수형 컴포넌트 내에서 실행해야 한다는 제약이 없으며, 반응성을 유지하기 위해서는 JSX 스코프 내에서만 호출하면 됩니다. 아래 코드에서 보면,
drawLots()
함수 내에서 스토어를 업데이트할 수 있으며, 스토어가 업데이트된 경우PresentationList
컴포넌트의presentations
props 는 반응성을 유지하기 때문에 변경된 presentation 에 해당하는 부분만 리렌더링 됩니다.
function drawLots(p: Presentation) {
// ...
const selectedName = wasmDrawLots(JSON.stringify(candidates)) ?? "";
const member = findMemberByName(selectedName);
updatePresentation({
...p,
presenterId: member.id,
});
}
export default function Study() {
const params = useParams();
return (
<main>
<PresentationList
presentations={presentationState.presentations}
onDrawLots={(p) => drawLots(p)}
/>
</main>
);
}
Control Flow
<For>
<For>
를 사용하면 변경된 항목만 효율적으로 업데이트할 수 있습니다.- react 와 달리
key
를 지정하지 않아도 됩니다.
export default function MemberList(props: Props) {
return (
<For each={props.members}>
{(member) => (
<MemberListItem
member={member}
onDelete={(id) => props.onDelete(id)}
onUpdate={(update) => props.onUpdate(update)}
/>
)}
</For>
);
}
<Switch>
<Switch>/<Match>
를 사용하면 2개 이상의 상호 배타적인 조건을 렌더링할 때 유용합니다.
export default function MemberListItem(props: Props) {
const [editMode, setEditMode] = createSignal(false);
return (
<div>
<Switch>
<Match when={!editMode()}>
{getDisplayText(props.member)}
<Button variant="warning" onClick={() => setEditMode(!editMode())}>
변경
</Button>
</Match>
<Match when={editMode()}>
<MemberUpdateForm
member={props.member}
onUpdate={(update: Member) => {
setEditMode(false);
props.onUpdate(update);
}}
/>
</Match>
</Switch>
</div>
);
}
- 시그널은 react의 state 에 해당합니다. 반응성을 유지하기 위해
editModel()
처럼 getter 함수를 호출해 값을 가져옵니다.
미구현 항목
- 페이지 리로딩시 서브 페이지로 랜딩하는 경우,
*.wasm
파일 로딩이 되지 않음.src/root.tsx
파일에서 wasm 초기화 작업을 실행public/init_wasm.js
파일에서main.wasm
파일 로딩시/
디렉토리가 아닌 서브 페이지를 기준으로 파일을 불러오려고 시도하면서 에러 발생 (예:GET /study/main.wasm
)
- 서버사이드에서 DB 연결을 하려고 했으나, 문서(Usage with database)가 아직 작성되지 않은 상태임.
- 서버사이드 API routes 역시 아직 작성되지 않은 상태라, 데이터를 서버에 저장할 수 없음.
소감
어쩔수 없이 react와 비교할 수 밖에 없는데, 사용해 본 소감은
- react 와 다르게 컴포넌트는 단 한 번만 실행되기 때문에, 처음 개발할 때 이게 엄청 헷갈립니다.
- 아직 익숙하지 않고 Best Practice 가 많지 않다보니 코드 작성시 이게 제대로 하고 있는게 맞나? 싶은 경우가 많습니다.
useEffect
사용시에 dependency 를 신경쓰지 않아도 됩니다.- 배열 컴포넌트 렌더링시
key
를 지정하지 않아도 최적화된 렌더링이 됩니다. - zustand 와 같은 별도 라이브러리 없이 기본 제공 스토어만으로도 원하는 기능을 대부분 제공합니다.
- 함수형 컴포넌트 내부에서만 스토어에 접근하거나, 상태에 접근해야한다는 제약이 없어서 컴포넌트가 비대해지지 않아서 소스를 모듈화하기 편해 보입니다.
- next.js 와 유사한 solid-start 를 만들고 있지만, 아직 초창기라 많이 부족해서 production 용으로 사용할 만한 프레임워크는 아직 없다고 할 수 있습니다.
- solid.js 로 서비스를 갈아엎었다는 케이스를 보긴 했는데, 밑바닥부터 하나씩 프로젝트 세팅해가면서 해야할듯 해서 아직은 production에 적용하기는 힘들고, 간단한 애플리케이션 작성 정도로는 괜찮아 보입니다.