React Native 在模式 open/close 上重新渲染整个组件
React native rerenders whole component on modal open/close
我的问题
我使用 Array.map
方法呈现简单项目(数字或字符串)的列表。我使用 Modal
到 add/update 项。但是,每次打开或关闭模式都会使 react
重新渲染整个数组,即使数组保持不变。我觉得这是预期的行为。
问题
- 是否可以在打开或关闭模态时不重新渲染整个组件?
- 在不重新呈现整个列表的情况下 add/update 数组中的新项目的常用方法是什么?
谢谢大家
最小代码示例
/* Console output:
* ---------------
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* ROOT: open modal
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* MODAL: close Modal
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
*/
import * as React from 'react';
import {useState} from 'react';
import {View, Text, Modal, Pressable, StyleSheet} from 'react-native';
const items = [1, 2];
const Item = ({el}) => {
console.log(`-> ITEM: render ${el}`);
return <Text>Item: {el}</Text>;
};
const Testing = () => {
const [visible, setVisible] = useState(false);
const openModal = () => {
console.log('ROOT: open modal');
setVisible(true);
};
console.log("ROOT: render component");
return (
<View style={styles.wrapper}>
{/* Render simple list */}
<Text style={styles.header}>All items:</Text>
{items.map(el => (
<Item el={el} key={el} />
))}
<Item el={'3 (not in map)'} />
{/* Button to open modal */}
<Pressable style={styles.button} onPress={openModal}>
<Text style={styles.header}>Tap me to open modal</Text>
</Pressable>
{/*The Modal*/}
<Modal
animationType="slide"
transparent={false}
visible={visible}
onRequestClose={() => {
console.log('MODAL: close Modal');
setVisible(false);
}}>
<Text style={styles.header}>No content here...</Text>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
wrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
button: {
borderRadius: 5,
backgroundColor: '#0aa',
marginVertical: 10,
padding: 10,
},
header: {
fontSize: 18,
fontWeight: '700',
},
});
export default Testing;
它 re-renders 的原因是因为您正在更新状态 (setVisible)。
你可以使用Ref.
useState 与组件生命周期直接相关; useRef 不是,这意味着您可以更改它并且它不会触发 re-render.
示例:
const visible = useRef()
//your code
useEffect(() => {
visible.current = false //initiate to false or true
depending on what you need.
}, [])
<Modal
animationType="slide"
transparent={false}
visible={visible}
onRequestClose={() => {
console.log('MODAL: close Modal');
visible.current = !visible.current //will toggle between true and false
}}>
<Text style={styles.header}>No content here...</Text>
</Modal>
我今天学到了什么
一旦我们创建了一个组件,React
就会跟踪它的状态并在状态发生变化时执行重新渲染。重新渲染包括组件的所有子项。因此,我可以回答我的问题。
问题 1 的答案
是的。我们需要 visible
不在我们不想重新呈现的组件的 状态 中。为此,Modal 应该与我们的组件分开实现,该组件变成了 Modal 的父级。
同时,我们希望父级可以使设置的模态框可见。 new-for-me react hook 在这里发挥作用:useImperativeHandle
.
工作流程如下。父级使用包装 Modal 组件的 forwardRef
函数将引用 ref
转发给 Modal。然后,Modal 将 ref
声明为父级可以使用的处理程序。此声明和可用的属性是通过 useImperativeHandler
挂钩提供的。就是这样。
但是请注意,在大多数情况下应避免使用 refs 的命令式代码,如 official docs 状态。
下面是代码片段,作为我的纪念。目前没有在 Modal open/close 上重新渲染!
/* Console output:
* ---------------
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* ROOT: open modal
* MODAL: openMe called from parent component via ref
* MODAL: close Modal
*/
//
// Modal component
//
const _MyModal = (props, ref) => {
const [visible, setVisible] = useState(false);
const openMe = () => {
console.log('MODAL: openMe called from parent component via ref');
setVisible(true);
};
useImperativeHandle(ref, () => ({publicHandler: openMe}}), [openMe]));
return (<Modal>...</Modal>);
};
const MyModal = forwardRef(_MyModal);
//
// Testing component
//
const Testing = () => {
const modalRef = useRef(null);
const openModal = () => {
console.log('ROOT: open modal');
modalRef.current.publicHandler();
};
// Rest of code without <Modal> tag
}
使用 redux
回答问题 1
如果使用redux
,则不需要useImperativeHandle
hook。只需将 Modal 组件连接到 store
以共享 visible
和 action creator actSetVisible
,而将父组件连接到仅共享此 action creator actSetVisible
。一切都与上图类似。
但是,如果您确实想使用 useImperativeHandle
,您应该在将 Modal 连接到商店时指出 redux
您正在转发引用的事实:
const MyModal = connect(mapS2P, mapD2P, null, {forwardRef: true}))(forwardRef(_MyModal));
问题 2 的答案
上面的建议展示了如何在模式打开或关闭时摆脱过多的重新渲染。我的第二个问题暗示我在反应中做了一些新的错误,因为我真的是这里的新手。因此,以我的愚见,使用模态是向列表添加元素的好方法。在我的例子中,它是一个组合组件数组,而不是一个简单字符串数组,重新渲染是一个关键问题。
后记
活一个世纪,学一个世纪,你就会傻到死。希望这对任何人都有帮助。
我的问题
我使用 Array.map
方法呈现简单项目(数字或字符串)的列表。我使用 Modal
到 add/update 项。但是,每次打开或关闭模式都会使 react
重新渲染整个数组,即使数组保持不变。我觉得这是预期的行为。
问题
- 是否可以在打开或关闭模态时不重新渲染整个组件?
- 在不重新呈现整个列表的情况下 add/update 数组中的新项目的常用方法是什么?
谢谢大家
最小代码示例
/* Console output:
* ---------------
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* ROOT: open modal
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* MODAL: close Modal
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
*/
import * as React from 'react';
import {useState} from 'react';
import {View, Text, Modal, Pressable, StyleSheet} from 'react-native';
const items = [1, 2];
const Item = ({el}) => {
console.log(`-> ITEM: render ${el}`);
return <Text>Item: {el}</Text>;
};
const Testing = () => {
const [visible, setVisible] = useState(false);
const openModal = () => {
console.log('ROOT: open modal');
setVisible(true);
};
console.log("ROOT: render component");
return (
<View style={styles.wrapper}>
{/* Render simple list */}
<Text style={styles.header}>All items:</Text>
{items.map(el => (
<Item el={el} key={el} />
))}
<Item el={'3 (not in map)'} />
{/* Button to open modal */}
<Pressable style={styles.button} onPress={openModal}>
<Text style={styles.header}>Tap me to open modal</Text>
</Pressable>
{/*The Modal*/}
<Modal
animationType="slide"
transparent={false}
visible={visible}
onRequestClose={() => {
console.log('MODAL: close Modal');
setVisible(false);
}}>
<Text style={styles.header}>No content here...</Text>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
wrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
button: {
borderRadius: 5,
backgroundColor: '#0aa',
marginVertical: 10,
padding: 10,
},
header: {
fontSize: 18,
fontWeight: '700',
},
});
export default Testing;
它 re-renders 的原因是因为您正在更新状态 (setVisible)。
你可以使用Ref.
useState 与组件生命周期直接相关; useRef 不是,这意味着您可以更改它并且它不会触发 re-render.
示例:
const visible = useRef()
//your code
useEffect(() => {
visible.current = false //initiate to false or true
depending on what you need.
}, [])
<Modal
animationType="slide"
transparent={false}
visible={visible}
onRequestClose={() => {
console.log('MODAL: close Modal');
visible.current = !visible.current //will toggle between true and false
}}>
<Text style={styles.header}>No content here...</Text>
</Modal>
我今天学到了什么
一旦我们创建了一个组件,React
就会跟踪它的状态并在状态发生变化时执行重新渲染。重新渲染包括组件的所有子项。因此,我可以回答我的问题。
问题 1 的答案
是的。我们需要 visible
不在我们不想重新呈现的组件的 状态 中。为此,Modal 应该与我们的组件分开实现,该组件变成了 Modal 的父级。
同时,我们希望父级可以使设置的模态框可见。 new-for-me react hook 在这里发挥作用:useImperativeHandle
.
工作流程如下。父级使用包装 Modal 组件的 forwardRef
函数将引用 ref
转发给 Modal。然后,Modal 将 ref
声明为父级可以使用的处理程序。此声明和可用的属性是通过 useImperativeHandler
挂钩提供的。就是这样。
但是请注意,在大多数情况下应避免使用 refs 的命令式代码,如 official docs 状态。
下面是代码片段,作为我的纪念。目前没有在 Modal open/close 上重新渲染!
/* Console output:
* ---------------
* ROOT: render component
* -> ITEM: render 1
* -> ITEM: render 2
* -> ITEM: render 3 (not in map)
* ROOT: open modal
* MODAL: openMe called from parent component via ref
* MODAL: close Modal
*/
//
// Modal component
//
const _MyModal = (props, ref) => {
const [visible, setVisible] = useState(false);
const openMe = () => {
console.log('MODAL: openMe called from parent component via ref');
setVisible(true);
};
useImperativeHandle(ref, () => ({publicHandler: openMe}}), [openMe]));
return (<Modal>...</Modal>);
};
const MyModal = forwardRef(_MyModal);
//
// Testing component
//
const Testing = () => {
const modalRef = useRef(null);
const openModal = () => {
console.log('ROOT: open modal');
modalRef.current.publicHandler();
};
// Rest of code without <Modal> tag
}
使用 redux
回答问题 1
如果使用redux
,则不需要useImperativeHandle
hook。只需将 Modal 组件连接到 store
以共享 visible
和 action creator actSetVisible
,而将父组件连接到仅共享此 action creator actSetVisible
。一切都与上图类似。
但是,如果您确实想使用 useImperativeHandle
,您应该在将 Modal 连接到商店时指出 redux
您正在转发引用的事实:
const MyModal = connect(mapS2P, mapD2P, null, {forwardRef: true}))(forwardRef(_MyModal));
问题 2 的答案
上面的建议展示了如何在模式打开或关闭时摆脱过多的重新渲染。我的第二个问题暗示我在反应中做了一些新的错误,因为我真的是这里的新手。因此,以我的愚见,使用模态是向列表添加元素的好方法。在我的例子中,它是一个组合组件数组,而不是一个简单字符串数组,重新渲染是一个关键问题。
后记
活一个世纪,学一个世纪,你就会傻到死。希望这对任何人都有帮助。