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

React组件向自定义Web Component传递Props失败问题求助

问题描述

我尝试将InputForUserProfile组件中的name、bio、experience、ctc属性传递给自定义Web Component——userprofile,期望数据发生变化时能在userprofile中展示,但传递失败,控制台打印显示undefined。

现有代码

App.js

import "./App.css";
import Routing from "./Routing";
function App() {
  return (
    <div className="App">
      <Routing/>
    </div>
  );
}
export default App;

Routing.js

import InputForUserProfile from "./InputForUserProfile";
import "./userprofile.js";
import { Link, Route, Routes } from "react-router-dom";
const Routing = ({name, bio, experience, ctc}) => {
    console.log("1", name);
  return (
    <>
      <nav>
        <ul>
          <li>
            <Link to="*">InputFields</Link>
          </li>
          <li>
            <Link to="/userprofile">userprofile</Link>
          </li>
        </ul>
      </nav>
      <Routes>
        <Route path="*" element={<InputForUserProfile />} />
        <Route
          path="/userprofile"
          element={
            <user-profile
              name={name}
              bio={bio}
              exp={experience}
              ctc={ctc}
            />
          }
        />
      </Routes>
    </>
  );
};
export default Routing;

InputForUserProfile.js

import { useState } from "react";
import { Route, Routes } from "react-router-dom";
import "./userprofile.js";
const InputForUserProfile = () => {
  const [name, setName] = useState(localStorage.getItem("name") || "");
  const [bio, setBio] = useState(localStorage.getItem("bio") || "");
  const [experience, setExperience] = useState(
    localStorage.getItem("experience") || ""
  );
  const [ctc, setCtc] = useState(localStorage.getItem("ctc") || "");

  const handleSubmit = (event) => {
    event.preventDefault();
    localStorage.setItem("name", name);
    localStorage.setItem("bio", bio);
    localStorage.setItem("experience", experience);
    localStorage.setItem("ctc", ctc);
  };
  return (
    <>
      <form onSubmit={handleSubmit}>
        <label>
          Change User Name:
          <input
            type="text"
            placeholder="Change User Name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User Bio:
          <textarea
            placeholder="Change User Bio"
            value={bio}
            onChange={(e) => setBio(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User Experience:
          <input
            type="text"
            placeholder="Change User Experience"
            value={experience}
            onChange={(e) => setExperience(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User CTC:
          <input
            type="text"
            placeholder="Change User CTC"
            value={ctc}
            onChange={(e) => setCtc(e.target.value)}
          />
        </label>
        <br />
        <button type="submit">Save Changes</button>
      </form>
      <Routes>
        <Route
          path="/userprofile"
          element={
            <user-profile name={name} bio={bio} exp={experience} ctc={ctc} />
          }
        />
      </Routes>
    </>
  );
};
export default InputForUserProfile;

userprofile.js(自定义Web Component)

class UserProfile extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
        <div id="profile">
        <br /><br />
        <img
          src=""
          alt="Profile Picture"
         
        />
        <h1>
          Name:
          <p id="name"></p>
        </h1>
    
        <h1>
          BIO:
          <p id="bio"></p>
        </h1>
        <h1>
          Experiance:
          <p id="exp"></p>
        </h1>
        <h1>
          CTC:
          <p id="CTC"></p>
        </h1>
        <input type="text" id="user-name"  class="hide-input" placeholder="changeusername">
        <input type="text" id="user-bio" class="hide-input" placeholder="changeuserbio">
        <input type="text" id="user-experience" class="hide-input" placeholder="changeuserexperience">
        <input type="text" id="user-CTC" class="hide-input" placeholder="changeuserCTC">
        <button id="save-button" class="hide-input" >save</button>
    
    
        <button id="edit-button"  >Edit Profile</button
        ><br /><br />
      </div>`;
  }

  connectedCallback() {
    const userVaule = this.shadowRoot.querySelector("div");
    this.shadowRoot.querySelector('#name').textContent = this.getAttribute("name");
    this.shadowRoot.querySelector('#bio').textContent = this.getAttribute("bio");
    this.shadowRoot.querySelector('#exp').textContent = this.getAttribute("exp");
    this.shadowRoot.querySelector('#CTC').textContent = this.getAttribute("ctc");
    userVaule
      .querySelector("#save-button")
      .addEventListener("click", this.saveProfile.bind(this));
    userVaule
      .querySelector("#edit-button")
      .addEventListener("click", this.editProfile.bind(this));
    userVaule.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.add("hide-input");
    });
    
  }
 editProfile() {
    this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.remove("hide-input");
    });

    this.shadowRoot.querySelector("#user-name").value =
      localStorage.getItem("name") || "";

    this.shadowRoot.querySelector("#user-bio").value =
      localStorage.getItem("bio") || "";

    this.shadowRoot.querySelector("#user-experience").value =
      localStorage.getItem("experience") || "";

    this.shadowRoot.querySelector("#user-CTC").value =
      localStorage.getItem("ctc") || "";

  }
  saveProfile() {
    this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.add("hide-input");
    });

    let name = this.shadowRoot.querySelector("#name");
    let bio = this.shadowRoot.querySelector("#bio");
    let exp = this.shadowRoot.querySelector("#exp");
    let CTC = this.shadowRoot.querySelector("#CTC");

    const userName = this.shadowRoot.querySelector("#user-name").value;
    localStorage.setItem("name", userName);

    const userBio = this.shadowRoot.querySelector("#user-bio").value;
    localStorage.setItem("bio", userBio);

    const userExperience =
      this.shadowRoot.querySelector("#user-experience").value;
    localStorage.setItem("exp", userExperience);

    const userCTC = this.shadowRoot.querySelector("#user-CTC").value;
    localStorage.setItem("CTC", userCTC);

    name.textContent = userName;
    bio.textContent = userBio;
    exp.textContent = userExperience;
    CTC.textContent = userCTC;

    
  }
}

customElements.define("user-profile", UserProfile);

问题根源

  1. 状态隔离与路由冲突:InputForUserProfile的状态是局部的,没有传递给Routing组件;同时InputForUserProfile内部嵌套Routes,和Routing的路由规则冲突,导致导航到/userprofile时,实际渲染的是Routing里的user-profile组件,但该组件的props未被赋值,所以显示undefined。
  2. Web Component属性未监听更新:自定义Web Component仅在connectedCallback中初始化一次属性,后续属性变化时不会自动更新UI;且未定义observedAttributes来监听属性变更。

解决方案

步骤1:状态提升到Routing组件

把InputForUserProfile的局部状态移到Routing,作为父组件的状态,确保两个路由组件共享同一数据源:

// 修改后的Routing.js
import InputForUserProfile from "./InputForUserProfile";
import "./userprofile.js";
import { Link, Route, Routes } from "react-router-dom";
import { useState, useEffect } from "react";

const Routing = () => {
  // 从localStorage初始化状态
  const [name, setName] = useState(localStorage.getItem("name") || "");
  const [bio, setBio] = useState(localStorage.getItem("bio") || "");
  const [experience, setExperience] = useState(localStorage.getItem("experience") || "");
  const [ctc, setCtc] = useState(localStorage.getItem("ctc") || "");

  // 同步状态到localStorage
  useEffect(() => {
    localStorage.setItem("name", name);
    localStorage.setItem("bio", bio);
    localStorage.setItem("experience", experience);
    localStorage.setItem("ctc", ctc);
  }, [name, bio, experience, ctc]);

  return (
    <>
      <nav>
        <ul>
          <li>
            <Link to="/">InputFields</Link>
          </li>
          <li>
            <Link to="/userprofile">userprofile</Link>
          </li>
        </ul>
      </nav>
      <Routes>
        <Route path="/" element={
          <InputForUserProfile 
            name={name}
            bio={bio}
            experience={experience}
            ctc={ctc}
            setName={setName}
            setBio={setBio}
            setExperience={setExperience}
            setCtc={setCtc}
          />
        } />
        <Route
          path="/userprofile"
          element={
            <user-profile
              name={name}
              bio={bio}
              exp={experience}
              ctc={ctc}
            />
          }
        />
      </Routes>
    </>
  );
};
export default Routing;

步骤2:简化InputForUserProfile组件

移除内部Routes,改为受控组件,接收父组件的状态和更新回调:

// 修改后的InputForUserProfile.js
import { useState } from "react";
import "./userprofile.js";

const InputForUserProfile = ({ name, bio, experience, ctc, setName, setBio, setExperience, setCtc }) => {
  const handleSubmit = (event) => {
    event.preventDefault();
    // 状态已通过useEffect同步到localStorage,无需重复存储
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <label>
          Change User Name:
          <input
            type="text"
            placeholder="Change User Name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User Bio:
          <textarea
            placeholder="Change User Bio"
            value={bio}
            onChange={(e) => setBio(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User Experience:
          <input
            type="text"
            placeholder="Change User Experience"
            value={experience}
            onChange={(e) => setExperience(e.target.value)}
          />
        </label>
        <br />
        <label>
          Change User CTC:
          <input
            type="text"
            placeholder="Change User CTC"
            value={ctc}
            onChange={(e) => setCtc(e.target.value)}
          />
        </label>
        <br />
        <button type="submit">Save Changes</button>
      </form>
    </>
  );
};
export default InputForUserProfile;

步骤3:优化Web Component支持属性更新

添加observedAttributes和attributeChangedCallback,监听属性变化并更新UI:

// 修改后的userprofile.js
class UserProfile extends HTMLElement {
  // 定义需要监听的属性
  static get observedAttributes() {
    return ["name", "bio", "exp", "ctc"];
  }

  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
        <div id="profile">
        <br /><br />
        <img
          src=""
          alt="Profile Picture"
         
        />
        <h1>
          Name:
          <p id="name"></p>
        </h1>
    
        <h1>
          BIO:
          <p id="bio"></p>
        </h1>
        <h1>
          Experience:
          <p id="exp"></p>
        </h1>
        <h1>
          CTC:
          <p id="CTC"></p>
        </h1>
        <input type="text" id="user-name"  class="hide-input" placeholder="changeusername">
        <input type="text" id="user-bio" class="hide-input" placeholder="changeuserbio">
        <input type="text" id="user-experience" class="hide-input" placeholder="changeuserexperience">
        <input type="text" id="user-CTC" class="hide-input" placeholder="changeuserCTC">
        <button id="save-button" class="hide-input" >save</button>
    
    
        <button id="edit-button"  >Edit Profile</button
        ><br /><br />
      </div>`;
  }

  connectedCallback() {
    const userVaule = this.shadowRoot.querySelector("div");
    // 初始化属性
    this.updateProfile();
    userVaule
      .querySelector("#save-button")
      .addEventListener("click", this.saveProfile.bind(this));
    userVaule
      .querySelector("#edit-button")
      .addEventListener("click", this.editProfile.bind(this));
    userVaule.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.add("hide-input");
    });
  }

  // 属性变化时触发
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.updateProfile();
    }
  }

  // 统一更新UI的方法
  updateProfile() {
    this.shadowRoot.querySelector('#name').textContent = this.getAttribute("name");
    this.shadowRoot.querySelector('#bio').textContent = this.getAttribute("bio");
    this.shadowRoot.querySelector('#exp').textContent = this.getAttribute("exp");
    this.shadowRoot.querySelector('#CTC').textContent = this.getAttribute("ctc");
  }

  editProfile() {
    this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.remove("hide-input");
    });

    this.shadowRoot.querySelector("#user-name").value =
      localStorage.getItem("name") || "";

    this.shadowRoot.querySelector("#user-bio").value =
      localStorage.getItem("bio") || "";

    this.shadowRoot.querySelector("#user-experience").value =
      localStorage.getItem("experience") || "";

    this.shadowRoot.querySelector("#user-CTC").value =
      localStorage.getItem("ctc") || "";
  }

  saveProfile() {
    this.shadowRoot.querySelectorAll("input, #save-button").forEach((el) => {
      el.classList.add("hide-input");
    });

    const userName = this.shadowRoot.querySelector("#user-name").value;
    localStorage.setItem("name", userName);

    const userBio = this.shadowRoot.querySelector("#user-bio").value;
    localStorage.setItem("bio", userBio);

    const userExperience = this.shadowRoot.querySelector("#user-experience").value;
    localStorage.setItem("experience", userExperience);

    const userCTC = this.shadowRoot.querySelector("#user-CTC").value;
    localStorage.setItem("ctc", userCTC);

    // 更新组件属性,触发attributeChangedCallback
    this.setAttribute("name", userName);
    this.setAttribute("bio", userBio);
    this.setAttribute("exp", userExperience);
    this.setAttribute("ctc", userCTC);
  }
}

customElements.define("user-profile", UserProfile);

内容的提问来源于stack exchange,提问作者prabahar.L

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 18:05:46