React - UseEffect 不使用新数据重新渲染?

时间:2021-03-24 00:10:34

标签: reactjs

这是我的 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 中的重新渲染条件(以及事实上它也是我的状态之一),我的页面没有使用添加的标签更新

2 个答案:

答案 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 在节点创建时访问它。或者您可以使用 onKeyDownonKeyPress

将回调直接放在节点本身上

答案 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>
  );
}