当子级是功能组件且父级不返回JSX时,使用引用从父级访问子级函数

问题描述

标题有点罗word。我的意思是我有一个子组件(这是一个功能组件)和一个父类。但是,该父类不会返回JSX,因此,我不能使用hooks / useRef()。由于出现此错误,我也不能仅向子组件添加引用:

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

这是子类,其中有一些代码被剥离:

const BluetoothDeviceItem = (Props) => {
    const [connectingState,setConnectingState] = useState(Props.state());
    useEffect(() => {
        stateChangeCallback();
    },[connectingState]);

    const { onClick } = Props;

    const stateChangeCallback = () => {
        switch (connectingState) {
            case 'not_connected':
                //stuff
                break;
            case 'connecting':
                //stuff
                break;
            case 'connected':
                //stuff
                break;
        }
    }

    const rerender = () => {
        setConnectingState(Props.state());
    }

    const wait = async () => {
        onClick(Props.mac);
        rerender();

    }

    return (
        <IonItem lines="none" class="item-container" id={Props.mac} onClick={wait} ref={Props.elRef}>
            <IonAvatar slot="start" class="bluetooth-icon-container">
                <IonIcon icon={bluetoothOutline} class="bluetooth-icon"></IonIcon>
            </IonAvatar>
            <IonAvatar slot="end" class="connecting-icons-container">
                <IonIcon icon={cloSEOutline} class="connecting-icons"></IonIcon>
                <IonSpinner name="dots" class="connecting-spinner"></IonSpinner>
            </IonAvatar>
            <IonLabel>
                <div className="device-name">{Props.name}</div>
                <div className="device-mac">{Props.mac}</div>
            </IonLabel>
        </IonItem>
    );
}
export default BluetoothDeviceItem;

这是父母(再次打扫一点):

const _Bluetooth = () => {

    let states: {} = {}
    const deviceItems: any[] = [];
    const deviceRefs: any[] = [];

    const bluetoothInitialize = () => {
        for (let i = 0; i < 5; i++) {
            let a = {name: "test_"+i,mac: i.toString(),connected: false}
            createDeviceItem(a);
        }
    }

    const connect = (id) => {
        deviceItems.forEach(item => { //disconnecting any other currently connected devices and changing their respective states
            if ((item.props.state() === 'connecting' || item.props.state() === 'connected') && item.props.mac !== id) {
                updateDeviceItemState(item.props.mac,'not_connected');
            }

            if(id === item.props.mac) {
                updateDeviceItemState(item.props.mac,'connecting');
            }
        });
    }

    const createDeviceItem = (data) => {
        states = {...states,[data.mac]: data.connected ? 'connected' : 'not_connected'}
        let ref = createRef();
        deviceItems.push(<BluetoothDeviceItem elRef={ref} key={data.mac} mac={data.mac} name={data.name} onClick={(id) => connect(id)} state={() => {return states[data.mac]}} elRef={ref}></BluetoothDeviceItem>);
        deviceRefs.push(ref);
    }
    const updateDeviceItemState = (id,connectingState) => {
        states[id] = connectingState;
    }

    return (
        {deviceItems,bluetoothInitialize}
    );
}
export default _Bluetooth;

父类进行蓝牙扫描(目前仅被for循环替换)。每次找到设备时,它都会创建一个新的BluetoothDeviceItem。当您单击一个时,它的状态设置为not_connected。同时,它会检查当前是否还有其他设备connectingconnected
现在,将设备设置为connecting可以正常工作,因为在单击设备时,它将调用wait函数,该函数将更新BluetoothDeviceItem的状态。但是,在更新所有其他设备的状态时(检查它们是否为connectedconnecting之后),因为它们不是通过onClick调用的,所以它们不会意识到其状态已更新。这就是为什么我实现了rerender函数的原因,该函数将在每个BluetoothDeviceItem上调用以确保它们具有最新状态。
问题是我需要提升此功能,以便父类可以访问它。最明显的想法是使用引用。创建一个引用,将其附加到BluetoothDeviceItem组件并获取功能。当然这样做会产生我上面所说的错误
第二个最好的方法是将ref作为props传递并将其附加到DOM元素。不幸的是,这意味着我无法访问这些函数,因为它们在DOM元素上不存在。
我尝试的最后一件事是使用传播操作符(...)传播重新渲染功能,如下所示: <IonItem lines="none" class="item-container" id={Props.mac} onClick={wait} ref={Props.elRef}> {...rerender} 因此我可以使用ref访问它。这是行不通的,因为... rerender的求值为{}(在某个时间点我可以展开一个函数,但我不记得怎么做)。

当状态改变时,如何将此重新渲染功能提升到父级更新BluetoothDeviceItem?

这类似于我的previous post,但实际上我正在使用提供的答案并将所有状态存储在父类中。我认为,由于我需要这些类和组件的工作方式的性质,总会需要从父类的子组件中访问某些内容,同时也有严格的限制,例如子组件是函数组件还是父组件不是返回JSX

解决方法

我真的找不到简单的方法来做,所以我最终只是将BluetoothDeviceItem函数组件转换为React组件,就像这样:

class BluetoothDeviceItem extends React.Component<Props,{}> {
    constructor(props) {
        super(props);
    }

    private updateIcons = () => {
        switch (this.props.state()) {
            case 'not_connected':
                break;
            case 'connecting':
                break;
            case 'connected':
                break;
        }
    }

    public rerender = () => {
        this.updateIcons();
    }

    private wait = async () => {
        this.props.onClick(this.props.mac);
        this.rerender();
    }

    render() {
        return (
            <IonItem lines="none" class="item-container" id={this.props.mac} onClick={this.wait}>
                <IonAvatar slot="start" class="bluetooth-icon-container">
                    <IonIcon icon={bluetoothOutline} class="bluetooth-icon"></IonIcon>
                </IonAvatar>
                <IonAvatar slot="end" class="connecting-icons-container">
                    <IonIcon icon={closeOutline} class="connecting-icons"></IonIcon>
                    <IonSpinner name="dots" class="connecting-spinner"></IonSpinner>
                </IonAvatar>
                <IonLabel>
                    <div className="device-name">{this.props.name}</div>
                    <div className="device-mac">{this.props.mac}</div>
                </IonLabel>
            </IonItem>
        );
    }
}

export default BluetoothDeviceItem;

,然后在该组件上创建实例时仅使用引用。这使我可以访问所需的功能。