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

IT技术 javascript node.js reactjs components
2021-05-26 05:36:25

有一堆类似的问题,但我看不到一个与我的难题相匹配的问题。

我有一个react组件(径向旋钮控件 - 有点像滑块)。我想达到两个结果:

  1. 旋转旋钮并将旋钮值向上传递给父级以进行进一步操作。
  2. 从父级接收目标旋钮值并相应地更新旋钮。
  3. 一切都没有进入无限循环!

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

我有Knob.js作为环绕第三方旋钮组件的react组件,我有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) 的更新,我在 Knob.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)
      }

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

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

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>

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

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

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

所以卡住了。征求意见并感激地收到。长篇文章的 Apols。

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

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

1个回答

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

在您的情况下,当 App 正在呈现时,KnobonChange()会在加载时被触发,然后调用this.handleOnChange()然后this.props.handleChangePan()让 App 的setPanLevel().

修复使用useEffect()

  1. 在 中knob.js,您可以panLevel像在 App 中一样先存储为状态,而不是直接调用this.props.handleChangePan()调用 App 的setPanLevel().
  2. 然后,使用useEffect(_=>props.handleChangePan(panLevel),[panLevel])调用 App 的setPanLevel()via useEffect()

您的旋钮.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 ***;
}

setState()useEffect()在渲染完成后调用 inside将生效。

简而言之,在第一次渲染时不能调用 parent 的setState()外部useEffect(),否则会出现错误消息。