memo与性能优化

避免多层渲染

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需要的无参回调函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容