渲染不同组件时无法更新组件(`App`)

Posted

技术标签:

【中文标题】渲染不同组件时无法更新组件(`App`)【英文标题】:Cannot update a component (`App`) while rendering a different component 【发布时间】:2020-09-25 21:06:48 【问题描述】:

关于 so 有很多类似的问题,但我找不到与我的难题相符的问题。

我有一个反应组件(一个径向旋钮控件 - 有点像滑块)。 我想实现两个结果:

    转动旋钮并将旋钮值传递给父级以进行进一步操作。 从父级接收目标旋钮值并相应地更新旋钮。 所有这些都不会陷入无限循环!

我已经拔掉了头发 - 但有一个可行的解决方案似乎违反了反应原则。

我有 knob.js 作为反应组件,它环绕第三方旋钮组件,我有 app.js 作为父级。

在knob.js中,我们有:

export default class MyKnob extends React.Component 
    constructor(props, context) 
        super(props, context)

        this.state = 
            size: props.size || 100,
            radius: (props.value/2).toString(),
            fontSize: (props.size * .2)
        
        if (props.value)
            console.log("setting value prop", props.value)
            this.state.value = props.value
         else 
            this.state.value = 25           // any old default value
        

      

要处理来自父级 (app.js) 的更新,我在 kub.js 中有这个:

      // this is required to allow changes at the parent to update the knob
      componentDidUpdate(prevProps) 
        if (prevProps.value !== this.props.value) 
           this.setState(value: this.props.value)
        
        console.log("updating knob from parent", value)
      

然后将旋钮值的更改传递回父级,我有:

    handleOnChange = (e)=>
        //this.setState(value: e)    <--used to be required until line below inserted. 
        this.props.handleChangePan(e)
      

这也有效,但会触发警告:

在渲染不同的组件 (Knob) 时无法更新组件 (App)

render()
        return (
            <Styles font-size=this.state.fontSize>
            <Knob size=this.state.size  
                angleOffset=220 
                angleRange=280
                steps=10
                min=0
                max=100
                value=this.state.value
                ref=this.ref
                onChange=value => this.handleOnChange(value)
            >
...

现在转到 app.js:

function App() 
  const [panLevel, setPanLevel] = useState(50);

// called by the child knob component. works -- but creates the warning
    function handleChangePan(e)
      setPanLevel(e)
    

    // helper function for testing
    function changePan(e)
      if (panLevel + 10>100)
        setPanLevel(0)
       else 
        setPanLevel(panLevel+10)
      
    

return (
    <div className="App">
        ....
        <div className='mixer'>
          <div key=1 className='vStrip'>
            <Knob size=150 value=panLevel handleChangePan = (e) => handleChangePan(e)/>
          </div>
        <button onClick=(e) => changePan(e)>CLICK ME TO INCREMENT BY 10</button>
      ...
    </div>

所以 - 它有效 - 但我违反了反应原则 - 我还没有找到另一种方法来保持外部“旋钮值”和内部“旋钮值”同步。

只是为了进一步弄乱我的脑袋,如果我在“handleOnChange”中删除对父级的冒泡——这可能会触发道具的变化——>状态级联回落——我不仅与父级缺乏同步- 但我还需要恢复下面的 setState,以便通过旋转(鼠标等.._)使旋钮工作!这会产生另一个警告:

在现有状态转换期间更新...

所以卡住了。请求和感激地收到建议。长篇文章的 Apol。

    handleOnChange = (e)=>
        //this.setState(value: e)
        **this.props.handleChangePan(e)**
      

在另一篇文章中建议,应该将 setState 包装到 useEffect 中 - 但我不知道该怎么做 - 更不用说这是否是正确的方法。

【问题讨论】:

【参考方案1】:

如果在渲染子(旋钮)时设置了父(应用)状态,则会显示错误消息。

在您的情况下,当 App 正在渲染时,Knob'sonChange() 在加载时被触发,然后调用 this.handleOnChange() 然后 this.props.handleChangePan() 具有 App'ssetPanLevel()

使用useEffect()修复:

    knob.js中,您可以像在App中一样先将panLevel存储为状态,而不是直接调用this.props.handleChangePan()调用App的setPanLevel()。 然后,使用useEffect(_=&gt;props.handleChangePan(panLevel),[panLevel])通过useEffect()调用App的setPanLevel()

您的knob.js 将如下所示:

function Knob(props)
  let [panLevel, setPanLevel] = useState(50);
  useEffect(_=>
    props.handleChangePan(panLevel);
  , [panLevel]);
  return *** Knob that does not call props.handleChangePan(), but call setPanLevel() instead ***;

useEffect()内部调用的setState()会在渲染完成后生效。

总之,第一次渲染时不能在useEffect()之外调用parent'ssetState(),否则会报错。

【讨论】:

以上是关于渲染不同组件时无法更新组件(`App`)的主要内容,如果未能解决你的问题,请参考以下文章

从 Redux Store 更新后 React 组件无法重新渲染

如果没有附加的渲染器,则无法更新组件。组件类:“类 org.primefaces.component.menuitem.UIMenuItem”

在 React 中更新后无法重新渲染

道具更新时重新渲染组件(使用 Redux)

SecurityError:replaceState 无法将历史记录更新为在路径、查询或片段以外的组件中不同的 URL

React setstate 只能更新挂载或挂载的组件——在重新渲染时