避免多層渲染
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: '各位產品經理大家好,我是個人開發(fā)者張三队贱,我學習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: '各位產品經理大家好明郭,我是個人開發(fā)者張三,我學習RN兩年半了丰泊,我喜歡安卓薯定、RN、Flutter瞳购,Thank you!话侄。'
});
}}
/>
{/* <InfoView info={info}/> */}
<InfoView2 info={info}/>
</View>
);
}
避免重復計算、重復創(chuàng)建對象
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重復創(chuàng)建盏浇,我們可以使用useCallback來緩存回調函數变丧;但此時我們發(fā)現在onPress中需要的是一個無參數的返回函數,這里也需要做緩存绢掰,那該如何實現呢痒蓬?顯然我們可以使用高階函數來處理,先說一下高階函數如何處理滴劲,再來解說useCallback攻晒。
//高階函數寫法,函數返回一個無參數的函數
const itemPress = (item: any, index: number) => () => {
console.log('itemPress...')
}
onPress={itemPress(item, index)}
現在我們使用useCallback緩存回調函數:
//useCallback避免重復創(chuàng)建函數對象班挖,這里避免了兩層鲁捏,一層是itemPress這個函數,另一層是無參數的這個返回函數聪姿,即onPress需要的
const itemPress = useCallback((item: any, index: number) => () => {
console.log('itemPress...')
}, [])
這里useCallback緩存了兩層碴萧,一層是itemPress這個回調函數,另一層是onPress需要的無參回調函數末购。