在本指南中,我們將逐步使用 @perseid/form 庫建立動態使用者回饋表單,這是 Formik 和 React Hook Form 的強大替代品。您將看到 @perseid/form 如何輕鬆管理表單狀態、驗證和條件渲染。我們將建立的表單將要求用戶對服務進行評分並提供回饋。根據評級,它會顯示“謝謝”訊息或提示用戶提供其他回饋。
?讓我們開始吧!
第一步是定義表單配置。此配置概述了表單的行為方式,包括欄位、步驟以及它們之間的流程。在這裡,我們將根據使用者的評分使用 條件邏輯 建立評分和評論欄位。我們還將定義正面和負面回饋的訊息。
這是設定碼:
import { type Configuration } from "@perseid/form"; const formConfiguration: Configuration = { // Root step-the form will start from there. root: "feedback", // Callback triggered on form submission. onSubmit(data) { alert(`Submitting the following JSON: ${JSON.stringify(data)}`); return Promise.resolve(); }, // `fields` define the data model the form is going to deal with. // Expect the submitted data JSON to match this schema. fields: { rating: { type: "integer", required: true, }, review: { type: "string", required: true, // Display this field only if condition is met... condition: (inputs) => inputs.rating !== null && (inputs.rating as number) < 3, }, // Type `null` means that the value of this field will not be included in submitted data. submit: { type: "null", submit: true, }, message_good: { type: "null", }, message_bad: { type: "null", }, }, // Now that fields are defined, you can organize them in a single or multiple steps, // depending on the UI you want to build! steps: { feedback: { fields: ["rating", "review", "submit"], // Whether to submit the form at the end of this step. submit: true, // Next step is conditionned to previous user inputs... nextStep: (inputs) => (inputs.rating as number) < 3 ? "thanks_bad" : "thanks_good", }, thanks_good: { fields: ["message_good"], }, thanks_bad: { fields: ["message_bad"], }, }, };
在此配置:
這裡要掌握的重點是fields屬性的作用。它定義了將提交的資料的結構,本質上充當資料模型。相較之下,steps 屬性概述了表單的流程,決定如何將這些欄位呈現給使用者。
現在我們已經有了配置,是時候建立將呈現表單的實際 UI 了。使用@perseid/form/react,我們可以建立自訂欄位元件來管理表單每個部分的使用者互動。
這是核心 React 元件:
import React from "react"; import Form, { type FormFieldProps } from "@perseid/form/react"; // The actual React component, used to build the UI! function Field(props: FormFieldProps): JSX.Element { const { path, engine, value, status } = props; const [currentRating, setCurrentRating] = React.useState(0); // Display a different element depending on the field... if (path === "thanks_good.1.message_good") { return ( <div className="message"> <h1>Thanks for the feedback ?</h1> <p>We are glad you enjoyed!</p> </div> ); } if (path === "thanks_bad.1.message_bad") { return ( <div className="message"> <h1>We're sorry to hear that ?</h1> <p>We'll do better next time, promise!</p> </div> ); } if (path === "feedback.0.rating") { return ( // Depending on the field status, define some extra classes for styling... <div className={`rating ${status === "error" ? "rating--error" : ""}`} onMouseLeave={() => { setCurrentRating((value as number | null) ?? 0); }} > <h1>How would you rate our service?</h1> {[1, 2, 3, 4, 5].map((rating) => ( <span key={rating} className={`rating__star ${ currentRating >= rating ? "rating__star--active" : "" }`} onMouseEnter={() => { setCurrentRating(rating); }} onClick={() => { // On click, notify the form engine about new user input. engine.userAction({ type: "input", path, data: rating }); }} ></span> ))} </div> ); } if (path === "feedback.0.review") { return ( <div className={`review ${status === "error" ? "review--error" : ""}`}> <label>Could you tell us more?</label> <textarea onChange={(e) => engine.userAction({ type: "input", path, data: e.target.value }) } /> </div> ); } // path === 'feedback.0.submit' return ( <button className="submit" onClick={() => { engine.userAction({ type: "input", path, data: true }); }} > Submit </button> ); }
這裡,Field 元件使用 path 屬性決定要渲染什麼:
根據評分顯示的「謝謝」訊息。此表單將根據使用者輸入動態調整其欄位和步驟。
很酷,對吧?
現在我們的表單配置和元件已準備就緒,讓我們將它們整合到一個基本的 React 應用程式中。這是初始化和渲染表單的程式碼:
import { createRoot, type Root } from "react-dom/client"; // Let's run the app! let app: Root; // Creating React root... const container = document.querySelector("#root") as unknown as HTMLElement; app = createRoot(container); app.render( // Router is the main component for any Perseid app. <Form Field={Field} configuration={formConfiguration} /> );
此程式碼使用 React 的 createRoot API 將表單安裝到 DOM。 Form 元件連接我們的配置和 Field 元件,處理其他所有事情。
好吧,我們有了應用程式邏輯,但是如果您現在運行程式碼,您會發現它有點......原始?
所以,讓我們透過添加一些樣式和動畫來修飾表單!下面是一個簡單的樣式表,使它更具吸引力:
// A few animations for fun... @keyframes swipe-out { 0% { opacity: 1; transform: translateX(0); } 75% { opacity: 0; transform: translateX(-100%); } 100% { opacity: 0; transform: translateX(-100%); } } @keyframes swipe-in-one { 0% { opacity: 0; transform: translateX(100%); } 75% { transform: translateX(0); } 100% { opacity: 1; transform: translateX(0); } } @keyframes swipe-in-two { 0% { opacity: 0; transform: translateX(0); } 75% { transform: translateX(-100%); } 100% { opacity: 1; transform: translateX(-100%); } } @keyframes bubble-in { 0% { transform: scale(0.5); } 75% { transform: scale(1.5); } 100% { transform: scale(1); } } @keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } // Some global basic styling... * { box-sizing: border-box; } body { margin: 0; display: grid; height: 100vh; color: #aaaaaa; align-items: center; font-family: "Helvetica", sans-serif; } // And form-specific styling. .perseid-form { width: 100%; margin: auto; &__steps { display: flex; overflow: hidden; } &__step { min-width: 100%; padding: 1rem 3rem; animation: 500ms ease-in-out forwards swipe-out; &__fields { display: grid; row-gap: 2rem; } } &__step[class*="active"]:first-child { animation: 500ms ease-in-out forwards swipe-in-one; } &__step[class*="active"]:last-child:not(:first-child) { animation: 500ms ease-in-out forwards swipe-in-two; } } .submit { border: none; cursor: pointer; padding: 1rem 2rem; border-radius: 8px; color: #fefefe; font-size: 1.25rem; background: #46c0b0; justify-self: flex-end; transition: all 250ms ease-in-out; &:hover { background: #4cccbb; } } .rating { position: relative; padding: 0.25rem 0; &__star { cursor: pointer; display: inline-block; font-size: 2rem; min-width: 2rem; min-height: 2rem; &::after { content: "⚪️"; } &--active { animation: 250ms ease-in-out forwards bubble-in; &::after { content: "?"; } } } &[class*="error"] { &::after { left: 0; bottom: -1.5rem; color: #f13232; position: absolute; font-size: 0.75rem; content: "? This field is required"; animation: 250ms ease-in-out forwards fade-in; } } } .review { display: grid; row-gap: 1rem; position: relative; animation: 250ms ease-in-out forwards fade-in; label { font-size: 1.25rem; } textarea { resize: none; min-height: 5rem; border-radius: 8px; border: 1px solid #46c0b0; transition: all 250ms ease-in-out; } &[class*="error"] { &::after { left: 0; bottom: -1.5rem; color: #f13232; position: absolute; font-size: 0.75rem; content: "? This field is required"; animation: 250ms ease-in-out forwards fade-in; } } } @media screen and (min-width: 30rem) { .perseid-form { max-width: 30rem; } }
瞧?
恭喜! ?您剛剛使用 Perseid 和 React 建立了一個動態使用者回饋表單。
在本教學中,我們介紹如何:
請隨意嘗試其他欄位和步驟以適合您的用例。享受建立精彩表單的樂趣! ?
以上是使用 Perseid 和 React 建立使用者回饋表的詳細內容。更多資訊請關注PHP中文網其他相關文章!