서버 액션은 클라이언트 코드를 줄이고 서버와의 통신이 필요한 상호작용을 단순화하기 위한 아이디어로 등장했습니다. 개발자가 더 적은 양의 코드를 작성할 수 있게 해주는 탁월한 솔루션입니다. 그러나 다른 프레임워크에서의 구현과 관련하여 간과해서는 안 되는 몇 가지 과제가 있습니다.
이 기사에서는 이러한 문제에 대해 이야기하고 브리사에서 어떻게 해결책을 찾았는지 설명합니다.
서버 작업이 제공하는 기능을 이해하려면 서버와의 통신 방식을 검토하는 것이 유용합니다. 아마도 서버와의 각 상호 작용에 대해 다음 작업을 수행하는 데 익숙할 것입니다.
이 7가지 작업은 각 상호작용마다 반복됩니다. 예를 들어, 10개의 서로 다른 상호 작용이 있는 페이지가 있는 경우 매우 유사한 코드를 10번 반복하여 요청 유형, URL, 전송된 데이터 및 고객 상태와 같은 세부 정보만 변경합니다.
익숙한 예는 다음과 같습니다
A:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
그리고 서버에서:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
클라이언트 번들 크기 증가... 그리고 개발자들의 좌절감.
서버 작업은 이러한 작업을 원격 프로시저 호출(RPC)으로 캡슐하여 클라이언트-서버 통신을 관리하고 클라이언트의 코드를 줄이고 서버의 논리를 중앙 집중화합니다. :
여기에서는 Brisa RPC가 모든 작업을 수행합니다.
이것은 서버 구성요소의 코드입니다:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
여기서 클라이언트 코드는 서버 구성 요소이므로 개발자가 작성하지 않습니다. onInput 이벤트는 디바운스 후에 수신되어 클라이언트 RPC에 의해 처리되는 반면 서버 RPC는 "작업 신호"를 사용하여 해당 상점 속성에 등록된 신호가 있는 웹 구성 요소를 트리거합니다.
보시다시피 이렇게 하면 서버 코드가 크게 줄어들고 무엇보다도 클라이언트의 코드 크기가 상호 작용할 때마다 증가하지 않습니다. RPC 클라이언트 코드는 이러한 상호 작용이 10개이든 1000개이든 상관없이 고정된 2KB를 차지합니다. 이는 클라이언트 번들 크기가 0바이트 증가 즉, 증가하지 않는다는 의미입니다.
게다가 재렌더링이 필요한 경우 서버에서 수행되고 HTML 스트리밍으로 반환되므로 이후 클라이언트에서 이 작업을 수행해야 했던 기존 방식보다 사용자가 변경 사항을 훨씬 빨리 확인할 수 있습니다. 서버 응답입니다.
이런 식으로:
React와 같은 다른 프레임워크에서는 이벤트 대신 form onSubmit의 일부인 오직 작업에만 집중했습니다.
클라이언트 코드를 추가하지 않고 서버 구성 요소에서도 처리해야 하는 비형식 이벤트가 많기 때문에 이것이 문제가 됩니다. 예를 들어 자동 제안을 수행하기 위한 입력의 onInput, 무한 스크롤을 로드하기 위한 onScroll, onMouseOver 호버 등을 수행합니다.
또한 많은 프레임워크에서는 HTMX 라이브러리를 서버 액션에 대한 매우 다른 대안으로 여겼습니다. 그러나 실제로는 HTML에 추가 속성을 추가함으로써 더 많은 잠재력을 가질 수 있도록 서버 액션과 결합할 수 있는 아주 좋은 아이디어를 가져왔습니다. RPC 클라이언트는 이전에 본 debounceInput과 같은 것을 고려할 수 있습니다. 또한 요청하는 동안 스피너를 표시하거나 RPC 클라이언트에서 오류를 처리할 수 있는 표시기와 같은 다른 HTMX 아이디어도 있습니다.
React에 Server Actions가 도입되면서 새로운 패러다임 전환이 발생하여 많은 개발자들이 작업 시 멘탈 칩을 바꿔야 했습니다.
저희는 이를 웹 플랫폼에 최대한 친숙하게 만들고 싶었습니다. 이렇게 하면 서버에서 직렬화된 이벤트를 캡처하고 해당 속성을 사용할 수 있습니다. 약간 다른 유일한 이벤트는 이미 FormData를 전송하고 e.formData 속성을 갖는 onSubmit이지만 그럼에도 불구하고 나머지 이벤트 속성은 상호 작용 가능합니다. 다음은 양식 재설정의 예입니다.
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
이 예에는 클라이언트 코드가 전혀 없으며 서버 작업 중에 CSS를 사용하여 표시기로 제출 버튼을 비활성화하여 양식을 두 번 제출할 수 없도록 할 수 있습니다. 서버에서 작업을 수행한 후 e.formData를 사용하여 양식 데이터에 액세스한 다음 동일한 이벤트 API를 사용하여 양식을 재설정합니다.
정신적으로 웹 플랫폼 작업과 유사합니다. 유일한 차이점은 모든 서버 구성 요소의 모든 이벤트가 서버 작업이라는 점입니다.
이렇게 하면 "사용자 서버" 또는 "클라이언트 사용"을 넣을 필요가 필요하지 않습니다. 구성요소 더 이상.
모든 것이 서버에서만 실행됩니다. 유일한 예외는 클라이언트에서 실행되는 src/web-comComponents 폴더에 대한 것이며 이벤트는 정상입니다.
Brisa에서 서버 액션은 마치 DOM 이벤트인 것처럼 서버 구성 요소 간에 전파됩니다. 즉, 서버 액션에서 서버 컴포넌트 소품의 이벤트를 호출한 다음 상위 서버 컴포넌트의 서버 액션이 실행되는 등의 작업을 할 수 있습니다.
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
이 경우 상위 컴포넌트에서 onAfterMyAction 이벤트가 실행되고 서버에서 액션을 수행할 수 있습니다. 이는 여러 서버 구성요소에 영향을 미치는 서버 작업을 수행하는 데 매우 유용합니다.
특히 지난 몇 주 동안 X(이전의 Twitter)에 대한 여러 토론 이후 웹 구성 요소에 대한 눈살을 찌푸리게 되었습니다. 그러나 HTML의 일부이기 때문에 다음과 같은 여러 가지 이유로 Server Actions와 상호작용하는 가장 좋은 방법입니다.
웹 구성 요소에서 속성을 사용하려면 웹 구성 요소를 사용하지 않고 서버에서 클라이언트로 데이터를 전송하는 것과 같은 방식으로 직렬화가 필요하므로 두 가지를 모두 사용하면 관리할 추가 직렬화가 없습니다.
참고: HTML을 스트리밍하고 diffing 알고리즘으로 처리하는 방법은 관심이 있으시면 다른 기사에서 설명한 내용입니다.
브리사에서는 서버 액션에 더욱 강력한 기능을 부여하기 위해 새로운 개념을 추가했는데, 이 개념을 "액션 시그널"이라고 합니다. "Action Signals"의 아이디어는 2개의 매장이 있고, 하나는 서버에 있고 다른 하나는 클라이언트에 있다는 것입니다.
왜 매장이 2개인가요?
기본 서버 스토어 라이브는 요청 수준에서만 가능합니다. 그리고 클라이언트에게 표시되지 않는 데이터를 공유할 수 있습니다. 예를 들어 미들웨어가 사용자를 설정하고 모든 서버 구성 요소의 민감한 사용자 데이터에 액세스하도록 할 수 있습니다. 요청 수준에 따라 생활하면 각 요청에 자체 저장소가 있고 어떤 데이터베이스에도 저장되지 않습니다. 요청이 완료되면 기본적으로 종료됩니다.
한편, 클라이언트 스토어에서는 소비 시 각 속성이 신호인 스토어입니다. 업데이트되면 해당 신호를 듣고 있던 웹 구성 요소가 반응합니다.
그러나 "액션 시그널"의 새로운 개념은 요청 이상으로 서버 스토어의 수명을 연장할 수 있다는 것입니다. 이렇게 하려면 다음 코드를 사용해야 합니다:
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
이 transferToClient 메소드는 서버 데이터를 공유하여 클라이언트 스토어에 신호로 변환됩니다. 이런 방식으로 서버에서 재렌더링을 수행할 필요가 없는 경우가 많으며, 간단히 서버 작업을 통해 해당 신호를 듣고 있던 웹 구성 요소의 신호에 반응할 수 있습니다.
이번 매장 이전으로 서버 스토어의 수명이 바뀌었습니다.
초기 서버 컴포넌트 → 클라이언트 → 서버 액션 → 클라이언트 → 서버 액션 렌더링...
그래서 요청 수준에서만 생활하는 것에서 영구적으로 생활하고, 페이지 간 탐색과 호환됩니다.
예:
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
이 예에서는 오류 저장소 속성의 수명을 연장하여 클라이언트에서 사용하지 않고 서버 작업에서 재사용한 다음 마지막으로 서버 작업을 다시 렌더링합니다. 이 경우에는 민감하지 않은 데이터이므로 암호화할 필요가 없습니다. 이 예제 코드는 모두 서버에서 발생합니다. 심지어 다시 렌더링하는 경우에도 사용자는 서버 RPC가 스트리밍으로 HTML 청크를 보내고 클라이언트 RPC가 이를 처리하여 비교하고 표시하는 서버에서 이 렌더링 후에 오류를 보게 됩니다. 사용자에게 피드백을 주기 위해 오류가 발생했습니다.
서버 작업 내에서 렌더링 수준에 존재했던 일부 변수가 사용되는 경우 보안 수준에서 Next.js 14와 같은 많은 프레임워크는 이 데이터를 암호화하여 다음에서 사용되는 데이터의 스냅샷을 생성합니다. 렌더링 시간. 어느 정도 괜찮지만 항상 데이터를 암호화하면 계산 비용이 발생하며 항상 민감한 데이터는 아닙니다.
Brisa에서는 이 문제를 해결하기 위해 다양한 요청이 있습니다. 초기 렌더링에서는 값이 있고, 서버 작업에서는 이 요청에 있는 값을 캡처할 수 있습니다.
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
이는 어떤 경우에는 유용하지만 항상 그런 것은 아닙니다. 예를 들어 Math.random을 수행하는 경우 초기 렌더링과 서버 작업 실행 간에는 확실히 다릅니다.
<input onInput={(e) => { // debounce if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fetch("/api/search", { method: "POST", body: JSON.stringify({ query: e.target.value }), }) .then((res) => res.json()) .then((data) => { setState({ data }); }); }, 300); }} />
이것이 우리가 서버 스토어에서 클라이언트 스토어데이터를 전송하기 위해 "액션 시그널"이라는 개념을 만든 이유입니다. > 개발자는 암호화할지 암호화할지결정결정
할 수 있습니다.
app.post("/api/search", async (req, res) => { const { query } = req.body; const data = await search(query); res.json(data); });
<input debounceInput={300} onInput={async (e) => { // All this code only runs on the server const data = await search(e.target.value); store.set("query", data); store.transferToClient(["query"]); }} />
웹 구성 요소(클라이언트) 내부는 항상 암호화되지만 서버에서는 항상 해독됩니다. 참고
: Brisa는 OpenSSL에서 권장하는 정보를 안전하게 암호화하는 데 사용되는 암호화 알고리즘의 조합인 aes-256-cbc를 암호화에 사용합니다. 암호화 키는 프로젝트 빌드 중에 생성됩니다.
결론
Brisa에서는 쉽게 웹 구성 요소 작성을 지원하고 싶지만 클라이언트 코드 없이 SPA를 만들고 순수 클라이언트 상호 작용이거나 웹 API를 터치해야 하는 경우에만 웹 구성 요소를 사용할 수 있도록 하는 것이 목표입니다. 그렇기 때문에 클라이언트 코드를 작성하지 않고도 서버와 상호 작용할 수 있도록 하는 서버 작업이 매우 중요합니다.
위 내용은 서버 작업이 수정되었습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!