React Native - 在使用 React 钩子时减少渲染时间以优化性能
Posted
技术标签:
【中文标题】React Native - 在使用 React 钩子时减少渲染时间以优化性能【英文标题】:React Native - reduce render times to optimize performance while using React hooks 【发布时间】:2020-09-12 18:30:15 【问题描述】:背景
在释放React v16.8
之后,现在我们可以在 React Native 中使用钩子了。
我正在做一些简单的测试来查看渲染时间和性能
挂钩的功能组件和类组件。这是我的示例:
@Components/Button.js
import React, memo from 'react';
import TouchableOpacity, Text from 'react-native';
const Button = memo(( title, onPress ) =>
console.log("Button render"); // check render times
return (
<TouchableOpacity onPress=onPress disabled=disabled>
<Text>title</Text>
</TouchableOpacity>
);
);
export default Button;
@Contexts/User.js
import React, createContext, useState from 'react';
import User from '@Models/User';
export const UserContext = createContext();
export const UserContextProvider = ( children ) =>
let [ user, setUser ] = useState(null);
const login = (loginUser) =>
if (loginUser instanceof User) setUser(loginUser);
;
const logout = () =>
setUser(null);
;
return (
<UserContext.Provider value=value: user, login: login, logout: logout>
children
</UserContext.Provider>
);
;
export function withUserContext(Component)
return function UserContextComponent(props)
return (
<UserContext.Consumer>
(contexts) => <Component ...props ...contexts />
</UserContext.Consumer>
);
案例
我们有以下两种情况来构建屏幕组件:
@Screens/Login.js
案例 1:带有 Hooks 的功能组件
import React, memo, useContext, useState from 'react';
import View, Text from 'react-native';
import Button from '@Components/Button';
import UserContext from '@Contexts/User';
const LoginScreen = memo(( navigation ) =>
const appUser = useContext(UserContext);
const [foo, setFoo] = useState(false);
const userLogin = async () =>
let response = await fetch('blahblahblah');
if (response.is_success)
appUser.login(user);
else
// fail on login, error handling
;
const toggleFoo = () =>
setFoo(!foo);
console.log("current foo", foo);
;
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress=userLogin title="Login" />
<Button onPress=toggleFoo title="Toggle Foo" />
</View>
);
);
export default LoginScreen;
案例 2:使用 HOC 封装的组件
import React, Component from 'react';
import View, Text from 'react-native';
import Button from '@Components/Button';
import withUserContext from '@Contexts/User';
import UserService from '@Services/User';
class LoginScreen extends Component
state = foo: false ;
userLogin = async () =>
let response = await UserService.login();
if (response.is_success)
login(user); // function from UserContext
else
// fail on login, error handling
;
toggleFoo = () =>
const foo = this.state;
this.setState( foo: !foo );
console.log("current foo", foo);
;
render()
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress=userLogin title="Login" />
<Button onPress=toggleDisable title="Toggle" />
</View>
);
结果
两种情况开始时的渲染时间相同:
render Login Screen
Button render
Button render
但是当我按下“切换”按钮时,状态发生了变化,结果如下:
案例 1:带有 Hooks 的功能组件
render Login Screen
Button render
Button render
案例 2:使用 HOC 封装的组件
render Login Screen
问题
虽然按钮组件不是一大堆代码,但考虑到两种情况之间的重新渲染时间,Case 2
的性能应该比Case 1
更好。
但是,考虑到代码的可读性,我绝对喜欢使用钩子而不是使用 HOC。 (特别是函数:appUser.login()
和login()
)
所以问题来了。有没有什么解决方案可以保持两种尺寸的好处,减少使用钩子时的重新渲染时间?谢谢。
【问题讨论】:
【参考方案1】:原因是在功能组件中,每当组件重新渲染时,新的userLogin
created => Button
组件被重新渲染。
const userLogin = async () =>
const response = await fetch("blahblahblah")
if (response.is_success)
appUser.login(user)
else
// fail on login, error handling
您可以使用 useCallback
来记忆 userLogin
函数 + 用 React.memo
包装 Button
组件(就像您所做的那样)防止不必要的重新渲染:
const userLogin = useCallback(async () =>
const response = await fetch("blahblahblah")
if (response.is_success)
appUser.login(user)
else
// fail on login, error handling
, [])
在类组件中没有发生这种情况的原因是当类组件重新渲染时,只有render
函数被触发(当然还有一些其他生命周期函数,例如shoudlComponentUpdate,componentDidUpdate触发)。 ==> userLogin
不改变 ==> Button
组件不重新渲染。
这里是great article 看看useCallback
+ memo
注意:当你使用Context
时,memo
不能阻止组件,也就是Consumer
,如果Context Provider的值改变了,则重新渲染。
例如:
如果你在UserContext
中调用setUser
=> UserContext
重新渲染 => value=value: user, login: login, logout: logout
更改 => LoginScreen
重新渲染。您不能使用shouldComponentUpdate
(类组件)或memo
(功能组件)来防止重新渲染,因为它不是通过props
更新的,而是通过Context Provide 的值更新的
【讨论】:
非常感谢您回答这个问题。 ;)【参考方案2】:即使您在功能组件的情况下使用memo
,两个按钮都会重新呈现的原因是因为函数引用在每次重新呈现时都会更改,因为它们是在功能组件中定义的。
如果在类组件的渲染中使用arrow functions
也会发生类似情况
在类的情况下,函数引用不会随着您定义它们的方式而改变,因为函数是在您的渲染方法之外定义的
要优化重新渲染,您应该使用useCallback
挂钩来记忆您的函数引用
const LoginScreen = memo(( navigation ) =>
const appUser = useContext(UserContext);
const [foo, setFoo] = useState(false);
const userLogin = useCallback(async () =>
let response = await fetch('blahblahblah');
if (response.is_success)
appUser.login(user);
else
// fail on login, error handling
, []); // Add dependency if need i.e when using value from closure
const toggleFoo = useCallback(() =>
setFoo(prevFoo => !prevFoo); // use functional state here
, []);
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress=userLogin title="Login" />
<Button onPress=toggleFoo title="Toggle Foo" />
</View>
);
);
export default LoginScreen;
另请注意,React.memo
无法防止由于上下文值更改而重新渲染。另请注意,在将值传递给上下文提供者时,您也应该使用useMemo
export const UserContextProvider = ( children ) =>
let [ user, setUser ] = useState(null);
const login = useCallback((loginUser) =>
if (loginUser instanceof User) setUser(loginUser);
, []);
const logout = useCallback(() =>
setUser(null);
, []);
const value = useMemo(() => (
value: user,
login: login,
logout: logout,
), [user, login, logout]);
/*
Note that login and logout functions are implemented using `useCallback` and
are created on initial render only and hence adding them as dependency here
doesn't make a difference and will definitely not lead to new referecne for
value. Only `user` value change will create a new object reference
*/
return (
<UserContext.Provider value=value>
children
</UserContext.Provider>
);
;
【讨论】:
非常感谢您回答这个问题,也感谢您指出我可以做的其他一些可能的优化。欣赏!以上是关于React Native - 在使用 React 钩子时减少渲染时间以优化性能的主要内容,如果未能解决你的问题,请参考以下文章
使用 react-native-firebase 在 React Native 上自定义通知
Flipper 不会在任何断点处暂停 - react-native - react-native-reanimated
在带有 wix/react-native-navigation 的模态中使用 react-native-gesture-handler (RNGH)
使用 React-Native-Router-Flux 在 React Native 中嵌套场景
使用 react-native-pdf 在 react-native 上获取准确的 x 和 y 坐标
React-native:如何在 React-native 中使用(和翻译)带有 jsx 的 typescript .tsx 文件?