本文介紹了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)文章。