React, React Native Uygulamada Yaşanan Performans Sorunları

javascriptreact

1 yıl önce 1 yorum

Merhabalar, React ve React Native üzerinde birçok performans sorunu ile karşılaştım ve çoğuyla bazı şeylerden vazgeçerek daha hızlı bir hale getirebildim, bazılarında getiremedim.

Bu makalede uygulamada yaşanan state güncellemelerinin açtığı sorunlara birkaç örnek verip çözüm yollarından bahsedeceğim.

React'in asıl mantığını bilmek gerekiyor. En kaba şekilde mantığı şunun üzerine kurulu. Bir React class'ının render methodu var ve bu state değişimlerinde çalışıyor. Eğer içinde de başka React class'ları varsa aynı şekilde onların da render methodu parent state değiştiğinde çalışıyor.

Örnek vererek başlayalım

Bir listeniz var ve bu listedeki sadece 1 nesneyi değiştirmek istiyorsunuz. Ama listeyi şöyle yaptınız diyelim:

return(
    <View>
        {
            list.map(x => (
                <Text>
                    {x.text}
                </Text>
            ))
        }
    </View>
)

Böyle bir liste için hız çok da bir sorun oluşturmayacaktır.
Ama list.map üzerinde dönerken Text component'i başka bir component olsaydı ve o component içinde bir çok komponent içerseydi işler o zaman değişirdi. Çünkü map döngü başına en uçtaki component'e kadar tüm render methodlarını çalıştıracaktı.

Bu durumda şunları sormalıyız:

State gerçekten güncellenmeli mi?

Eğer bazı durumlarda güncellemeye gerek yoksa kontrollü setState çalıştırılmalıdır.

Bir event oluştu veya sunucudan bir değer geldi bunu direkt yansıtmalı mıyız? Şöyle olduğunu varsayalım. Bir ürün listemiz var bu listedeki çorabın fiyatı 5 lira. Bir soket'imiz var ve bu tüm ürünlerin fiyatını değişikliğini dinliyor. Soket'e bir değişiklik olduğu bilgisi geldi ve çorap 5 lira olmuş. Yani değişiklik yok. Bu durumda state'i güncellemeye gerek yok çünkü hiçbir değişiklik yok. Bu durum aslında sunucu ile alakalı bir durum. Ya da sunucu çorap fiyatı değişikliği yerine ürün değişikliğini takip ediyor olsun. Bu durumda eğer sadece isim ve fiyat gösteriliyorsa (view'a yansıtılıyorsa) kullanıcıya gelen başka bir alanın (örn: güncellenme tarihi) değişikliğinin state'i güncellememesi gerekir. Yani sadece isim ve fiyat gösteriliyorsa güncellenme tarihi için state'i güncellemeye gerek yok.

Eğer listedeki bir nesnede değişiklik yapıldıysa ve alt nesnelerin render methodunun çalıştırılması gerekmiyorsa alt nesnelerin shouldComponentUpdate kullanması gerekir.

Bence en önemli nokta tam da burası. Örneğin bir listemiz var ve içinde render edecek çok component var. shouldComponentUpdate kullanarak, içeriğin sadece gerektiğinde güncellenmesini sağlayabiliriz.

// herhangi bir component
class Card extends Component {

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.name === nextProps.name &&
            this.props.price === nextProps.price) {
            // zaten şu an hepsi aynı render'ın çalışmasına gerek yok
            return false;
        }
        // en az biri farklı ise render çalışsın
        return true;
    }

    render() {
        console.log('güncellendi'); // console üzerinden sürekli güncellenmediğini görebilirsiniz.
        return(
            <View>
                <Text>İsim: {this.props.name}</Text>
                <Text>Fiyat: {this.props.price}</Text>
            </View>
        )
    }
}

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            list: [/* burada veriler var */],
        };
    }

    componentWillMount() {
        var self = this;
        // eğer belirli aralıklarda değişiklik olsa
        setInterval(() => {
            self.setState({
                list: [{
                    name: '1 USD',
                    price: '4,02 TRY (bu günleri de mi... :)'
                }]
            })
        }, 200);
    }

    render() {
        return(
            <View>
                {
                    this.state.list.map((x, i) => (
                        <Card
                            key={i}
                            name={x.name}
                            price={x.price}/>
                    ))
                }
            </View>
        )
    }
}

Eğer gerçekten güncellenmesi gerekiyorsa belirli aralıklarda veya kullanıcıya bağlı olarak setState çalıştırılmalıdır.

Örneğin sürekli değişen bir liste var ve çok hızlı bir şekilde değişiyor 200ms gibi bir süre. Bunu 1 saniyeye veya kullanıcının çok da hissetemeyeceği bir aralığa düşürerek güncellemek gerekir. Şöyle bir kod buna örnek olabilir:

constructor(props) {
    super(props);

    this.state = {
        list: [],
    };

    // state'in güncelleme yapması gerektiği yerleri anlamak için yazdık
    this.lockState = false;
}

componentWillMount() {
    var self = this;
    // state'i güncellemesi gereken timeout'un bir defa oluşması gerektiği için yazdık
    var timeoutStart = false;
    // bu 100ms süreyle sürekli çalışacak. burası bir başka state değiştiren yer olabilir
    setInterval(() => {
        // eğer o an kilitli değilse
        if (self.lockState === false) {
            self.setState({
                list: [/* burada veriler var */]
            });
            // burada kilitledik çünkü biz 200ms yerine istediğimiz aralığı vereceğiz
            self.lockState = true;
        }
        // kilitlenmişse bizim belirlediğimiz süre sonrasında yenilemeli
        else {
            // state'i her şekilde güncelliyoruz.
            self.state.list = [/* burada veriler var */];
            if (timeoutStart === false) {
                timeoutStart = true;
                setTimeout(() => {
                    // en son state'imizi güncelliyoruz
                    self.setState(self.state.list);
                    // timeout bittiğinde burasını çalıştırmalıyız çünkü bir sonraki güncelleme için tekrar timeout oluşturmalı
                    timeoutStart = false;
                }, 1000); // biz burayı 1sn yaptık bu sayı kullanıcının view güncellemesini anlayacağı bir aralık.
            }
        }
    }, 200);
}

render() {
    return(
        <View>
            {
                list.map(x => (
                    <Text>
                        {x.text}
                    </Text>
                ))
            }
        </View>
    )
}

Eğer kullanıcı herşeyi hemen istemiyorsa uygulamaya fonksiyonlar ekleyerek kullanıcının setState yapması sağlanabilir.

Örneğin bir kişi listesindeki kişilerin isimleri sürekli değişen bir şey değildir. Bir değişiklik olduğunda hemen listeyi yenilemek yerine kullanıcının yukarıdan aşağıya çekmesi istenebilir. Bu çoğunlukla ağır listelerde kullanılır.

Yine de bir şekilde işlemciyi çok kullanıyorsa

Üstte bahsedilen herşeyi doğru yaptığınızı ve hala aynı sorunu yaşıyorsanız büyük ihtimalle yazdığınız bir fonksiyon uzun veya sonsuz bir döngüye girmiştir.

Bu tür hataları bulmak için kodunuza debug yapmalısınız. Chrome profile kullanabilirsiniz.

Kaynaklar

Console.profile ile profile almak:
http://www.avarekodcu.com/konu/11/javascript-console-log-komutunu-ozellestirme-kullanma

React Optimizasyon ve Performans:
https://reactjs.org/docs/optimizing-performance.html

React Native Performans ile ilgili yapılması gerekenler:
https://facebook.github.io/react-native/docs/performance.html

6 basit adımda React uygulamanızı hızlandırın:
https://codeburst.io/6-simple-ways-to-speed-up-your-react-native-app-d5b775ab3f16

React'in veri değişim felsefesi
https://github.com/ustun/reactjs-giris#veri-değişimi

Yorumlar ({{totalCommentCount}})

  • sercan

    {{commentLike106Count}} beğenme 2 ay önce

    Önemli bir noktaya değinmişsin, güzel yazı olmuş tebrik ederim.
    Beğen Beğendin
  • Düşündüklerin nedir ?

    Abdurrahman Eker

    (1010 Eylül 11111001100)

  • Full Stack Developer at Detaysoft Turkey/Sivas
  • İnternette Avare Kodcu
  • github
  • linkedin
  • youtube
  • Yeni içeriklerden haberdar olmak ister misin ?