You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SolidJS中父组件API调用后如何触发子组件重置状态?

问题描述

我用SolidJS、Vite和TailwindCSS做SPA,需要架构建议。页面有三个组件:

  • 父组件App
  • 创建联系人组件
  • 联系人列表组件

用户点击+按钮,下方弹出菜单式弹窗,包含全名、邮箱两个字段,以及“添加”“清除”按钮。这个弹窗是独立的Add New Person组件,自身维护弹窗状态(是否打开)、表单字段(姓名、邮箱)和表单有效性,组件接收父组件传入的createNewContact属性,在用户点击“添加”且表单验证通过时调用。表单的valid状态会随用户输入实时更新并反馈。

需求是:点击“添加”时调用API创建联系人——成功的话,Add New Person组件清空字段并关闭弹窗,同时联系人列表更新显示新联系人;失败的话,显示错误信息,弹窗保持打开。

我试了两种方案:

  1. Add New Contact组件维护自身状态和有效性,通过传入的createNewContact事件通知父组件执行操作
  2. 状态由父组件管理,通过props绑定到创建组件:<CreateNewContact firstname={newContact.firstName} ... addNewContact={handleAddNewContact} />

但两种方案里,都不知道怎么通知子组件后续操作(成功就关闭清空/失败就保持打开并显示错误)。还见过类似let [NewContactForm, openAddNewContact, closeNewContact, clearNewContact] = createNewContactForm();的写法,不确定是不是SolidJS的正确用法。

我倾向第一种方案,因为父组件负责跨组件通信,第二种方案状态分散,我想尽量遵循单一职责原则(SRP)。另外也关心测试问题,但这是另一个范畴,之后再研究重构。

解决方案

方案1:利用Promise回调(适配组件自维护状态)

让父组件传入的createNewContact方法返回Promise,子组件根据Promise的状态(resolve/reject)执行对应操作:

  1. 父组件修改newContact方法,模拟API请求并返回Promise:
function newContact(c) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟API成功逻辑
      setContacts(produce(contacts => {
        contacts.push(c);
      }));
      resolve();
      // 模拟失败:reject(new Error("创建联系人失败"));
    }, 1000);
  });
}
  1. 子组件修改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获取组件实例,调用子组件暴露的方法:

  1. 子组件暴露控制方法:
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>
  );
}
  1. 父组件用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} />
    </>
  );
}
完整代码示例(方案1实现)
/*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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:00:58