如何在 React Native 中从祖父母 child 向上传递到其祖父母,然后返回到祖父母的 child
How to communicate from a grandchild up to its grandparent then back down to the grandparent's child in React Native
我是 React 的新手,所以我希望我能正确地解决这个问题。首先,我有一个名为 SearchLocationsScreen 的屏幕。在该屏幕内,我有一个名为 Map 的组件,在 Map 内,我有一个名为 LocationMarker 的自定义标记组件.在与 Map 组件相同的层级,我有一个名为 CheckinModal 的自定义 ModalBox。这是一个帮助的粗略图表:
在 SearchLocationsScreen 中,我从 API 电话中获取位置信息。然后我将这些位置向下传递到我的 Map 组件。在我的 Map 组件中,我将标记的信息传递给自定义 LocationMarker class 并填充地图。
目标是按下一个标记,让 CheckinModal 从底部弹出,并用来自被按下的特定标记的信息填充它。为此,我使用 useRef 挂钩和 forwardRef 挂钩将对模态的引用向下传递到 LocationMarker class。在这里我调用 ref.current.open()
并且模式按预期打开。
问题是我想不出一种方法来从标记传递位置信息,将层次结构备份到屏幕,然后向下备份到模态以用相关信息填充模态。有谁知道如何实现这一目标?我将下面的代码发布到我的屏幕、地图组件和标记组件(不包括样式)。预先感谢您的帮助。
SearchLocationsScreen.js
const SearchLocationsScreen = ({isFocused, navigation}) => {
const {updateLocation} = useContext(CurrentLocationContext);
// hooks
const callback = useCallback((location) => {
updateLocation(location)
}, []);
const [err] = useCurrentLocation(isFocused, callback);
const [businessLocations] = useGetLocations();
const modalRef = useRef(null);
let locations = [];
if (businessLocations) {
for (let x = 0; x < businessLocations.length; x++) {
locations.push({
...businessLocations[x],
latitude: businessLocations[x].lat,
longitude: businessLocations[x].lng,
key: x,
})
}
}
return (
<View style={{flex: 1}}>
<Map markers={locations} ref={modalRef}/>
<SearchBar style={styles.searchBarStyle}/>
{err ? <View style={styles.errorContainer}><Text
style={styles.errorMessage}>{err.message}</Text></View> : null}
<CheckinModal
ref={modalRef}
/>
</View>
);
};
Map.js
const Map = ({markers}, ref) => {
const {state: {currentLocation}} = useContext(Context);
// todo figure out these error situations
if (!currentLocation) {
return (
<View style={{flex: 1}}>
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 27.848680,
longitude: -82.646560,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
/>
<ActivityIndicator size='large' style={styles.indicator} />
</View>
)
}
return (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
...currentLocation.coords,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
showsUserLocation
>
{ markers ? markers.map((marker, index) => {
return <LocationMarker
ref={ref} // passing the ref down to the markers
key={index}
coordinate={marker}
title={marker.company}
waitTime={ marker.wait ? `${marker.wait} minutes` : 'Open'}
/>;
}) : null}
</MapView>
)
};
const forwardMap = React.forwardRef(Map);
export default forwardMap;
LocationMarker.js
const LocationMarker = ({company, coordinate, title, waitTime, onShowModal}, ref) => {
return (
<View>
<Marker
coordinate={coordinate}
title={title}
onPress={() => {
console.log(ref);
ref.current.open();
}}
>
<Image
source={require('../../assets/marker2.png')}
style={styles.locationMarker}/>
<View style={styles.waitContainer}><Text style={styles.waitText}>{waitTime}</Text></View>
</Marker>
</View>
)
};
const forwardMarker = React.forwardRef(LocationMarker);
export default forwardMarker;
如果我理解正确,而不是使用 forwardRef
使用 ref
道具从 parent 传递 ref,我建议将其作为简单道具传递。当它到达嵌套组件(LocationMarker 在你的情况下)你可以分配它。这是一个简化版本:
const SearchLocationsScreen = props => {
const marker_ref = useRef(null);
const modal_ref = useRef(null);
return (
<View>
<Map marker_ref={marker_ref} modal_ref={modal_ref} />
<CheckinModal marker_ref={marker_ref} modal_ref={modal_ref} />
</View>
);
};
const Map = props => {
const { marker_ref, modal_ref } = props;
return <LocationMarker marker_ref={marker_ref} modal_ref={modal_ref} />;
};
const LocationMarker = props => {
const { marker_ref, modal_ref } = props;
return <div ref={marker_ref} />;
};
const CheckinModal = props => {
const { marker_ref, modal_ref } = props;
return <div ref={modal_ref} />;
};
当 ref 到达最后一个元素时,我们使用 ref=
分配它。请记住,最后一个元素必须是 JSX 元素,例如 div
,而不是组件。
为了避免通过中间的每个组件将这些道具从 grandparent 传递到 children,您可以在 SearchLocationsScreen
.[=17 中使用 Context =]
你想过用钩子吗? Hooks 允许你将功能组合到你的组件中,而不必通过 props 传递所有东西。如果您需要在多个地方使用此值,它可能会让您的生活更轻松?在你的情况下,你将钩子设置在一个地方,然后在它发生变化时更新你的另一个组件,而不用担心传递任何东西
我在 Alvaro 对我的主要 post 的评论的帮助下弄明白了。
这是我所做的。首先,我将生成 LocationMarker 的代码移到了 SearchLocationsScreen。无论如何,我已经在该屏幕上访问了这些标记所需的位置(最初我将这些位置传递给 Map 组件并在那里创建它们)。在 SearchLocationsScreen 中,我遍历所有位置以生成 LocationMarkers,添加一个使用 useReducer[=45 的回调=] 钩子来存储模态的状态。因为它们都在同一级别,所以我可以使用来自 reducer 状态的正确数据填充模态的字段。该回调被传递到 LocationMarkers。然后在 LocationMarker onPress 中调用此方法。一切都按预期工作。这是更新后的代码:
SearchLocationsScreen
const SearchLocationsScreen = ({isFocused, navigation}) => {
const {updateLocation} = useContext(CurrentLocationContext);
// hooks
const callback = useCallback((location) => {
updateLocation(location)
}, []);
const [err] = useCurrentLocation(isFocused, callback);
const [businessLocations] = useGetLocations();
const modalRef = useRef(null);
let locations = [];
if (businessLocations) {
for (let x = 0; x < businessLocations.length; x++) {
locations.push({
...businessLocations[x],
latitude: businessLocations[x].lat,
longitude: businessLocations[x].lng,
key: x,
})
}
}
const modalReducer = (state, action) => {
console.log("payload: ", action.payload);
switch (action.type) {
case 'show_modal':
return {...state,
companyName: action.payload.companyName,
companyAddress: action.payload.companyAddress,
waitTime: action.payload.waitTime
};
default:
return state;
}
};
const [modalState, dispatch] = useReducer(modalReducer, {
companyName: "Company Name",
companyAddress: "123 Elm St",
waitTime: 0
});
const createMarkers = () => {
let result = [];
if (locations) {
for (let i = 0; i < locations.length; i++) {
result.push(
<LocationMarker
key={i}
id={i}
coordinate={locations[i]}
title={locations[i].company}
waitTime={locations[i].wait ? `${locations[i].wait} minutes` : 'Closed'}
onShowModal={() => {
dispatch({
type: 'show_modal', payload: {
companyName: locations[i].company,
companyAddress: locations[i].address,
waitTime: locations[i].wait,
}
});
modalRef.current.open();
}}
/>
)
}
}
return result;
};
return (
<View style={{flex: 1}}>
<Map markers={createMarkers()}/>
{/*<Map ref={modalRef} markers={...createMarkers()} />*/}
<SearchBar style={styles.searchBarStyle}/>
{err ? <View style={styles.errorContainer}><Text
style={styles.errorMessage}>{err.message}</Text></View> : null}
<CheckinModal
ref={modalRef}
businessName={modalState.companyName}
address={modalState.companyAddress}
waitTime={modalState.waitTime}
/>
</View>
);
};
地图
const Map = ({markers}, ref) => {
const {state: {currentLocation}} = useContext(Context);
return (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
...currentLocation.coords,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
showsUserLocation
>
{markers ? markers.map((marker, index) => {
return marker;
}): null}
</MapView>
)
};
export default Map;
CheckinModal
const CheckinModal = ({businessName, address, waitTime}, ref) => {
return (
<ModalBox
style={styles.modal}
position={'bottom'}
backdrop={true}
ref={ref}
>
<Text>Store Name: {businessName}</Text>
<Text>Store Address: {address}</Text>
<Text>Wait time: {waitTime} minutes</Text>
</ModalBox>
)
};
const forwardModal = React.forwardRef(CheckinModal);
export default forwardModal;
位置标记
const LocationMarker = (props) => {
return (
<View>
<Marker
coordinate={props.coordinate}
title={props.title}
id={props.id}
onPress={() => {
props.onShowModal();
}}
>
<Image
source={require('../../assets/marker2.png')}
style={styles.locationMarker}/>
<View style={styles.waitContainer}>
<Text style={styles.waitText}>{props.waitTime}</Text>
</View>
</Marker>
</View>
)
};
export default LocationMarker;
使用这个新代码,我不必转发那么多引用,只需转发到 CheckinModal。
如果有人有任何问题 post 他们在这个答案下面,我会尽快回答他们。谢谢大家的帮助。
我是 React 的新手,所以我希望我能正确地解决这个问题。首先,我有一个名为 SearchLocationsScreen 的屏幕。在该屏幕内,我有一个名为 Map 的组件,在 Map 内,我有一个名为 LocationMarker 的自定义标记组件.在与 Map 组件相同的层级,我有一个名为 CheckinModal 的自定义 ModalBox。这是一个帮助的粗略图表:
在 SearchLocationsScreen 中,我从 API 电话中获取位置信息。然后我将这些位置向下传递到我的 Map 组件。在我的 Map 组件中,我将标记的信息传递给自定义 LocationMarker class 并填充地图。
目标是按下一个标记,让 CheckinModal 从底部弹出,并用来自被按下的特定标记的信息填充它。为此,我使用 useRef 挂钩和 forwardRef 挂钩将对模态的引用向下传递到 LocationMarker class。在这里我调用 ref.current.open()
并且模式按预期打开。
问题是我想不出一种方法来从标记传递位置信息,将层次结构备份到屏幕,然后向下备份到模态以用相关信息填充模态。有谁知道如何实现这一目标?我将下面的代码发布到我的屏幕、地图组件和标记组件(不包括样式)。预先感谢您的帮助。
SearchLocationsScreen.js
const SearchLocationsScreen = ({isFocused, navigation}) => {
const {updateLocation} = useContext(CurrentLocationContext);
// hooks
const callback = useCallback((location) => {
updateLocation(location)
}, []);
const [err] = useCurrentLocation(isFocused, callback);
const [businessLocations] = useGetLocations();
const modalRef = useRef(null);
let locations = [];
if (businessLocations) {
for (let x = 0; x < businessLocations.length; x++) {
locations.push({
...businessLocations[x],
latitude: businessLocations[x].lat,
longitude: businessLocations[x].lng,
key: x,
})
}
}
return (
<View style={{flex: 1}}>
<Map markers={locations} ref={modalRef}/>
<SearchBar style={styles.searchBarStyle}/>
{err ? <View style={styles.errorContainer}><Text
style={styles.errorMessage}>{err.message}</Text></View> : null}
<CheckinModal
ref={modalRef}
/>
</View>
);
};
Map.js
const Map = ({markers}, ref) => {
const {state: {currentLocation}} = useContext(Context);
// todo figure out these error situations
if (!currentLocation) {
return (
<View style={{flex: 1}}>
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 27.848680,
longitude: -82.646560,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
/>
<ActivityIndicator size='large' style={styles.indicator} />
</View>
)
}
return (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
...currentLocation.coords,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
showsUserLocation
>
{ markers ? markers.map((marker, index) => {
return <LocationMarker
ref={ref} // passing the ref down to the markers
key={index}
coordinate={marker}
title={marker.company}
waitTime={ marker.wait ? `${marker.wait} minutes` : 'Open'}
/>;
}) : null}
</MapView>
)
};
const forwardMap = React.forwardRef(Map);
export default forwardMap;
LocationMarker.js
const LocationMarker = ({company, coordinate, title, waitTime, onShowModal}, ref) => {
return (
<View>
<Marker
coordinate={coordinate}
title={title}
onPress={() => {
console.log(ref);
ref.current.open();
}}
>
<Image
source={require('../../assets/marker2.png')}
style={styles.locationMarker}/>
<View style={styles.waitContainer}><Text style={styles.waitText}>{waitTime}</Text></View>
</Marker>
</View>
)
};
const forwardMarker = React.forwardRef(LocationMarker);
export default forwardMarker;
如果我理解正确,而不是使用 forwardRef
使用 ref
道具从 parent 传递 ref,我建议将其作为简单道具传递。当它到达嵌套组件(LocationMarker 在你的情况下)你可以分配它。这是一个简化版本:
const SearchLocationsScreen = props => {
const marker_ref = useRef(null);
const modal_ref = useRef(null);
return (
<View>
<Map marker_ref={marker_ref} modal_ref={modal_ref} />
<CheckinModal marker_ref={marker_ref} modal_ref={modal_ref} />
</View>
);
};
const Map = props => {
const { marker_ref, modal_ref } = props;
return <LocationMarker marker_ref={marker_ref} modal_ref={modal_ref} />;
};
const LocationMarker = props => {
const { marker_ref, modal_ref } = props;
return <div ref={marker_ref} />;
};
const CheckinModal = props => {
const { marker_ref, modal_ref } = props;
return <div ref={modal_ref} />;
};
当 ref 到达最后一个元素时,我们使用 ref=
分配它。请记住,最后一个元素必须是 JSX 元素,例如 div
,而不是组件。
为了避免通过中间的每个组件将这些道具从 grandparent 传递到 children,您可以在 SearchLocationsScreen
.[=17 中使用 Context =]
你想过用钩子吗? Hooks 允许你将功能组合到你的组件中,而不必通过 props 传递所有东西。如果您需要在多个地方使用此值,它可能会让您的生活更轻松?在你的情况下,你将钩子设置在一个地方,然后在它发生变化时更新你的另一个组件,而不用担心传递任何东西
我在 Alvaro 对我的主要 post 的评论的帮助下弄明白了。
这是我所做的。首先,我将生成 LocationMarker 的代码移到了 SearchLocationsScreen。无论如何,我已经在该屏幕上访问了这些标记所需的位置(最初我将这些位置传递给 Map 组件并在那里创建它们)。在 SearchLocationsScreen 中,我遍历所有位置以生成 LocationMarkers,添加一个使用 useReducer[=45 的回调=] 钩子来存储模态的状态。因为它们都在同一级别,所以我可以使用来自 reducer 状态的正确数据填充模态的字段。该回调被传递到 LocationMarkers。然后在 LocationMarker onPress 中调用此方法。一切都按预期工作。这是更新后的代码:
SearchLocationsScreen
const SearchLocationsScreen = ({isFocused, navigation}) => {
const {updateLocation} = useContext(CurrentLocationContext);
// hooks
const callback = useCallback((location) => {
updateLocation(location)
}, []);
const [err] = useCurrentLocation(isFocused, callback);
const [businessLocations] = useGetLocations();
const modalRef = useRef(null);
let locations = [];
if (businessLocations) {
for (let x = 0; x < businessLocations.length; x++) {
locations.push({
...businessLocations[x],
latitude: businessLocations[x].lat,
longitude: businessLocations[x].lng,
key: x,
})
}
}
const modalReducer = (state, action) => {
console.log("payload: ", action.payload);
switch (action.type) {
case 'show_modal':
return {...state,
companyName: action.payload.companyName,
companyAddress: action.payload.companyAddress,
waitTime: action.payload.waitTime
};
default:
return state;
}
};
const [modalState, dispatch] = useReducer(modalReducer, {
companyName: "Company Name",
companyAddress: "123 Elm St",
waitTime: 0
});
const createMarkers = () => {
let result = [];
if (locations) {
for (let i = 0; i < locations.length; i++) {
result.push(
<LocationMarker
key={i}
id={i}
coordinate={locations[i]}
title={locations[i].company}
waitTime={locations[i].wait ? `${locations[i].wait} minutes` : 'Closed'}
onShowModal={() => {
dispatch({
type: 'show_modal', payload: {
companyName: locations[i].company,
companyAddress: locations[i].address,
waitTime: locations[i].wait,
}
});
modalRef.current.open();
}}
/>
)
}
}
return result;
};
return (
<View style={{flex: 1}}>
<Map markers={createMarkers()}/>
{/*<Map ref={modalRef} markers={...createMarkers()} />*/}
<SearchBar style={styles.searchBarStyle}/>
{err ? <View style={styles.errorContainer}><Text
style={styles.errorMessage}>{err.message}</Text></View> : null}
<CheckinModal
ref={modalRef}
businessName={modalState.companyName}
address={modalState.companyAddress}
waitTime={modalState.waitTime}
/>
</View>
);
};
地图
const Map = ({markers}, ref) => {
const {state: {currentLocation}} = useContext(Context);
return (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
...currentLocation.coords,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
showsUserLocation
>
{markers ? markers.map((marker, index) => {
return marker;
}): null}
</MapView>
)
};
export default Map;
CheckinModal
const CheckinModal = ({businessName, address, waitTime}, ref) => {
return (
<ModalBox
style={styles.modal}
position={'bottom'}
backdrop={true}
ref={ref}
>
<Text>Store Name: {businessName}</Text>
<Text>Store Address: {address}</Text>
<Text>Wait time: {waitTime} minutes</Text>
</ModalBox>
)
};
const forwardModal = React.forwardRef(CheckinModal);
export default forwardModal;
位置标记
const LocationMarker = (props) => {
return (
<View>
<Marker
coordinate={props.coordinate}
title={props.title}
id={props.id}
onPress={() => {
props.onShowModal();
}}
>
<Image
source={require('../../assets/marker2.png')}
style={styles.locationMarker}/>
<View style={styles.waitContainer}>
<Text style={styles.waitText}>{props.waitTime}</Text>
</View>
</Marker>
</View>
)
};
export default LocationMarker;
使用这个新代码,我不必转发那么多引用,只需转发到 CheckinModal。
如果有人有任何问题 post 他们在这个答案下面,我会尽快回答他们。谢谢大家的帮助。