React Native學(xué)習(xí)筆記(三)

本文介紹了Component和PureComponent的之間的區(qū)別、如何封裝原生模塊及原生View供React Native調(diào)用。

本文首發(fā):http://yuweiguocn.github.io/

《春望》
國(guó)破山河在,城春草木深。
感時(shí)花濺淚,恨別鳥(niǎo)驚心。
烽火連三月,家書(shū)抵萬(wàn)金。
白頭搔更短,渾欲不勝簪。
—唐,杜甫

Component vs PureComponent

在上一篇文章中我們簡(jiǎn)單介紹了Component的使用,PureComponent又是用來(lái)做什么的,和Component有什么區(qū)別?

在React中,只要我們調(diào)用了 this.setState 更新組件狀態(tài),組件就會(huì)被重新渲染。我們通常會(huì)重寫(xiě) shouldComponentUpdate 方法返回 true 或 false 告訴系統(tǒng)當(dāng)前組件是否需要重新渲染以此來(lái)提升性能。我們來(lái)改一下上一篇文章中的示例,初始賦值為0,點(diǎn)擊按鈕更新為10,然后重寫(xiě)shouldComponentUpdate方法判斷是否需要重新渲染。

App.js

import React, {Component} from 'react';
import {StyleSheet, Text, Button, View} from 'react-native';

class CountText extends Component {
    render() {
        return (<Text>{this.props.count}</Text>);
    }
}

export default class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    
    shouldComponentUpdate(nextProps, nextState){
        console.log("shouldComponentUpdate");
        if(this.state.count != nextState.count){
            return true;
        }
        return false;
    }

    pressButton = () => {
        this.setState({count:10})
    };


    render() {
        console.log("render");
        return (
            <View style={styles.container}>
                <Button onPress={this.pressButton}
                        title="Click Me"
                        color="#841584"/>
                <CountText style={styles.countText} count={this.state.count} />
            </View>
        );
    }
}
...

tips:在終端上查看日志打印命令:react-native log-android

第一次點(diǎn)擊按鈕,輸出日志:

01-29 19:58:47.633  5029  5090 I ReactNativeJS: shouldComponentUpdate
01-29 19:58:47.634  5029  5090 I ReactNativeJS: render

第二次及第三次點(diǎn)擊,輸出日志:

01-29 19:58:52.036  5029  5090 I ReactNativeJS: shouldComponentUpdate
01-29 19:58:53.636  5029  5090 I ReactNativeJS: shouldComponentUpdate

可以看到只有第一次點(diǎn)擊按鈕執(zhí)行了render方法進(jìn)行了渲染,之后便不再進(jìn)行重新渲染。

使用Component我們需要自己重寫(xiě)shouldComponentUpdate方法判斷組件是否需要重新渲染以此來(lái)提升性能,PureComponent幫我們重寫(xiě)了shouldComponentUpdate方法,但是對(duì)props和state只是進(jìn)行淺比較(shadow comparison),當(dāng)props或者state本身是嵌套對(duì)象或數(shù)組等時(shí),淺比較并不能得到預(yù)期的結(jié)果,這會(huì)導(dǎo)致實(shí)際的props和state發(fā)生了變化,但組件卻沒(méi)有更新的問(wèn)題。

PureComponent對(duì)性能的提升是非??捎^的,因?yàn)樗鼫p少了應(yīng)用中的渲染次數(shù),所以推薦使用 PureComponent。使用PureComponent我們只需要簡(jiǎn)單地將Component替換為PureComponent即可:

App.js

import React, {PureComponent} from 'react';
import {StyleSheet, Text, Button, View} from 'react-native';

class CountText extends PureComponent {
    render() {
        return (<Text>{this.props.count}</Text>);
    }
}

export default class App extends PureComponent {

    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    pressButton = () => {
        this.setState({count:10})
    };


    render() {
        console.log("render");
        return (
            <View style={styles.container}>
                <Button onPress={this.pressButton}
                        title="Click Me"
                        color="#841584"/>
                <CountText style={styles.countText} count={this.state.count} />
            </View>
        );
    }
}
...

封裝原生模塊(Native Module)

通過(guò)封裝原生模塊使我們可以通過(guò)JS調(diào)用原生代碼,例如調(diào)用Android中的Toast顯示一個(gè)消息。本示例僅作為了解封裝原生模塊說(shuō)明,React Native已經(jīng)幫我們封裝了ToastAndroid模塊。我們先來(lái)看一下Java代碼實(shí)現(xiàn)部分。創(chuàng)建一個(gè)類(lèi)繼承ReactContextBaseJavaModule類(lèi),實(shí)現(xiàn)getName方法返回module名稱(chēng),添加一個(gè)public void的方法并添加@ReactMethod注解,可以重寫(xiě)getConstants方法定義一些JS端方便使用的常量。

public class RNToast extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public RNToast(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "CustomToast"; //這個(gè)就是JS調(diào)用的module的名稱(chēng)
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    @ReactMethod
    public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
    }
}

創(chuàng)建一個(gè)類(lèi)繼承LazyReactPackage類(lèi)實(shí)現(xiàn)抽象方法,其中g(shù)etNativeModules用于注冊(cè)原生模塊,將我們新寫(xiě)的RNToast注冊(cè)進(jìn)去:

public class RNPackage extends LazyReactPackage {

    @Override
    public List<ModuleSpec> getNativeModules(final ReactApplicationContext reactContext) {
        return Arrays.asList(
                ModuleSpec.nativeModuleSpec(
                        RNToast.class,
                        new Provider<NativeModule>() {
                            @Override
                            public NativeModule get() {
                                return new RNToast(reactContext);
                            }
                        }));
    }

    @Override
    public ReactModuleInfoProvider getReactModuleInfoProvider() {
        return LazyReactPackage.getReactModuleInfoProviderViaReflection(this);
    }
}

最后將我們自定義的ReactPackage在MainApplication注冊(cè)一下:

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    ...

    @Override
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
                new MainReactPackage(),
                new RNPackage()
        );
    }
    ...
  };
  ...
}

至此Java代碼實(shí)現(xiàn)部分就完成了,接下來(lái)看一下JS代碼實(shí)現(xiàn)部分。為了方便使用我們新建一個(gè)JS文件引入CustomToast模塊:
CustomToast.js

'use strict';

/**
 * This exposes the native CustomToast module as a JS module. This has a function 'show'
 * which takes the following parameters:
 *
 * 1. String message: A string with the text to toast
 * 2. int duration: The duration of the toast. May be CustomToast.SHORT or CustomToast.LONG
 */
import { NativeModules } from 'react-native';

export default NativeModules.CustomToast;

然后在App.js文件中引入CustomToast模塊,在按鈕點(diǎn)擊方法中調(diào)用原生模塊方法:

App.js

import React, {PureComponent} from 'react';
import {StyleSheet, Text, Button, View} from 'react-native';
import CustomToast from './CustomToast';

class CountText extends PureComponent {
    render() {
        return (<Text>{this.props.count}</Text>);
    }
}

export default class App extends PureComponent {

    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    pressButton = () => {
        let result=this.state.count+1;
        CustomToast.show("count is "+result,CustomToast.SHORT);
        this.setState(preState=>{
            return {count: preState.count+1}
        })
    };

    render() {
        console.log("render");
        return (
            <View style={styles.container}>
                <Button onPress={this.pressButton}
                        title="Click Me"
                        color="#841584"/>
                <CountText style={styles.countText} count={this.state.count} />
            </View>
        );
    }
}
...

圖 JS調(diào)用原生Toast效果

封裝原生View

React Native已經(jīng)幫我們封裝了大部分常見(jiàn)組件,但我們也可能會(huì)遇到需要封裝自定義View的組件情況。接下來(lái)介紹如何封裝原生View,本示例僅作封裝原生View的說(shuō)明。例如封裝一個(gè)原生ImageView。

新建一個(gè)類(lèi)繼承SimpleViewManager類(lèi)并指定自定義的View:

public class RNImageView extends SimpleViewManager<ImageView> {
    @Override
    public String getName() {
        return "CustomImageView";
    }

    @Override
    protected ImageView createViewInstance(final ThemedReactContext reactContext) {
        ImageView imageView = new ImageView(reactContext);
        imageView.setImageResource(R.drawable.logo);
        return imageView;
    }
}

然后在我們自定義的ReactPackage中注冊(cè)一下:

public class RNPackage extends LazyReactPackage {
    ...

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new RNImageView()
        );
    }
    ...
}

接下來(lái)看一下JS端需要處理的代碼。同樣新建一個(gè)類(lèi)引入原生View,...View.propTypes用于引入原生View的原有屬性。

CustomImageView.js

import { requireNativeComponent, View } from 'react-native';

var iface = {
    name: 'CustomImageView',
    propTypes: {
        ...View.propTypes
    }
};

module.exports = requireNativeComponent('CustomImageView', iface);

然后在App.js文件中引入CustomImageView組件并指定寬高:

import React, {PureComponent} from 'react';
import {StyleSheet, Text, Button, View} from 'react-native';
import CustomToast from './CustomToast';
import CustomImageView from './CustomImageView'

class CountText extends PureComponent {
    render() {
        return (<Text>{this.props.count}</Text>);
    }
}

export default class App extends PureComponent {

    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    ...

    render() {
        console.log("render");
        return (
            <View style={styles.container}>
                <Button onPress={this.pressButton}
                        title="Click Me"
                        color="#841584"/>
                <CountText style={styles.countText} count={this.state.count} />

                <CustomImageView style={{width: 200,height: 200}}/>
            </View>
        );
    }
}
...

圖 JS引用原生View效果

查看完整源碼:https://github.com/yuweiguocn/RNTest

查看React Native學(xué)習(xí)筆記相關(guān)文章。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 很多React/React Native的初學(xué)者都被ES6的問(wèn)題迷惑:各路大神都建議我們直接學(xué)習(xí)ES6的語(yǔ)法,然而...
    Nickyzhang閱讀 608評(píng)論 0 1
  • 山雨欲來(lái)風(fēng)滿樓,清瀾之下波暗涌。 此徑固鎖終可耐,空將嘆月轉(zhuǎn)頭空。 尹常說(shuō):人總是過(guò)分高估三年所到達(dá)的層次,卻低估...
    牧之多瑙河閱讀 371評(píng)論 4 0
  • https://www.w3cways.com/1972.html
    lmh829閱讀 437評(píng)論 0 0
  • 最近總是遇到很多顧客,我平平靜靜地說(shuō)了三句話,突然間顧客暴走。也總是遇到,因?yàn)橐稽c(diǎn)點(diǎn)小事就在路上大吵大鬧的人。 某...
    棉棉花花閱讀 223評(píng)論 0 0

友情鏈接更多精彩內(nèi)容