详解Stack Navigator中使用自定义的Render Callback
作者:前百花真君
Stack Navigator使用component props传递组件
通常来说,Stack Navigator的默认用法,是这样的
<NavigationContainer>\ <Stack.Navigator>\ <Stack.Screen name="Home" component={HomeScreen} />\ </Stack.Navigator>\ </NavigationContainer>
自定义的组件HomeScreen
是作为component
属性,传递给Stack.Screen
的。这种默认的做法,会让Stack.Screen
对Screen Component进行优化,避免了很多不必要的渲染。官方文档中,是这样描述的。
Note: By default, React Navigation applies optimizations to screen components to prevent unnecessary renders. Using a render callback removes those optimizations. So if you use a render callback, you'll need to ensure that you use React.memo or React.PureComponent for your screen components to avoid performance issues.
从这段话中,我们可以看出,当使用自定义的render callback
时,避免组件重复渲染的工作,就移交给了使用者。render callback
通常是为了传递extra props
,但是优化方式和extra props
是没什么关系的,以下的例子中,为了避免干扰,没有新引入extra props
,只是用stack navigator
传递给组件的默认属性来举例子。
为了更好的监控,HomeScreen
是否被重复渲染,在代码中打印了一个随机数,便于观察日志输出。
无因素引起组件更新时,使用render callback的效果
下面这段代码,使用了render callback来渲染HomeScreen。
const homeInst = (props) => (<HomeScreen {...props} />)
运行起来的效果和不使用render callback的效果是一样的。在频繁的HomeScreen和DetailsScreen切换过程中,因为没有引起HomeScreen重绘的因素存在,所以HomeScreen并没有被重复渲染。
import React from 'react' import { View, Text, Button } from 'react-native' import { NavigationContainer } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' function HomeScreen({ navigation }) { console.log(`home: ${Math.random(new Date().getTime())}`) const goToDetail = () => { navigation.navigate('Details') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title='Go To Detail' onPress={goToDetail}></Button> </View> ) } function DetailsScreen({ navigation }) { const goHome = () => { navigation.navigate('Home') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title='Go Home' onPress={goHome}></Button> </View> ) } const Stack = createNativeStackNavigator() function App() { const homeInst = (props) => (<HomeScreen {...props} />) return ( <NavigationContainer> <Stack.Navigator initialRouteName='Home'> <Stack.Screen name='Home'> {homeInst} </Stack.Screen> <Stack.Screen name='Details' component={DetailsScreen}/> </Stack.Navigator> </NavigationContainer> ) } export default App
有因素引起组件更新时,使用component props的效果
为了引起HomeScreen
组件的更新,以便验证Screen Navigator
是否对HomeScreen做了避免重复渲染的优化,在代码中加入了一个新的状态age
,当点击Button
时,这个age
不断的自增1,因为App
里有state
的更新,所以作为父组件的App
会更新,而作为子组件的HomeScreen
通常意义上(不通常的情况下,就是使用了React.memo
等优化手段)说,也会重新渲染。因为这就是React
的重绘机制:从父组件开始,一层一层向下重绘。
import React, {useState} from 'react' import { View, Text, Button } from 'react-native' import { NavigationContainer } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' function HomeScreen({ navigation }) { console.log(`home: ${Math.random(new Date().getTime())}`) const goToDetail = () => { navigation.navigate('Details') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title='Go To detail' onPress={goToDetail}></Button> </View> ) } function DetailsScreen({ navigation }) { const goHome = () => { navigation.navigate('Home') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title='Go Home' onPress={goHome}></Button> </View> ) } const Stack = createNativeStackNavigator() function App() { const [age, setAge] = useState(20) return ( <NavigationContainer> <Stack.Navigator initialRouteName='Home'> <Stack.Screen name='Home' component={HomeScreen} /> <Stack.Screen name='Details' component={DetailsScreen} /> </Stack.Navigator> <View> <Text>{age}</Text> <Button title='Increase Age' onPress={() => (setAge(age + 1))}></Button> </View> </NavigationContainer> ) } export default App
当我点击Button
后,发现HomeScreen
并没有重绘,所以当使用component props
传递组件时,Stack Navigator
确实是做了防止不必要重绘的优化。
具体效果可以参考下面的动画:
有因素引起组件更新时,使用render callback的效果
那么在上面所说的场景下,用render callback
会怎么样呢?答案显而易见,如果没有做任何优化处理,那么HomeScreen的不必要的重复渲染,是无法避免的了。
代码如下:
import React, { useState } from 'react' import { View, Text, Button } from 'react-native' import { NavigationContainer } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' function HomeScreen({ navigation }) { console.log(`home: ${Math.random(new Date().getTime())}`) const goToDetail = () => { navigation.navigate('Details') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title='Go To detail' onPress={goToDetail}></Button> </View> ) } function DetailsScreen({ navigation }) { const goHome = () => { navigation.navigate('Home') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title='Go Home' onPress={goHome}></Button> </View> ) } const Stack = createNativeStackNavigator() function App() { const [age, setAge] = useState(20) const homeInst = (props) => (<HomeScreen {...props} />) return ( <NavigationContainer> <Stack.Navigator initialRouteName='Home'> <Stack.Screen name='Home'> {homeInst} </Stack.Screen> <Stack.Screen name='Details' component={DetailsScreen} /> </Stack.Navigator> <View> <Text>{age}</Text> <Button title='Increase Age' onPress={() => (setAge(age + 1))}></Button> </View> </NavigationContainer> ) } export default App
动画效果如下:
可以看到,当我点击Button
改变App
的状态时,本来没有必要变化的HomeScreen
,就疯狂的重绘了起来,当然每次重绘的结果,都和之前一样,这就是无效的重绘,我们应该避免。
有因素引起组件更新时,在render callback中使用React.memo
根据上面官网文档给出的提示,如果想避免重绘,应该用React.memo
(因为感觉FB已经全面拥抱Hook了,所以这里也不考虑PureComponent了)来包装你的组件。
const MemoHomeScreen = React.memo(HomeScreen)
说一百句,也顶不上一句代码,具体代码如下(都是可以copy到你的环境中直接运行的):
import React, {useState} from 'react'; import { View, Text, Button } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; function HomeScreen({ navigation }) { console.log(`home: ${Math.random(new Date().getTime())}`) const goToDetail = () => { navigation.navigate('Details') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title='Go To detail' onPress={goToDetail}></Button> </View> ) } function DetailsScreen({ navigation }) { const goHome = () => { navigation.navigate('Home') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title='Go Home' onPress={goHome}></Button> </View> ) } const Stack = createNativeStackNavigator() const MemoHomeScreen = React.memo(HomeScreen) function App() { const [age, setAge] = useState(20) const homeInst = (props) => (<MemoHomeScreen {...props} />) return ( <NavigationContainer> <Stack.Navigator initialRouteName='Home'> <Stack.Screen name='Home'> {homeInst} </Stack.Screen> <Stack.Screen name='Details' component={DetailsScreen} /> </Stack.Navigator> <View> <Text>{age}</Text> <Button title='Increase Age' onPress={() => (setAge(age + 1))}></Button> </View> </NavigationContainer> ) } export default App;
上面这段代码的运行效果,和使用component props
传递HomeScreen
的运行效果一样。只不过前者是使用者自己优化了重绘,后者是Stack Navigator
替你优化了。
有因素引起组件更新时,在render callback中使用useCallback
如果我们再稍微多想一下,hostInst
本质上是一个function
,而说道function
的避免重复计算的手段,自然想到了useCallback
。我用useCallback
来包装一下,看看是否能达到一样的效果:
const homeInst = useCallback((props) => (<HomeScreen {...props} />), [])
完整代码如下:
// In App.js in a new project import React, {useState, useCallback} from 'react' import { View, Text, Button } from 'react-native' import { NavigationContainer } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' function HomeScreen({ navigation }) { console.log(`home: ${Math.random(new Date().getTime())}`) const goToDetail = () => { navigation.navigate('Details') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Home Screen</Text> <Button title='Go To detail' onPress={goToDetail}></Button> </View> ) } function DetailsScreen({ navigation }) { const goHome = () => { navigation.navigate('Home') } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text>Details Screen</Text> <Button title='Go Home' onPress={goHome}></Button> </View> ) } const Stack = createNativeStackNavigator() function App() { const [age, setAge] = useState(20) const homeInst = useCallback((props) => (<HomeScreen {...props} />), []) return ( <NavigationContainer> <Stack.Navigator initialRouteName='Home'> <Stack.Screen name='Home'> {homeInst} </Stack.Screen> <Stack.Screen name='Details' component={DetailsScreen} /> </Stack.Navigator> <View> <Text>{age}</Text> <Button title='Increase Age' onPress={() => (setAge(age + 1))}></Button> </View> </NavigationContainer> ) } export default App
我试了一下,效果和使用React.memo
是一样的,都可以达到避免无效重复绘制HomeScreen
的目的。
总结
Stack Navigator
的使用,除非特殊情况,非得加extraData
,否则强烈推荐用props
的方式传递组件,减少思维负担。如果要使用render callback
,那么我是推荐使用useCallback
代替React.memo
的,因为配合useCallback
的第二个参数,控制起来更加有针对性。
以上就是详解Stack Navigator中使用自定义的Render Callback的详细内容,更多关于Stack Navigator自定义Render Callback的资料请关注脚本之家其它相关文章!