使用反应钩子时如何通知父组件 属性 变化?

How to notify parent component of property change when using react hooks?

假设我有一个父组件和一个子组件。父组件由多个子组件组成。父组件持有和管理一个非常复杂和深的数据对象。每个子组件提供 UI 来管理主数据对象的各种子对象和属性。每当子组件更改数据对象层次结构中的 属性 值时,该更改需要冒泡到主数据对象。

这是我在子组件 class 中传递回调对象的方法...

<div>
  <button onClick={e => this.setState({propA: e.target.value}, () => props.onChangePropA(this.state.propA)}>Prop A</button>
  <button onClick={e => this.setState({propB: e.target.value}, () => props.onChangePropB(this.state.propB)}>Prop B</button>
</div>

与我认为我需要使用钩子来完成它的方式相比。我看到的主要问题是状态更改完成后没有回调选项。所以我必须在 useEffect 中检测它并弄清楚哪个 属性 刚刚改变了...

let prevPropA = props.propA;
let prevPropB = props.propB;

const [propA, setPropA] = useState(props.propA);
const [propB, setPropB] = useState(props.propB);

useEffect(() => { 
  if (prevPropA != propA) props.onChangePropA(propA); 
  if (prevPropB != propB) props.onChangePropB(propB); 
});

<div>
  <button onClick={e => {prevPropA = propA; setPropA(e.target.value)}}>Prop A</button>
  <button onClick={e => {prevPropB = propB; setPropB(e.target.value)}}>Prop B</button>
</div>

我发现这种方法变得极其繁琐和混乱。是否有更多 robust/proper 方法来完成此操作?

谢谢

============================================= ================

下面是根据 Shubham 的回答更新的示例代码, 赖安的反馈。 Shubham 按要求回答了问题,但是 Ryan 建议我举一个更详尽的例子来确保 我正在为正确的答案提供正确的信息。 这是更接近我的真实世界的示例代码 情况......虽然仍然是一个简化的例子。 父组件管理来自用户的评论。想象 他们可以创建新评论和 select 日期或日期范围。 他们还可以更新现有评论。我已经把日期 和日期范围 select 或在它自己的组件中。 因此,父评论管理器组件需要具备以下能力 create/load 评论并将相关日期传递给 日期-select或组件。然后用户可以更改日期 这些值需要传播回父评论 管理器稍后被发送到服务器并保存。 所以你看,有 属性 值(日期等)的双向流 可以随时从任一端更改。 注意:这个新示例是使用类似于什么的方法更新的 Shubham 根据我原来的问题提出建议。

============================================= ================

const DateTimeRangeSelector = (props) =>
{
    const [contextDateStart, setContextDateStart] = useState(props.contextDateStart);
    const [contextDateEnd, setContextDateEnd] = useState(props.contextDateEnd);
    const [contextDateOnly, setContextDateOnly] = useState(props.contextDateOnly);
    const [contextDateHasRange, setContextDateHasRange] = useState(props.contextDateHasRange);

    useEffect(() => { setContextDateStart(props.contextDateStart);  }, [ props.contextDateStart  ]);
    useEffect(() => { if (contextDateStart !== undefined) props.onChangeContextDateStart(contextDateStart);  }, [ contextDateStart  ]);

    useEffect(() => { setContextDateEnd(props.contextDateEnd);  }, [ props.contextDateEnd  ]);
    useEffect(() => { if (contextDateEnd !== undefined) props.onChangeContextDateEnd(contextDateEnd); }, [ contextDateEnd  ]);

    useEffect(() => { setContextDateOnly(props.contextDateOnly);  }, [ props.contextDateOnly  ]);
    useEffect(() => { if (contextDateOnly !== undefined) props.onChangeContextDateOnly(contextDateOnly); }, [ contextDateOnly  ]);

    useEffect(() => { setContextDateHasRange(props.contextDateHasRange); }, [ props.contextDateHasRange  ]);
    useEffect(() => { if (contextDateHasRange !== undefined) props.onChangeContextDateHasRange(contextDateHasRange);  }, [ contextDateHasRange  ]);


    return <div>
    <ToggleButtonGroup 
        exclusive={false}
        value={(contextDateHasRange === true) ? ['range'] : []}
        selected={true}
        onChange={(event, value) => setContextDateHasRange(value.some(item => item === 'range'))}
        >
        <ToggleButton value='range' title='Specify a date range'  > 
            <FontAwesomeIcon icon='arrows-alt-h' size='lg' />
        </ToggleButton>
    </ToggleButtonGroup>

    {
        (contextDateHasRange === true)
        ?
        <DateTimeRangePicker 
            range={[contextDateStart, contextDateEnd]} 
            onChangeRange={val => { setContextDateStart(val[0]); setContextDateEnd(val[1]);  }}
            onChangeShowTime={ val => setContextDateOnly(! val) }
            />
        :
        <DateTimePicker
            selectedDate={contextDateStart} 
            onChange={val => setContextDateStart(val)}
            showTime={! contextDateOnly}
        />

    }
</div>
}


const CommentEntry = (props) =>
{
    const [activeComment, setActiveComment] = useState(null);

    const createComment = () =>
    {
        return {uid: uuidv4(), content: '', contextDateHasRange: false,  contextDateOnly: false, contextDateStart: null, contextDateEnd: null};
    }

    const editComment = () =>
    {
        return loadCommentFromSomewhere();
    }

    const newComment = () =>
    {
        setActiveComment(createComment());
    }

    const clearComment = () =>
    {
        setActiveComment(null);
    }

    return (
    <div>

        <Button onClick={() => newComment()} variant="contained">
            New Comment
        </Button>
        <Button onClick={() => editComment()} variant="contained">
            Edit Comment
        </Button>

        {
            activeComment !== null &&
            <div>
                <TextField
                    value={(activeComment) ? activeComment.content: ''}
                    label="Enter comment..."
                    onChange={(event) => { setActiveComment({...activeComment, content: event.currentTarget.value, }) }}
                />
                <DateTimeRangeSelector

                    onChange={(val) => setActiveComment(val)}

                    contextDateStart={activeComment.contextDateStart}
                    onChangeContextDateStart={val => activeComment.contextDateStart = val}

                    contextDateEnd={activeComment.contextDateEnd}
                    onChangeContextDateEnd={val => activeComment.contextDateEnd = val}

                    contextDateOnly={activeComment.contextDateOnly}
                    onChangeContextDateOnly={val => activeComment.contextDateOnly = val}

                    contextDateHasRange={activeComment.contextDateHasRange}
                    onChangeContextDateHasRange={val => activeComment.contextDateHasRange = val}

                    />
                <Button onClick={() => clearComment()} variant="contained">
                    Cancel
                </Button>
                <Button color='primary' onClick={() => httpPostJson('my-url', activeComment, () => console.log('saved'))} variant="contained" >
                    <SaveIcon/> Save
                </Button>
            </div>
        }    
    </div>
    );
}

useEffect 采用第二个参数,表示何时执行效果。您可以将状态值传递给它,以便它在状态更新时执行。你也可以在你的代码中有多个 useEffect 钩子

const [propA, setPropA] = useState(props.propA);
const [propB, setPropB] = useState(props.propB);

useEffect(() => { 
  props.onChangePropA(propA); 
}, [propA]);

useEffect(() => { 
  props.onChangePropB(propB); 
}, [propB]);
<div>
  <button onClick={e => {setPropA(e.target.value)}}>Prop A</button>
  <button onClick={e => {setPropB(e.target.value)}}>Prop B</button>
</div>