詳解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進行優(yōu)化,避免了很多不必要的渲染。官方文檔中,是這樣描述的。
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,但是優(yōu)化方式和extra props是沒什么關系的,以下的例子中,為了避免干擾,沒有新引入extra props,只是用stack navigator傳遞給組件的默認屬性來舉例子。
為了更好的監(jiān)控,HomeScreen是否被重復渲染,在代碼中打印了一個隨機數(shù),便于觀察日志輸出。
無因素引起組件更新時,使用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做了避免重復渲染的優(yōu)化,在代碼中加入了一個新的狀態(tài)age,當點擊Button時,這個age不斷的自增1,因為App里有state的更新,所以作為父組件的App會更新,而作為子組件的HomeScreen通常意義上(不通常的情況下,就是使用了React.memo等優(yōu)化手段)說,也會重新渲染。因為這就是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后,發(fā)現(xiàn)HomeScreen并沒有重繪,所以當使用component props傳遞組件時,Stack Navigator確實是做了防止不必要重繪的優(yōu)化。
具體效果可以參考下面的動畫:

有因素引起組件更新時,使用render callback的效果
那么在上面所說的場景下,用render callback會怎么樣呢?答案顯而易見,如果沒有做任何優(yōu)化處理,那么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的狀態(tài)時,本來沒有必要變化的HomeScreen,就瘋狂的重繪了起來,當然每次重繪的結果,都和之前一樣,這就是無效的重繪,我們應該避免。
有因素引起組件更新時,在render callback中使用React.memo
根據(jù)上面官網(wǎng)文檔給出的提示,如果想避免重繪,應該用React.memo (因為感覺FB已經(jīng)全面擁抱Hook了,所以這里也不考慮PureComponent了)來包裝你的組件。
const MemoHomeScreen = React.memo(HomeScreen)
說一百句,也頂不上一句代碼,具體代碼如下(都是可以copy到你的環(huán)境中直接運行的):
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的運行效果一樣。只不過前者是使用者自己優(yōu)化了重繪,后者是Stack Navigator替你優(yōu)化了。
有因素引起組件更新時,在render callback中使用useCallback
如果我們再稍微多想一下,hostInst本質(zhì)上是一個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的第二個參數(shù),控制起來更加有針對性。
以上就是詳解Stack Navigator中使用自定義的Render Callback的詳細內(nèi)容,更多關于Stack Navigator自定義Render Callback的資料請關注腳本之家其它相關文章!
相關文章
React前端開發(fā)createElement源碼解讀
這篇文章主要為大家介紹了React前端開發(fā)createElement源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
React Native 使用Fetch發(fā)送網(wǎng)絡請求的示例代碼
本篇文章主要介紹了React Native 使用Fetch發(fā)送網(wǎng)絡請求的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
React系列useSyncExternalStore學習詳解
這篇文章主要為大家介紹了React系列useSyncExternalStore的學習及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07
react如何同步獲取useState的最新狀態(tài)值
這篇文章主要介紹了react如何同步獲取useState的最新狀態(tài)值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

