SolidJS中父组件API调用后如何触发子组件重置状态?
我用SolidJS、Vite和TailwindCSS做SPA,需要架构建议。页面有三个组件:
- 父组件App
- 创建联系人组件
- 联系人列表组件
用户点击+按钮,下方弹出菜单式弹窗,包含全名、邮箱两个字段,以及“添加”“清除”按钮。这个弹窗是独立的Add New Person组件,自身维护弹窗状态(是否打开)、表单字段(姓名、邮箱)和表单有效性,组件接收父组件传入的createNewContact属性,在用户点击“添加”且表单验证通过时调用。表单的valid状态会随用户输入实时更新并反馈。
需求是:点击“添加”时调用API创建联系人——成功的话,Add New Person组件清空字段并关闭弹窗,同时联系人列表更新显示新联系人;失败的话,显示错误信息,弹窗保持打开。
我试了两种方案:
- Add New Contact组件维护自身状态和有效性,通过传入的
createNewContact事件通知父组件执行操作 - 状态由父组件管理,通过props绑定到创建组件:
<CreateNewContact firstname={newContact.firstName} ... addNewContact={handleAddNewContact} />
但两种方案里,都不知道怎么通知子组件后续操作(成功就关闭清空/失败就保持打开并显示错误)。还见过类似let [NewContactForm, openAddNewContact, closeNewContact, clearNewContact] = createNewContactForm();的写法,不确定是不是SolidJS的正确用法。
我倾向第一种方案,因为父组件负责跨组件通信,第二种方案状态分散,我想尽量遵循单一职责原则(SRP)。另外也关心测试问题,但这是另一个范畴,之后再研究重构。
方案1:利用Promise回调(适配组件自维护状态)
让父组件传入的createNewContact方法返回Promise,子组件根据Promise的状态(resolve/reject)执行对应操作:
- 父组件修改
newContact方法,模拟API请求并返回Promise:
function newContact(c) { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟API成功逻辑 setContacts(produce(contacts => { contacts.push(c); })); resolve(); // 模拟失败:reject(new Error("创建联系人失败")); }, 1000); }); }
- 子组件修改
handleSubmit方法,根据Promise结果处理:
async function handleSubmit(e) { e.preventDefault(); // 本地表单验证 if (!newContact.name.trim() || !newContact.email.trim()) { setError("姓名和邮箱不能为空"); return; } try { await props.newContact({...newContact}); // 成功:清空表单+关闭弹窗 clearForm(); setOpen(false); } catch (err) { // 失败:显示错误信息 setError(err.message); } }
同时子组件新增错误状态信号:
let [error, setError] = createSignal("");
并在模板中添加错误显示:
<Show when={error()} style="color: red;">{error()}</Show>
方案2:组件实例暴露方法(用ref调用子组件方法)
SolidJS里可以用ref获取组件实例,调用子组件暴露的方法:
- 子组件暴露控制方法:
function AddContact(props) { let [open, setOpen] = createSignal(false); let [newContact, setNewContact] = createStore({ name: "", email: "" }); let [error, setError] = createSignal(""); // 暴露给父组件的方法 const resetForm = () => { setNewContact({ name: "", email: "" }); setOpen(false); setError(""); }; const setErrorMsg = (msg) => { setError(msg); }; return ( <ref={el => { if (el) { el.resetForm = resetForm; el.setErrorMsg = setErrorMsg; } }}> {/* 原组件内容 */} <Show when={error()} style="color: red;">{error()}</Show> </ref> ); }
- 父组件用ref调用子组件方法:
function App() { let addContactRef; let [contacts, setContacts] = createStore([...]); function newContact(c) { setTimeout(() => { try { setContacts(produce(contacts => contacts.push(c))); addContactRef.resetForm(); } catch (err) { addContactRef.setErrorMsg("创建失败"); } }, 1000); } return ( <> <section> <AddContact ref={addContactRef} newContact={newContact} /> </section> <ContactList contacts={contacts} /> </> ); }
方案3:createComponent式封装(组件与控制方法绑定)
这种方式把组件和操作方法封装在一起,逻辑更紧凑:
function createNewContactForm() { let [open, setOpen] = createSignal(false); let [newContact, setNewContact] = createStore({ name: "", email: "" }); let [error, setError] = createSignal(""); const openForm = () => setOpen(true); const closeForm = () => setOpen(false); const clearForm = () => setNewContact({ name: "", email: "" }); const NewContactForm = (props) => { async function handleSubmit(e) { e.preventDefault(); if (!newContact.name.trim() || !newContact.email.trim()) return; try { await props.onSubmit({...newContact}); clearForm(); closeForm(); } catch (err) { setError(err.message); } } return ( <> <button onClick={openForm}>Add new contact</button> <Show when={open()}> <form onsubmit={handleSubmit}> <Show when={error()} style="color: red;">{error()}</Show> {/* 表单字段与按钮 */} </form> </Show> </> ); }; return [NewContactForm, openForm, closeForm, clearForm]; }
父组件使用方式:
function App() { let [NewContactForm] = createNewContactForm(); let [contacts, setContacts] = createStore([...]); const handleAdd = async (c) => { return new Promise(resolve => { setTimeout(() => { setContacts(produce(contacts => contacts.push(c))); resolve(); }, 1000); }); }; return ( <> <section> <NewContactForm onSubmit={handleAdd} /> </section> <ContactList contacts={contacts} /> </> ); }
/*eslint-disable*/ import { render, Portal } from "solid-js/web"; import { For, createSignal, Show } from "solid-js"; import { createStore, produce } from "solid-js/store"; function AddContact(props) { let [open, setOpen] = createSignal(false); let [newContact, setNewContact] = createStore({ name: "", email: "" }); let [error, setError] = createSignal(""); function toggleAddNew() { setOpen(!open()); setError(""); } async function handleSubmit(e) { e.preventDefault(); // 本地表单验证 if (!newContact.name.trim() || !newContact.email.trim()) { setError("姓名和邮箱不能为空"); return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(newContact.email)) { setError("请输入有效的邮箱地址"); return; } try { await props.newContact({ name: newContact.name, email: newContact.email, }); clearForm(); setOpen(false); } catch (err) { setError(err.message); } } function nameChanged(e) { setNewContact("name", e.target.value); setError(""); } function emailChanged(e) { setNewContact("email", e.target.value); setError(""); } function clearForm() { setNewContact("email", ""); setNewContact("name", ""); setError(""); } return ( <> <button onClick={toggleAddNew}>Add new contact</button> <Show when={open()}> <form method="dialog" onsubmit={handleSubmit}> <Show when={error()} style="color: red; margin-bottom: 10px;">{error()}</Show> <div> <span style="margin-right: 10px">Name:</span> <input type="text" placeholder="Please enter a name" value={newContact.name} onChange={nameChanged} name="name" /> </div> <div> <span style="margin-right: 10px">Email:</span> <input type="text" placeholder="Please enter a e-mail" value={newContact.email} onChange={emailChanged} name="email" /> </div> <div> <button style="margin-right: 10px">Add</button> <button type="button" onClick={clearForm}> Cancel </button> </div> </form> </Show> </> ); } function ContactList(props) { return ( <ul style="list-style-type: decimal"> <For each={props.contacts}> {(contact, i) => ( <div> <li> <span style="margin-right: 10px">{contact.name}</span> <span>{contact.email}</span> </li> </div> )} </For> </ul> ); } function App() { let [contacts, setContacts] = createStore([ { name: "Jodoc Fito", email: "JodocFito@test.com" }, { name: "Archie Dragica", email: "ArchieDragica@test.com" }, ]); function newContact(c) { /* 模拟API请求 */ return new Promise((resolve, reject) => { setTimeout(() => { // 模拟随机失败场景,可注释测试成功情况 if (Math.random() > 0.7) { reject(new Error("创建联系人失败,请重试")); return; } setContacts( produce((contacts) => { contacts.push(c); }), ); resolve(); }, 1000); }); } return ( <> <section> <AddContact newContact={newContact} /> </section> <section> <ContactList contacts={contacts} /> </section> </> ); } render(() => <App />, document.getElementById("app")!);
内容的提问来源于stack exchange,提问作者Abdul

