You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复用React Native模态组件?适配不同数据、回调与按钮样式

复用模态框组件的解决方案

我有一个用于打开并展示模态框的组件,希望将其复用,因为大部分功能在多个场景都需要。差异点在于:

  1. 遍历的数据(属性名称不同);
  2. 触发模态框的按钮样式不同。

此外,父组件会传递回调函数,同时还需要将子组件的回调传递给遍历渲染数据的部分,因此无法仅通过children属性来渲染数据迭代内容(因为数据结构始终不同)。我尝试过实现render prop但未成功,请问该如何解决?

现有代码示例

父组件1

const Parent1 = () => {
    const [reportedLine, setReportedLine] = useState(null);
    const [availableLines, setAvailableLines] = useState([]);
    const [searchResultId, setSearchResultId] = useState('');

    return (
        <AvailableLinesSelector
            data={availableLines}
            disabled={searchResultId}
            onSelect={setReportedLine}
        />
    )
};

父组件2

const Parent2 = () => {
    const [line, setLine] = useState(null);
    return (
        <AvailableLinesSelector
            data={otherData}
            disabled={item}
            onSelect={setLine}
        />
    )
};

待复用的子组件

const AvailableLinesSelector = ({data, onSelect, disabled}) => {
    const [isVisible, setIsVisible] = useState(false);
    const [selectedLine, setSelectedLine] = useState('Pick the line');//placeholder should also be flexible

    const handleCancel = () => setIsVisible(false);
    const handleSelect = (input) => {
        onSelect(input)
        setSelectedLine(input)
        setIsVisible(false);
    };

    return (
        <View>
            <Button
                title={selectedLine}
                //a lot of styling that will be different depending on which parent renders
                disabled={disabled}
                onPress={() => setIsVisible(true)}
            />
            <BottomSheet isVisible={isVisible}>
                <View>
                    {data && data.map(line => (
                        <AvailableLine //here the properties as name, _id etc will be different depending on which parent renders this component
                            key={line._id} 
                            line={line.name}
                            onSelect={handleSelect}
                        />
                    ))}
                </View>
                <Button onPress={handleCancel}>Cancel</Button>
            </BottomSheet>
        </View>
    )
};

解决方法

方案1:使用Render Props传递自定义渲染逻辑

把需要自定义的部分通过render props传递,让父组件控制按钮和列表项的渲染,子组件专注于模态框的显示逻辑和回调处理,完全适配不同场景的差异。

修改后的子组件:

const AvailableLinesSelector = ({
    data, 
    onSelect, 
    disabled,
    renderTriggerButton, // 父组件传入的按钮渲染函数
    renderListItem, // 父组件传入的列表项渲染函数
    placeholder = 'Pick the line'
}) => {
    const [isVisible, setIsVisible] = useState(false);
    const [selectedLine, setSelectedLine] = useState(placeholder);

    const handleCancel = () => setIsVisible(false);
    const handleSelect = (input) => {
        onSelect(input);
        setSelectedLine(input);
        setIsVisible(false);
    };

    return (
        <View>
            {/* 父组件自定义触发按钮 */}
            {renderTriggerButton({
                selectedLine,
                disabled,
                onPress: () => setIsVisible(true)
            })}
            <BottomSheet isVisible={isVisible}>
                <View>
                    {data && data.map((item, index) => 
                        renderListItem({
                            item,
                            onSelect: handleSelect,
                            key: index // 也可让父组件指定唯一key
                        })
                    )}
                </View>
                <Button onPress={handleCancel}>Cancel</Button>
            </BottomSheet>
        </View>
    )
};

父组件1的使用方式:

const Parent1 = () => {
    const [reportedLine, setReportedLine] = useState(null);
    const [availableLines, setAvailableLines] = useState([]);
    const [searchResultId, setSearchResultId] = useState('');

    return (
        <AvailableLinesSelector
            data={availableLines}
            disabled={searchResultId}
            onSelect={setReportedLine}
            placeholder="选择线路"
            renderTriggerButton={({selectedLine, disabled, onPress}) => (
                <Button
                    title={selectedLine}
                    style={styles.parent1ButtonStyle} // 自定义按钮样式
                    disabled={disabled}
                    onPress={onPress}
                />
            )}
            renderListItem={({item, onSelect}) => (
                <AvailableLine
                    key={item._id}
                    line={item.name}
                    onSelect={() => onSelect(item.name)}
                />
            )}
        />
    )
};

父组件2的使用方式:

const Parent2 = () => {
    const [line, setLine] = useState(null);
    return (
        <AvailableLinesSelector
            data={otherData}
            disabled={item}
            onSelect={setLine}
            placeholder="选择其他线路"
            renderTriggerButton={({selectedLine, disabled, onPress}) => (
                <CustomButton
                    label={selectedLine}
                    variant="secondary" // 自定义按钮类型
                    disabled={disabled}
                    onClick={onPress}
                />
            )}
            renderListItem={({item, onSelect}) => (
                <OtherLineItem
                    key={item.id}
                    title={item.lineName}
                    onChoose={() => onSelect(item.lineName)}
                />
            )}
        />
    )
};

方案2:通过属性指定数据字段+样式覆盖

如果场景差异只是字段名和按钮样式的小改动,可通过传递字段名和样式属性来快速适配,代码更简洁。

修改后的子组件:

const AvailableLinesSelector = ({
    data, 
    onSelect, 
    disabled,
    triggerButtonStyle, // 父组件传递的按钮样式
    listItemKeyField = '_id', // 列表项key的字段名
    listItemLabelField = 'name', // 列表项显示文本的字段名
    placeholder = 'Pick the line'
}) => {
    const [isVisible, setIsVisible] = useState(false);
    const [selectedLine, setSelectedLine] = useState(placeholder);

    const handleCancel = () => setIsVisible(false);
    const handleSelect = (input) => {
        onSelect(input);
        setSelectedLine(input);
        setIsVisible(false);
    };

    return (
        <View>
            <Button
                title={selectedLine}
                style={triggerButtonStyle} // 应用父组件传递的样式
                disabled={disabled}
                onPress={() => setIsVisible(true)}
            />
            <BottomSheet isVisible={isVisible}>
                <View>
                    {data && data.map(line => (
                        <AvailableLine
                            key={line[listItemKeyField]}
                            line={line[listItemLabelField]}
                            onSelect={() => handleSelect(line[listItemLabelField])}
                        />
                    ))}
                </View>
                <Button onPress={handleCancel}>Cancel</Button>
            </BottomSheet>
        </View>
    )
};

父组件2的使用:

const Parent2 = () => {
    const [line, setLine] = useState(null);
    return (
        <AvailableLinesSelector
            data={otherData}
            disabled={item}
            onSelect={setLine}
            placeholder="选择线路"
            triggerButtonStyle={styles.parent2Button}
            listItemKeyField="id"
            listItemLabelField="lineName"
        />
    )
};

两种方案对比:

  • 方案1灵活性拉满,适合数据结构差异大、需要完全自定义UI的场景;
  • 方案2更轻量化,适合仅字段名不同、样式小修改的场景。

内容的提问来源于stack exchange,提问作者user6377312

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:30:47