避免多层渲染
1. 函数式组件:React.memo()
InfoView.tsx
import React, { useEffect } from 'react';
import {
StyleSheet,
View,
Image,
Text,
} from 'react-native';
type Props = {
info: UserInfo,
}
export default React.memo((props: Props) => {
const {info} = props;
const styles = darkStyles;
console.log('render...');
return (
<View style={styles.content}>
<Image style={styles.img} source={{ uri: info.avatar }} />
<Text style={styles.txt}>{info.name}</Text>
<View style={styles.infoLayout}>
<Text style={styles.infoTxt}>
{info.desc}
</Text>
</View>
</View>
);
}, (prevProps: Props, nextProps: Props) => {
return JSON.stringify(prevProps.info) === JSON.stringify(nextProps.info);
});
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
});
MemoPage.tsx
import { useState } from "react";
import InfoView from "./InfoView"
import { Button, View } from "react-native";
export default () => {
const [info, setInfo] = useState<UserInfo>(
{
name: '',
avatar: '',
desc: ''
}
);
const avatarUri = 'https://upload.jianshu.io/users/upload_avatars/19435884/5c30151f-7756-4071-843e-6ee1c755a031.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240';
return (
<View style={{ width: '100%'}}>
<Button
title="按钮"
onPress={() => {
setInfo({
name: '尼古拉斯',
avatar: avatarUri,
desc: '各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank you!。'
});
}}
/>
<InfoView info={info}/>
</View>
);
}
2. class组件:shouldComponentUpdate()
InfoView2.tsx
import React, { useEffect } from 'react';
import {
StyleSheet,
View,
Image,
Text,
} from 'react-native';
type Props = {
info: UserInfo,
}
export default class InfoView2 extends React.Component<Props, any> {
constructor(props: Props) {
super(props);
}
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
return JSON.stringify(nextProps.info) !== JSON.stringify(this.props.info);
}
render(): React.ReactNode {
const { info } = this.props;
const styles = darkStyles;
console.log('render 222...');
return (
<View style={styles.content}>
<Image style={styles.img} source={{ uri: info.avatar }} />
<Text style={styles.txt}>{info.name}</Text>
<View style={styles.infoLayout}>
<Text style={styles.infoTxt}>
{info.desc}
</Text>
</View>
</View>
);
}
}
const darkStyles = StyleSheet.create({
content: {
width: '100%',
height: '100%',
backgroundColor: '#353535',
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 64,
},
img: {
width: 96,
height: 96,
borderRadius: 48,
borderWidth: 4,
borderColor: '#ffffffE0',
},
txt: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
marginTop: 32,
},
infoLayout: {
width: '90%',
padding: 16,
backgroundColor: '#808080',
borderRadius: 12,
marginTop: 24,
},
infoTxt: {
fontSize: 16,
color: 'white',
},
});
MemoPage.tsx
import { useState } from "react";
import InfoView from "./InfoView"
import { Button, View } from "react-native";
import InfoView2 from "./InfoView2";
export default () => {
const [info, setInfo] = useState<UserInfo>(
{
name: '',
avatar: '',
desc: ''
}
);
const avatarUri = 'https://upload.jianshu.io/users/upload_avatars/19435884/5c30151f-7756-4071-843e-6ee1c755a031.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240';
return (
<View style={{ width: '100%'}}>
<Button
title="按钮"
onPress={() => {
setInfo({
name: '尼古拉斯',
avatar: avatarUri,
desc: '各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank you!。'
});
}}
/>
{/* <InfoView info={info}/> */}
<InfoView2 info={info}/>
</View>
);
}
避免重复计算、重复创建对象
1. useMemo缓存数据
ConsumeList.tsx:
import { StyleSheet, View, Text, FlatList, Switch } from "react-native"
import { ListData, TypeColors } from '../constants/Data'
import { useState, useMemo } from "react"
export default () => {
const [data, setData] = useState<any>(ListData);
const [typeSwitch, setTypeSwitch] = useState<boolean>(true);
const cacluateTotal = useMemo(() => {
// let total = 0;
// data.forEach((item: any) => {
// total += item.amount;
// });
// return total;
console.log("重新计算合计...")
return data.map((item:any) => item.amount)
.reduce((pre: number, cur: number) => pre + cur)
}, [data]);
const renderItem = ({item, index}:any) => {
const styles = StyleSheet.create({
itemLayout: {
width: '100%',
flexDirection: 'column',
borderBottomColor: '#e0e0e0',
borderBottomWidth: 0.5,
paddingVertical: 10,
paddingHorizontal: 10
},
titleLayout: {
width: '100%',
flexDirection: 'row'
},
first: {
flex: 0.4,
},
second: {
flex: 0.3
},
last: {
flex: 0.6
},
txt: {
flex: 1,
fontSize: 16,
color: "#666666",
},
valueRow: {
marginTop: 10
},
txtValue: {
color: 'black',
fontSize: 14,
flex: 1,
fontWeight: 'bold',
},
typeTxtValue: {
color: TypeColors[item.type],
fontSize: 14,
flex: 1,
fontWeight: 'bold',
}
})
return(
<View style={styles.itemLayout}>
<View style={styles.titleLayout}>
<Text style={[styles.first, styles.txt]}>序号</Text>
{typeSwitch && <Text style={[styles.second, styles.txt]}>类型</Text>}
<Text style={[styles.txt]}>消费名称</Text>
<Text style={[styles.last, styles.txt]}>消费金额</Text>
</View>
<View style={[styles.titleLayout, styles.valueRow]}>
<Text style={[styles.first, styles.txtValue]}>{item.index}</Text>
{typeSwitch && <Text style={[styles.second, styles.typeTxtValue]}>{item.type}</Text>}
<Text style={[styles.txtValue]}>{item.name}</Text>
<Text style={[styles.last, styles.txtValue]}>{item.amount}</Text>
</View>
</View>
)
}
return(
<View style={styles.root}>
<View style={styles.titleLayout}>
<Text style={styles.title}>消费记账单</Text>
<Switch
style={styles.typeSwitch}
value={typeSwitch}
onValueChange={(value) => {
setTypeSwitch(value);
}}
/>
</View>
<FlatList
data={data}
keyExtractor={(item, index) => `${item.index}-${item.name}`}
renderItem={renderItem}
>
</FlatList>
<View style={styles.totalLayout}>
<Text style={styles.totalTxt}>{cacluateTotal}</Text>
<Text style={styles.totalTxt}>合计:</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
root: {
width: '100%',
height: '100%',
},
titleLayout: {
width: '100%',
height: 50,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
title: {
color: 'black',
fontSize: 18,
fontWeight: 'bold'
},
typeSwitch: {
position: 'absolute',
right: 16,
},
totalLayout: {
width: '100%',
height: 50,
borderTopColor: '#c0c0c0',
borderTopWidth: 1,
flexDirection: 'row-reverse',
paddingHorizontal: 16,
paddingVertical: 10
},
totalTxt: {
color: 'black',
fontWeight: 'bold',
fontSize: 20
}
})
2. useMemo缓存ui渲染
//useMemo缓存ui
const totalAmountView = useMemo(() => {
console.log("重新渲染合计...")
const total = data.map((item:any) => item.amount)
.reduce((pre: number, cur: number) => pre + cur);
return(
<View style={styles.totalLayout}>
<Text style={styles.totalTxt}>{total}</Text>
<Text style={styles.totalTxt}>合计:</Text>
</View>
);
}, [data])
···
//使用的地方
{ totalAmountView }
3. useCallback缓存回调函数
我们给item添加点击事件,如下:
//原始写法
const itemPress = (item: any, index: number) => {
console.log('itemPress...')
}
<TouchableOpacity
onPress={() => {
itemPress(item, index)
}}
>
<View style={styles.itemLayout}>
<View style={styles.titleLayout}>
<Text style={[styles.first, styles.txt]}>序号</Text>
{typeSwitch && <Text style={[styles.second, styles.txt]}>类型</Text>}
<Text style={[styles.txt]}>消费名称</Text>
<Text style={[styles.last, styles.txt]}>消费金额</Text>
</View>
<View style={[styles.titleLayout, styles.valueRow]}>
<Text style={[styles.first, styles.txtValue]}>{item.index}</Text>
{typeSwitch && <Text style={[styles.second, styles.typeTxtValue]}>{item.type}</Text>}
<Text style={[styles.txtValue]}>{item.name}</Text>
<Text style={[styles.last, styles.txtValue]}>{item.amount}</Text>
</View>
</View>
</TouchableOpacity>
这种写法onPress需要一个无参数的返回,我们是写在View内部的,为了避免函数itemPress重复创建,我们可以使用useCallback来缓存回调函数;但此时我们发现在onPress中需要的是一个无参数的返回函数,这里也需要做缓存,那该如何实现呢?显然我们可以使用高阶函数来处理,先说一下高阶函数如何处理,再来解说useCallback。
//高阶函数写法,函数返回一个无参数的函数
const itemPress = (item: any, index: number) => () => {
console.log('itemPress...')
}
onPress={itemPress(item, index)}
现在我们使用useCallback缓存回调函数:
//useCallback避免重复创建函数对象,这里避免了两层,一层是itemPress这个函数,另一层是无参数的这个返回函数,即onPress需要的
const itemPress = useCallback((item: any, index: number) => () => {
console.log('itemPress...')
}, [])
这里useCallback缓存了两层,一层是itemPress这个回调函数,另一层是onPress需要的无参回调函数。