React Native实践React-Native 开发阵营React Native开发

React Native学习笔记(三)

2018-01-30  本文已影响225人  于卫国

本文介绍了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学习笔记相关文章

参考

上一篇 下一篇

猜你喜欢

热点阅读