【问题标题】:React re renders everything every time key is pressed每次按下键时,React re 都会渲染所有内容
【发布时间】:2021-05-17 04:58:13
【问题描述】:

我在下面有一个图表和一个输入字段。基本上用户可以提出问题,图表会相应地改变。这是它的样子

这是下面的代码示例(忽略 CoreUI 语法)

<CRow>
   <CCol xs="12" sm="12" lg="12">
      <CCard id="answerScreen" style={{height: "500px"}}>
      {
      !tempResponse.loading?  tempResponse.data.map(value => (  
      <ChartRender
         key={uuidv4()}
         data={value} 
         />
      ))
      :  
      <Loader/>
      }
      </CCard>
   </CCol>
</CRow>

<CRow>
   <CCol xs="12" sm="12" lg="12">
      <CCard>
        
            <CCardBody>
               <CForm className="form-horizontal">
                  <CFormGroup>
                           <CInputGroup size="lg" className="input-prepend">
                              <CInputGroupPrepend>
                                 <CInputGroupText className="">@Ask</CInputGroupText>
                              </CInputGroupPrepend>
                              <CInput 
                              size="16" 
                              type="text" 
                              value={userInput || ""}
                              onChange={e=> handleTextBoxInput(e)}
                              onClick={e=> handleTextBoxClick(e)}   
                              onKeyPress={e => handleTextBoxEnter(e)}
                              id="userQuery"
                              />
                            
                              <CInputGroupAppend>
                                 <CButton color="primary">Send</CButton>
                              </CInputGroupAppend>
                           </CInputGroup>
                  </CFormGroup>
               </CForm>
            </CCardBody>
        
      </CCard>
   </CCol>
</CRow>

这就是我定义状态的方式

const [userInput, setUserInput] = React.useState("");
const [tempResponse, setTempResponse] = React.useState({
        data: []
})

我怀疑这部分代码有问题

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={e=> handleTextBoxInput(e)}
   onClick={e=> handleTextBoxClick(e)}   
   onKeyPress={e => handleTextBoxEnter(e)}
   id="userQuery"
/>

我什至尝试像这样将useCallback 添加到onChange 函数

const handleTextBoxInput =  useCallback(e =>{   
        e.preventDefault();
        setUserInput(e.target.value)
}, [])

但没有帮助。我什至读过memo,但不确定在我的情况下在哪里或如何应用它。我做错了什么?

观察

正如@Matthew 所提到的,箭头语法每次都会创建一个不同的回调,这会导致重新渲染,因此必须删除。

但即使在删除它之后,每次按下一个键时图表都会重新呈现。我正在使用Chartjs,它在内部使用canvasChartjs 有问题吗?

【问题讨论】:

  • 使用 onBlur 比 onChange 更适合您吗?
  • @devnull69 关于使用 onBlur,我不能再在文本框中输入了。
  • 函数的绑定方式不会导致它重新渲染。这就是为什么它不能解决你的问题。引用您的函数是一种很好的做法,但这不是您的问题。

标签: reactjs react-functional-component react-memo


【解决方案1】:

您对有问题的代码是正确的。

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={e=> handleTextBoxInput(e)} // performance issue
   onClick={e=> handleTextBoxClick(e)}  // performance issue 
   onKeyPress={e => handleTextBoxEnter(e)} // performance issue
   id="userQuery"
/>

上述代码多次运行时,每次都会重新创建函数。因此,与其这样做,以下就足够了

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={handleTextBoxInput}
   onClick={handleTextBoxClick}
   onKeyPress={handleTextBoxEnter}
   id="userQuery"
/>

useCallback 挂钩返回一个回调函数。您可以简单地将返回值用作正常的事件回调。

【讨论】:

  • @Mathew 感谢您的回复。我也需要访问事件参数。我该怎么做?
  • onClick 这样的事件监听器默认将event 对象传递给回调。
  • 我明白了。我可以访问我的事件参数并做一些事情。但即使在按下键时,图表每次都会重新呈现。我错过了什么吗?
  • SVG 图表库总是会重新渲染。我尝试了其中的多个,并且它们的行为或多或少都相同。我不确定这是技术上的困难还是开发团队不专注于优化。
【解决方案2】:

根据您的输入,每次按键都会触发两个事件 - onKeyPressonChange - 删除 onKeyPress

我怀疑 handleTextBoxEnter 调用 setTempResponse 会更新 tempResponse。设置 UI 所依赖的状态将触发重新渲染。

您还应该使用onSubmit 事件处理表单提交。如果一个元素在表单内具有焦点并且按下了 enter 按钮 - 它会在 onSubmit 上触发。

<form onSubmit={handleTextBoxEnter}></form>

此外,如果键更改,React 将重新渲染。您正在调用密钥中的函数,因此每次更新都会更新它。

tempResponse.data.map((value, i) => (  
 <ChartRender key={`chart-${i}`} data={value} />
))

仅供参考,手动创建一个 UUID 以实现密钥过度杀伤。

export const YourComponent = (): JSX.Element => {
  const [userInput, setUserInput] = useState('');
  const [tempResponse, setTempResponse] = useState({ data: [], loading: true });

  useEffect(()=>{
    // handle initial data loading and set loading to false
  }, [])

  const handleSubmit = (e) => {
    e.preventDefault();
    setTempResponse(your_state_data);
  };

  // e.preventDefault shouldn't be used here and is not required
  const handleChange = ({ target }) => setUserInput(target.value);

  if (tempResponse.loading) {
    return <Loading />;
  }

  // action is set to # for iOS - an action is required to show the virtual submit button on the keyboard
  return (
    <>
      <form action="#" onSubmit={handleSubmit}>
        <input defaultValue={userInput} onChange={handleChange} type="text" />
        <button type="submit">Submit</button>
      </form>
      {!!tempResponse.length &&
        tempResponse.data.map((value, i) => (
          <ChartRender key={`chart-${i}`} data={value} />
        ))}
    </>
  );
};

【讨论】:

  • 感谢您的回复。首先,我保留onKeyPress 的原因是因为除了按钮之外,我希望用户在按下enter 时发送查询,而onChange 无法做到这一点。所以我不认为onSubmit 也可以用于检测输入。其次你是对的handleTextBoxEnter 调用setTempResponse 来更新图表的内容。你建议我在这里做什么以避免重新渲染?
  • 您需要删除 onKeyPress。 onSubmit 将检测输入,因为您的输入在表单元素内。您还应该将 type=submit 添加到您的按钮。输入 onClick 事件有什么作用?它也可能不需要。我也更新了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-14
  • 1970-01-01
  • 1970-01-01
  • 2017-05-31
  • 2022-11-15
  • 2017-06-18
相关资源
最近更新 更多