详解Stack Navigator中使用自定义的Render Callback

目录
  • Stack Navigator使用component props传递组件
  • 无因素引起组件更新时,使用render callback的效果
  • 有因素引起组件更新时,使用component props的效果
  • 有因素引起组件更新时,使用render callback的效果
  • 有因素引起组件更新时,在render callback中使用React.memo
  • 有因素引起组件更新时,在render callback中使用useCallback
  • 总结

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的资料请关注我们其它相关文章!

(0)

相关推荐

  • react render props模式实现组件复用示例

    目录 一 render props的使用步骤 二 组件的复用 三 使用children名代替属性 一 render props的使用步骤 1 创建要复用的组件,在组件中提供要复用的状态逻辑代码2 将要复用的state作为方法的参数,暴露到组件外部 import React from "react"; import ReactDOM from "react-dom"; class App extends React.Component { render() { ret

  • 解析React中useMemo与useCallback的区别

    useMemo 把“创建”函数和依赖项数组作为参数传⼊入useMemo,它仅会在某个依赖项改变时才重新计算memoized 值.这种优化有助于避免在每次渲染时都进⾏行行⾼高开销的计算. importReact, { useState, useMemo } from"react"; export default functionUseMemoPage(props) { const [count, setCount] =useState(0); constexpensive=useMemo

  • React useMemo和useCallback的使用场景

    useMemo 我们知道当父组件发生重新渲染时,其所有(状态.局部变量等)都是新的.一旦子组件依赖于父组件的某一个对象变量,那么无论对象是否发生变化,子组件拿到的都是新的对象,从而使子组件对应的 diff 失效,依旧会重新执行该部分逻辑.在下面的例子中,我们的副作用依赖项中包含了父组件传入的对象参数,每次父组件发生更新时,都会触发数据请求. function Info({ style, }) { console.log('Info 发生渲染'); useEffect(() => { consol

  • react组件memo useMemo useCallback使用区别示例

    目录 正文 memo使用 useMemo使用 useCallback使用 正文 memo用来优化函数组件的重复渲染行为,针对的是一个组件 useMemo返回一个memoized的值 本质都是用同样的算法来判定依赖是否发生改变,继而决定是否触发memo或者useMemo中的逻辑,利用memo就可以避免不必要的重复计算,减少资源浪费 useCallback返回一个memoized的函数 useMemo和useCallback都接收两个参数,第一个参数为fn,第二个参数为[],数组中是变化依赖的参数

  • 关于react中useCallback的用法

    目录 基础用法 父组件 子组件 useCallback是react中比较重要的一个hook useCallback 用来返回一个函数,在父子组件传参或者通用函数封装中,起到举足轻重的作用. 基础用法 useCallback的用法与useState的用法基本一致,但最后会返回一个函数,用一个变量保存起来. 返回的函数a会根据b的变化而变化,如果b始终未发生变化,a也不会重新生成,避免函数在不必要的情况下更新. 记得子组件导出时使用memo包裹一下,其作用是对组件前后两次进行浅对比,阻止不必要的更新

  • React的createElement和render手写实现示例

    目录 TL;DR 科普概念 准备工作 实现 createElement 实现 render 测试 TL;DR 本文的目标是,手写实现createElement和render React.createElement实现的本质就是整合参数变成对象,这个对象就是react元素 ReactDOM.render实现的本质就是根据react元素(对象)创建真实元素及其属性和子元素 科普概念 JSX 语法 - 就是类似 html 的写法<h1>颜酱<span>最酷</span><

  • 详解Stack Navigator中使用自定义的Render Callback

    目录 Stack Navigator使用component props传递组件 无因素引起组件更新时,使用render callback的效果 有因素引起组件更新时,使用component props的效果 有因素引起组件更新时,使用render callback的效果 有因素引起组件更新时,在render callback中使用React.memo 有因素引起组件更新时,在render callback中使用useCallback 总结 Stack Navigator使用component p

  • 详解Android XML中引用自定义内部类view的四个why

    今天碰到了在XML中应用以内部类形式定义的自定义view,结果遇到了一些坑.虽然通过看了一些前辈写的文章解决了这个问题,但是我看到的几篇都没有完整说清楚why,于是决定做这个总结. 使用自定义内部类view的规则 本文主要是总结why,所以先把XML布局文件中引用内部类的自定义view的做法摆出来,有四点: 1.自定义的类必须是静态类: 2.使用view作为XML文件中的tag,注意,v是小写字母,小写字母v,小写字母v: 3.添加class属性,注意,没有带android:命名空间的,表明该自

  • 详解Spring Boot中如何自定义SpringMVC配置

    目录 前言 一.SpringBoot 中 SpringMVC 配置概述 二.WebMvcConfigurerAdapter 抽象类 三.WebMvcConfigurer 接口 四.WebMvcConfigurationSupport 类-自定义配置 五.WebMvcAutoConfiguration 配置类 – 自动化配置 六.@EnableWebMvc 注解 七.总结 前言 在 Spring Boot 框架中只需要在项目中引入 spring-boot-starter-web 依赖,Spring

  • 详解在Vue中通过自定义指令获取dom元素

    vue.js 是数据绑定的框架,大部分情况下我们都不需要直接操作 DOM Element,但在某些时候,我们还是有获取DOM Element的需求的: 在 vue.js 中,获取某个DOM Element常用的方法是将这个元素改成一个组件 (component),然后通过 this.$el 去获取,但是在一些很小的项目里,在一些没有使用 webpack 等构建工具的项目中,创建一个组件并不是那么值得,所以 vue 提供了另一种操作DOM元素的方式,就是自定义指令 (directive) : 自定

  • 详解React Native中如何使用自定义的引用路径

    目录 RN的相对路径地狱 RN的自定义路径需要的依赖 解决自定义引用路径导致的eslint报错问题 RN的相对路径地狱 我刚接触RN时,就发现所有的demo中给出来的路径都是相对路径,我自己的练习项目中想改成自定义的绝对路径,但是发现并没有我做前端时熟悉的webpack.config.js,所以也就不知道该怎么改了.伴随着RN的学习和开发练习,我的代码也变得越来越多,越来越复杂,我逐渐发现RN的相对路径越来越麻烦,比如我把某个文件移动到另一个不同深度的文件夹中,那么就需要把所有引用这个文件的地方

  • 一文详解Java线程中的安全策略

    目录 一.不可变对象 二.线程封闭 三.线程不安全类与写法 四.线程安全-同步容器 1. ArrayList -> Vector, Stack 2. HashMap -> HashTable(Key, Value都不能为null) 3. Collections.synchronizedXXX(List.Set.Map) 五.线程安全-并发容器J.U.C 1. ArrayList -> CopyOnWriteArrayList 2.HashSet.TreeSet -> CopyOnW

  • 详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现

    详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现 snsapi_base与snsapi_userinfo属于微信网页授权获取用户信息的两种作用域 snsapi_base只能获取access_token和openID snsapi_userinfo可以获取更详细的用户资料,比如头像.昵称.性别等 首先,这里的access_token与基础access_token(比如自定义菜单用到的)是不一样的.两者区别如下: 网页授权的access_token在每次获取ope

  • 详解c# PLINQ中的分区

    最近因为比较忙,好久没有写博客了,这篇主要给大家分享一下PLINQ中的分区.上一篇介绍了并行编程,这边详细介绍一下并行编程中的分区和自定义分区. 先做个假设,假设我们有一个200Mb的文本文件需要读取,怎么样才能做到最优的速度呢?对,很显然就是拆分,把文本文件拆分成很多个小文件,充分利用我们计算机中的多核cpu的优势,让每个cpu都充分的利用,达到效率的最大化.然而在PLINQ中也是,我们有一个数据源,如果想进行最大的并行化操作,那么就需要把其拆分为可以多个线程同时访问的多个部分,这就是PLIN

  • 详解关于mybatis-plus中Service和Mapper的分析

    在后端开发过程中,如果有用到mybatis-plus,肯定会发现在其内部存在着两种数据库操作接口,Iservice和BaseMapper,如果只是用增删改查会发现两者的功能是一致的,除了方法名称有所不同,其他的基本相似.对此,我颇为好奇,便打开两个接口的源码进行对比. 先演示一下基本开发中的继承关系,手动创建的Service继承于ServiceImpl,并加载自己创建的Mapper @Service public class RestDeptService extends ServiceImpl

  • 详解Spring Security 中的四种权限控制方式

    Spring Security 中对于权限控制默认已经提供了很多了,但是,一个优秀的框架必须具备良好的扩展性,恰好,Spring Security 的扩展性就非常棒,我们既可以使用 Spring Security 提供的方式做授权,也可以自定义授权逻辑.一句话,你想怎么玩都可以! 今天松哥来和大家介绍一下 Spring Security 中四种常见的权限控制方式. 表达式控制 URL 路径权限 表达式控制方法权限 使用过滤注解 动态权限 四种方式,我们分别来看.  1.表达式控制 URL 路径权

随机推荐