Dalam panduan ini, kami akan meneruskan pembinaan borang maklum balas pengguna dinamik menggunakan perpustakaan @perseid/form, alternatif yang berkuasa kepada Formik dan Borang Cangkuk Bertindak Balas. Anda akan melihat cara @perseid/form memudahkan untuk mengurus keadaan borang, pengesahan dan pemaparan bersyarat. Borang yang akan kami bina akan meminta pengguna menilai perkhidmatan dan memberikan maklum balas. Bergantung pada penilaian, ia sama ada akan memaparkan mesej "terima kasih" atau menggesa pengguna untuk memberikan maklum balas tambahan.
? Jom mulakan!
Langkah pertama ialah mentakrifkan konfigurasi borang. Konfigurasi ini menggariskan cara borang itu berkelakuan, termasuk medan, langkah dan aliran di antaranya. Di sini, kami akan mencipta medan untuk penilaian dan ulasan, dengan logik bersyarat berdasarkan penilaian pengguna. Kami juga akan menentukan mesej untuk maklum balas positif dan negatif.
Berikut ialah kod konfigurasi:
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"], }, }, };
Dalam konfigurasi ini:
Perkara utama yang perlu difahami di sini ialah fungsi sifat medan. Ia mentakrifkan struktur data yang akan diserahkan, pada asasnya bertindak sebagai model data. Sebaliknya, sifat langkah menggariskan aliran borang, menentukan cara medan ini akan dipersembahkan kepada pengguna.
Sekarang kita mempunyai konfigurasi, tiba masanya untuk membina UI sebenar yang akan menghasilkan borang. Menggunakan @perseid/form/react, kami boleh mencipta komponen medan tersuai untuk mengurus interaksi pengguna bagi setiap bahagian borang.
Berikut ialah komponen teras 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> ); }
Di sini, komponen Medan menggunakan prop laluan untuk memutuskan perkara yang hendak dipaparkan:
Mesej "Terima kasih" yang dipaparkan berdasarkan penilaian. Borang akan melaraskan medan dan langkahnya secara dinamik berdasarkan input pengguna.
Cukup keren, bukan?
Setelah konfigurasi dan komponen borang kami sudah sedia, mari sepadukan mereka ke dalam apl React asas. Berikut ialah kod untuk memulakan dan memberikan borang:
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} /> );
Kod ini melekapkan borang ke DOM menggunakan API createRoot React. Komponen Borang, yang menghubungkan konfigurasi dan komponen Medan kami, mengendalikan segala-galanya.
Baiklah, kami mempunyai logik apl kami, tetapi jika anda menjalankan kod itu sekarang, anda akan melihat bahawa ia agak... mentah ?
Jadi, mari kita pimp borang dengan menambah beberapa gaya dan animasi! Di bawah ialah helaian gaya ringkas yang menjadikannya lebih menarik:
// 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; } }
Dan voilà ?
Tahniah! ? Anda baru sahaja membina borang maklum balas pengguna dinamik dengan Perseid dan React.
Dalam tutorial ini, kami membincangkan cara untuk:
Jangan ragu untuk bereksperimen dengan medan dan langkah tambahan yang sesuai dengan kes penggunaan anda. Berseronoklah membina borang yang hebat! ?
Atas ialah kandungan terperinci Membina Borang Maklum Balas Pengguna dengan Perseid dan React. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!