这是我的 React Hook:
function Student(props){
const [open, setOpen] = useState(false);
const [tags, setTags] = useState([]);
useEffect(()=>{
let input = document.getElementById(tagBar);
input.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById(tagButton).click();
}
});
},[tags])
const handleClick = () => {
setOpen(!open);
};
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
tagList.push(input.value);
console.log("tag");
console.log(tags);
console.log("taglist");
console.log(tagList);
setTags(tagList);
}
const tagDisplay = tags.map(t => {
return <p>{t}</p>;
})
return(
<div className="tags">
<div>
{tagDisplay}
</div>
<input type='text' id={tagBar} className="tagBar" placeholder="Add a Tag"/>
<button type="submit" id={tagButton} className="hiddenButton" onClick={addTag}></button>
<div>
);
我想要做的是能够为这些学生元素添加一个标签(我有多个但每个元素彼此独立)并且添加的标签显示在我的显示的标签部分。我还需要通过在输入字段上按 Enter 来触发此操作。
由于我不确定的原因,我必须将 enter 绑定放在 useEffect 中(可能是因为输入元素尚未呈现)。
现在,当我在输入字段中输入文本时,它会正确更新 tags/tagList 变量,但是通过 console.logs 可以看到,即使我将标签设置为 useEffect 中的重新渲染条件(以及事实上它也是我的状态之一),我的页面没有使用添加的标签更新
答案 0 :(得分:1)
您是对的,该元素在第一次渲染时不存在,这就是 useEffect
可以派上用场的原因。至于为什么不重新渲染,您将标签作为依赖项传入以检查重新渲染。问题是,tags 是一个数组,这意味着它比较的是内存引用而不是内容。
var myRay = [];
var anotherRay = myRay;
var isSame = myRay === anotherRay; // TRUE
myRay.push('new value');
var isStillSame = myRay === anotherRay; // TRUE
// setTags(sameTagListWithNewElementPushed)
// React says, no change detected, same memory reference, skip
由于您的 add tag 方法将新元素推送到同一个数组引用中,所以 useEffect 认为它是同一个数组并且不会重新触发。最重要的是,React 只会在其 props 更改、状态更改或请求强制重新渲染时重新渲染。在你的情况下,你没有改变状态。试试这个:
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
// Create a new array reference with the same contents
// plus the new input value added at the end
setTags([...tagList, input.value]);
}
如果您不想使用 useEffect
,我相信您也可以使用 useRef
在节点创建时访问它。或者您可以使用 onKeyDown
或 onKeyPress
答案 1 :(得分:0)
我在你的代码中发现了一些错误。首先,您自己附加事件侦听器,这在反应中不是首选。另一方面,如果你真的需要在 useEffect 内部为 DOM 添加监听器,你也应该在你之后清理,否则,组件重新渲染时会添加另一个监听器。
useEffect( () => {
const handleOnKeyDown = ( e ) => { /* code */ }
const element = document.getElementById("example")
element.addEventListener( "keydown", handleOnKeyDown )
return () => element.removeEventListener( "keydown", handleOnKeyDown ) // cleaning after effect
}, [tags])
使用 React 处理事件的更好方法是使用合成事件和组件道具。
const handleOnKeyDown = event => {
/* code */
}
return (
<input onKeyDown={ handleOnKeyDown } />
)
第二件事是每个 React 组件都应该有唯一的键。没有它,React 可能无法正确渲染子列表并渲染所有子列表,这会对大型列表或具有许多子项的列表项产生不良的性能影响。使用map时默认没有设置该键,请自行处理。
tags.map( (tag, index) => {
return <p key={index}>{tag}</p>;
})
第三,当您尝试添加标签时,您会再次查询 DOM,而不使用 React 语法。此外,您根据以前的版本更新当前状态,这可能会导致问题,因为 setState 是异步函数,有时无法立即更新状态。
const addTag = newTag => {
setState( prevState => [ ...prevState, ...newTage ] ) // when you want to update state with previous version you should pass callback which always get correct version of state as parameter
}
我希望这篇评论能帮助你理解 React。
function Student(props) {
const [tags, setTags] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleOnKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
addTag();
}
};
function addTag() {
setTags((prev) => [...prev, inputValue]);
setInputValue("");
}
return (
<div className="tags">
<div>
{tags.map((tag, index) => (
<p key={index}>{tag}</p>
))}
</div>
<input
type="text"
onKeyDown={handleOnKeyDown}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a Tag"
/>
<button type="submit" onClick={addTag}>
ADD
</button>
</div>
);
}