如何复用React Native模态组件?适配不同数据、回调与按钮样式
复用模态框组件的解决方案
我有一个用于打开并展示模态框的组件,希望将其复用,因为大部分功能在多个场景都需要。差异点在于:
- 遍历的数据(属性名称不同);
- 触发模态框的按钮样式不同。
此外,父组件会传递回调函数,同时还需要将子组件的回调传递给遍历渲染数据的部分,因此无法仅通过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
相关产品推荐
相关产品推荐

