이 연구는 부모 컴포넌트에서 자식 컴포넌트로 api를 주입한 뒤, 자식 컴포넌트에서 생성된 데이터를 기반으로 주입받은 api를 호출하도록 코드를 만드는 과정에서 아이디어를 받아 진행되었다. 원래는 단순히 클라이언트 사이드로 동작하는 부모 컴포넌트의 api를 자식 클라이언트 컴포넌트로 전달만 해주려 했는데, 어쩌다보니 서버 사이드 부모 컴포넌트의 서버 액션(server actions)을 클라이언트 컴포넌트로 전달할 수도 있음을 알게 되었다.
부모 컴포넌트(서버 사이드)
// page.tsx
import { ActionsContainer } from '@/containers/actions/actions-container'
import { FunctionComponent } from 'react'
interface PageProps {}
export type Action = {
type: 'button'
label: string
api: (data: any) => unknown
}
const actionsList: Action[] = [
{
type: 'button',
label: '콘솔로그 콜백1',
api: async <T,>(data: T) => {
'use server'
console.log(data)
},
},
{
type: 'button',
label: '콘솔로그 콜백2',
api: async (data: any) => {
'use server'
console.log(data)
},
},
{
type: 'button',
label: 'async 콜백',
api: async (data: { index: number }) => {
'use server'
const { index } = data
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${index}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
)
const result = await response.json()
console.log(result)
},
},
]
const Page: FunctionComponent<PageProps> = async ({}) => {
return (
<main>
<ActionsContainer actionsList={actionsList} />
</main>
)
}
export default Page
서버 액션을 클라이언트 컴포넌트로 전달하기 위해서는 해당 액션이 서버 액션임을 알려주는 'use server'가 함수의 최상단 또는 파일의 최상단에 명시되어 있어야 한다. (이같은 기본 사용 방법은 next.js의 공식문서에 잘 설명되어 있다.)
// actions-container.tsx
'use client'
import { Action } from '@/app/(routes)/callback/page'
import { Button } from '@/components/ui/button'
export const ActionsContainer = ({
actionsList,
}: {
actionsList: Action[]
}) => {
return (
<div>
<div className="flex gap-2">
{actionsList.map((action, index) => (
<Button key={action.label} onClick={() => action.api({ index })}>
{action.label}
</Button>
))}
</div>
</div>
)
}
위 ActionsContainer 컴포넌트는 부모 컴포넌트로부터 받은 actions list를 버튼으로 리스트렌더링 하는 코드이다.
최종적으로 아래와 같은 버튼들이 브라우저에 렌더링되며, 각각의 버튼들을 클릭하면 props로 전달받은 서버 액션 함수들이 호출된다.
가령 async 콜백 버튼을 누르면 next.js 서버에 아래와 같은 콘솔이 찍힌다.
원리
동작 원리를 그림으로 표현하면 아래와 같다.
브라우저에서 서버 액션을 호출하면 내부적으로는 next.js 서버에서 post 요청을 보내는 것으로 처리된다.
브라우저의 post요청을 받은 next.js 서버는 해당 post 요청과 매핑된 서버 액션을 실행시키고, 결과적으로 서버 액션이 next.js server에서 실행된다.
실용성?
솔직히 말하자면 위와 같이 사용하는 콜백 패턴 자체는 자제해서 사용해야할 것이다. 데이터가 단방향으로 흐르는 리액트를 쓰면서 굳이 억지스러운 콜백 API를 자식 컴포넌트에 주입한다? 이치에 맞지도 않고, 복잡도만 늘려 유지보수를 어렵게 할 것이다. 저렇게 코드를 짜야할 상황을 맞닥드리게 된다면 컨텍스트 또는 전역 상태관리를 사용해서라도 컴포넌트의 데이터 흐름 구조를 좀 더 효율적으로 리팩터링해야 할 것이다.
단, 위와 같은 방식으로 서버 액션 함수를 클라이언트로 넘겨줄 수 있다는 사실은 알아둘 필요가 있다. 실용성은 없지만서도 일단 알아두면 언젠가는 써먹을 수 있을 방식같다.
'개발 일지' 카테고리의 다른 글
블로그 이관합니다! (1) | 2024.10.03 |
---|---|
util) fetcher (fetch 래퍼) (0) | 2024.07.10 |
React Hook) hover시 state를 변경하는 Hook (useHover) (0) | 2024.07.09 |
React, Typescript) Debounce-Input 컴포넌트 구현 (0) | 2024.06.27 |
gitlab) runner has never contacted this instance (1) | 2024.05.27 |