React Native学习笔记(三)

本文介绍了Component和PureComponent的之间的区别、如何封装原生模块及原生View供React Native调用。

本文首发:http://yuweiguocn.github.io/

《春望》
国破山河在,城春草木深。
感时花溅泪,恨别鸟惊心。
烽火连三月,家书抵万金。
白头搔更短,浑欲不胜簪。
—唐,杜甫

Component vs PureComponent

在上一篇文章中我们简单介绍了Component的使用,PureComponent又是用来做什么的,和Component有什么区别?

在React中,只要我们调用了 this.setState 更新组件状态,组件就会被重新渲染。我们通常会重写 shouldComponentUpdate 方法返回 true 或 false 告诉系统当前组件是否需要重新渲染以此来提升性能。我们来改一下上一篇文章中的示例,初始赋值为0,点击按钮更新为10,然后重写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

第一次点击按钮,输出日志:

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

第二次及第三次点击,输出日志:

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

可以看到只有第一次点击按钮执行了render方法进行了渲染,之后便不再进行重新渲染。

使用Component我们需要自己重写shouldComponentUpdate方法判断组件是否需要重新渲染以此来提升性能,PureComponent帮我们重写了shouldComponentUpdate方法,但是对props和state只是进行浅比较(shadow comparison),当props或者state本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的props和state发生了变化,但组件却没有更新的问题。

PureComponent对性能的提升是非常可观的,因为它减少了应用中的渲染次数,所以推荐使用 PureComponent。使用PureComponent我们只需要简单地将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)

通过封装原生模块使我们可以通过JS调用原生代码,例如调用Android中的Toast显示一个消息。本示例仅作为了解封装原生模块说明,React Native已经帮我们封装了ToastAndroid模块。我们先来看一下Java代码实现部分。创建一个类继承ReactContextBaseJavaModule类,实现getName方法返回module名称,添加一个public void的方法并添加@ReactMethod注解,可以重写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"; //这个就是JS调用的module的名称
    }

    @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();
    }
}

创建一个类继承LazyReactPackage类实现抽象方法,其中getNativeModules用于注册原生模块,将我们新写的RNToast注册进去:

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注册一下:

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代码实现部分就完成了,接下来看一下JS代码实现部分。为了方便使用我们新建一个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模块,在按钮点击方法中调用原生模块方法:

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调用原生Toast效果

封装原生View

React Native已经帮我们封装了大部分常见组件,但我们也可能会遇到需要封装自定义View的组件情况。接下来介绍如何封装原生View,本示例仅作封装原生View的说明。例如封装一个原生ImageView。

新建一个类继承SimpleViewManager类并指定自定义的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中注册一下:

public class RNPackage extends LazyReactPackage {
    ...

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

接下来看一下JS端需要处理的代码。同样新建一个类引入原生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学习笔记相关文章

参考

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

推荐阅读更多精彩内容