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);
问题根源
- 状态隔离与路由冲突:InputForUserProfile的状态是局部的,没有传递给Routing组件;同时InputForUserProfile内部嵌套Routes,和Routing的路由规则冲突,导致导航到/userprofile时,实际渲染的是Routing里的user-profile组件,但该组件的props未被赋值,所以显示undefined。
- 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
相关产品推荐
相关产品推荐

