React native源码解析(5000字)

环境准备:Node、Watchman、Xcode 和 CocoaPods & XCode ,稳定的代理工具(如果没有稳定的代理工具,基本上可以考虑放弃了)

  • 生成项目

npx react-native init App

cd App

yarn cd

cd ios

pod install (注意不要+sudo,此处必须全局开启代理,否则下载会失败)

cd ..

yarn ios

  • 如果yarn ios后无法看到Simulator有APP,使用xCode找到这个项目的ios目录的.xcworkspace

  • 然后用xCode打开build,成功后模拟器就会出现APP,打开即可进入

  • ⚠️:一定不要升级xCode高版本,跟我的版本保持一致最好,因为高版本xCode的voip唤醒激活会出现电话界面

如果你的环境是windows或者安卓,请参考官网

正式开始

  • 启动后,发现APP这样

  • 我们打开主入口的index.js文件

/**

* @format

*/

import {AppRegistry} from 'react-native';

import App from './App';

import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

  • 默认使用AppRegistry.registerComponent帮我们注册了一个组件(今天不对原理做过多讲解,有兴趣的可以自己搭建一个React-native脚手架,你会对整套运行原理、流程有一个真正的了解)
  • 接下来看APP组件

import React from 'react';

import {

SafeAreaView,

StyleSheet,

ScrollView,

View,

Text,

StatusBar,

} from 'react-native';

import {

Header,

LearnMoreLinks,

Colors,

DebugInstructions,

ReloadInstructions,

} from 'react-native/Libraries/NewAppScreen';

const App: () => React$Node = () => {

return (

<>

<StatusBar barStyle="dark-content" />

<SafeAreaView>

<ScrollView

contentInsetAdjustmentBehavior="automatic"

style={styles.scrollView}>

<Header />

{global.HermesInternal == null ? null : (

<View style={styles.engine}>

<Text style={styles.footer}>Engine: Hermes</Text>

</View>

)}

<View style={styles.body}>

<View style={styles.sectionContainer}>

<Text style={styles.sectionTitle}>Step One</Text>

<Text style={styles.sectionDescription}>

Edit <Text style={styles.highlight}>App.js</Text> to change this

screen and then come back to see your edits.

</Text>

</View>

<View style={styles.sectionContainer}>

<Text style={styles.sectionTitle}>See Your Changes</Text>

<Text style={styles.sectionDescription}>

<ReloadInstructions />

</Text>

</View>

<View style={styles.sectionContainer}>

<Text style={styles.sectionTitle}>Debug</Text>

<Text style={styles.sectionDescription}>

<DebugInstructions />

</Text>

</View>

<View style={styles.sectionContainer}>

<Text style={styles.sectionTitle}>Learn More</Text>

<Text style={styles.sectionDescription}>

Read the docs to discover what to do next:

</Text>

</View>

<LearnMoreLinks />

</View>

</ScrollView>

</SafeAreaView>

</>

);

};

const styles = StyleSheet.create({

...

});

export default App;

我们今天只看react-native这个库,默认导出的内容.

  • 即下面这段代码

import {

SafeAreaView,

StyleSheet,

ScrollView,

View,

Text,

StatusBar,

} from 'react-native';

  • 打开react-native源码

'use strict';

import typeof Button from './Libraries/Components/Button';

....

export type HostComponent<T> = _HostComponentInternal<T>;

const invariant = require('invariant');

const warnOnce = require('./Libraries/Utilities/warnOnce');

module.exports = {

// Components

get Button(): Button {

return require('./Libraries/Components/Button');

},

...

};

if (__DEV__) {

// $FlowFixMe This is intentional: Flow will error when attempting to access ART.

Object.defineProperty(module.exports, 'ART', {

configurable: true,

get() {

invariant(

false,

'ART has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/art' instead of 'react-native'. " +

'See https://github.com/react-native-community/art',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access ListView.

Object.defineProperty(module.exports, 'ListView', {

configurable: true,

get() {

invariant(

false,

'ListView has been removed from React Native. ' +

'See https://fb.me/nolistview for more information or use ' +

'`deprecated-react-native-listview`.',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access SwipeableListView.

Object.defineProperty(module.exports, 'SwipeableListView', {

configurable: true,

get() {

invariant(

false,

'SwipeableListView has been removed from React Native. ' +

'See https://fb.me/nolistview for more information or use ' +

'`deprecated-react-native-swipeable-listview`.',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access WebView.

Object.defineProperty(module.exports, 'WebView', {

configurable: true,

get() {

invariant(

false,

'WebView has been removed from React Native. ' +

"It can now be installed and imported from 'react-native-webview' instead of 'react-native'. " +

'See https://github.com/react-native-community/react-native-webview',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access NetInfo.

Object.defineProperty(module.exports, 'NetInfo', {

configurable: true,

get() {

invariant(

false,

'NetInfo has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/netinfo' instead of 'react-native'. " +

'See https://github.com/react-native-community/react-native-netinfo',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access CameraRoll.

Object.defineProperty(module.exports, 'CameraRoll', {

configurable: true,

get() {

invariant(

false,

'CameraRoll has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/cameraroll' instead of 'react-native'. " +

'See https://github.com/react-native-community/react-native-cameraroll',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access ImageStore.

Object.defineProperty(module.exports, 'ImageStore', {

configurable: true,

get() {

invariant(

false,

'ImageStore has been removed from React Native. ' +

'To get a base64-encoded string from a local image use either of the following third-party libraries:' +

"* expo-file-system: `readAsStringAsync(filepath, 'base64')`" +

"* react-native-fs: `readFile(filepath, 'base64')`",

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access ImageEditor.

Object.defineProperty(module.exports, 'ImageEditor', {

configurable: true,

get() {

invariant(

false,

'ImageEditor has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/image-editor' instead of 'react-native'. " +

'See https://github.com/react-native-community/react-native-image-editor',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access TimePickerAndroid.

Object.defineProperty(module.exports, 'TimePickerAndroid', {

configurable: true,

get() {

invariant(

false,

'TimePickerAndroid has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/datetimepicker' instead of 'react-native'. " +

'See https://github.com/react-native-community/datetimepicker',

);

},

});

// $FlowFixMe This is intentional: Flow will error when attempting to access ViewPagerAndroid.

Object.defineProperty(module.exports, 'ViewPagerAndroid', {

configurable: true,

get() {

invariant(

false,

'ViewPagerAndroid has been removed from React Native. ' +

"It can now be installed and imported from '@react-native-community/viewpager' instead of 'react-native'. " +

'See https://github.com/react-native-community/react-native-viewpager',

);

},

});

}

  • 我删了一些倒入和get定义,方便阅读
  • 这个源码文件大概有650行,module.export暴露出来了很多东西,但是,区分多种

    • 一种是Components组件
    • 一种是API
    • 一种是Plugins
    • 一种是Prop Types
    • 还有一种是最后的DEV环境下,

逐个攻破

  • 首先是组件

  • 其次是API

  • 然后是Plugins

  • 然后是Prop types

  • 最后是DEV环境下的对旧版本的部分API使用方式警告

可以看到入口文件中的一些API

  • 例如

  get AppRegistry(): AppRegistry {

return require('./Libraries/ReactNative/AppRegistry');

},

  • 图片

  get Image(): Image {

return require('./Libraries/Image/Image');

},

拿Image组件源码示例

  • 找到./Libraries/Image/Image源码

  • 脚手架应该根据是react-native run ios 还是 安卓,选择加载对应js,我们找到Image.ios.js文件,只有200行,今天重点主攻下
  • 默认暴露

module.exports = ((Image: any): React.AbstractComponent<

ImagePropsType,

React.ElementRef<typeof RCTImageView>,

> &

ImageComponentStatics);

  • Image对象

  • Image组件真正展示的

  return (

<RCTImageView

{...props}

ref={forwardedRef}

style={style}

resizeMode={resizeMode}

tintColor={tintColor}

source={sources}

/>

);

  • 找到RCTImageView,ImageViewNativeComponent.js这个文件

let ImageViewNativeComponent;

if (global.RN$Bridgeless) {

ImageViewNativeComponent = codegenNativeComponent<NativeProps>(

'RCTImageView',

);

} else {

ImageViewNativeComponent = requireNativeComponent<NativeProps>(

'RCTImageView',

);

}

module.exports = (ImageViewNativeComponent: HostComponent<NativeProps>);

  • 真正展示的是ImageViewNativeComponent,关于上面这段源码我查阅了一些的外文资料和其他源码,最终发现了一个注释

const NativeModules = require('../BatchedBridge/NativeModules');

const turboModuleProxy = global.__turboModuleProxy;

export function get < T: TurboModule > (name: string): ? T {

if (!global.RN$Bridgeless) {

// Backward compatibility layer during migration.

const legacyModule = NativeModules[name];

if (legacyModule != null) {

return ((legacyModule: any): T);

}

}

if (turboModuleProxy != null) {

const module: ? T = turboModuleProxy(name);

return module;

}

return null;

}

export function getEnforcing < T: TurboModule > (name: string): T {

const module = get(name);

return module;

}

  • Backward compatibility layer during migration.,即迁移过程中向后兼容,即兼容性处理
  • 这个codegenNativeComponent就是图片展示最终的一环,我们去看看是什么

忽略类型等其它空值警告判断,直入主题

  let componentNameInUse =

options && options.paperComponentName

? options.paperComponentName

: componentName;

if (options != null && options.paperComponentNameDeprecated != null) {

if (UIManager.getViewManagerConfig(componentName)) {

componentNameInUse = componentName;

} else if (

options.paperComponentNameDeprecated != null &&

UIManager.getViewManagerConfig(options.paperComponentNameDeprecated)

) {

componentNameInUse = options.paperComponentNameDeprecated;

} else {

throw new Error(

`Failed to find native component for either ${componentName} or ${options.paperComponentNameDeprecated ||

'(unknown)'}`,

);

}

}

// If this function is run at runtime then that means the view configs were not

// generated with the view config babel plugin, so we need to require the native component.

//

// This will be useful during migration, but eventually this will error.

return (requireNativeComponent<Props>(

componentNameInUse,

): HostComponent<Props>);

  • 还是 要先看UIManager.getViewManagerConfig

'use strict';

import type {Spec} from './NativeUIManager';

interface UIManagerJSInterface extends Spec {

+getViewManagerConfig: (viewManagerName: string) => Object;

+createView: (

reactTag: ?number,

viewName: string,

rootTag: number,

props: Object,

) => void;

+updateView: (reactTag: number, viewName: string, props: Object) => void;

+manageChildren: (

containerTag: ?number,

moveFromIndices: Array<number>,

moveToIndices: Array<number>,

addChildReactTags: Array<number>,

addAtIndices: Array<number>,

removeAtIndices: Array<number>,

) => void;

}

const UIManager: UIManagerJSInterface =

global.RN$Bridgeless === true

? require('./DummyUIManager') // No UIManager in bridgeless mode

: require('./PaperUIManager');

module.exports = UIManager;

  • 进入PaperUIManager找到getViewManagerConfig

 getViewManagerConfig: function(viewManagerName: string): any {

if (

viewManagerConfigs[viewManagerName] === undefined &&

NativeUIManager.getConstantsForViewManager

) {

try {

viewManagerConfigs[

viewManagerName

] = NativeUIManager.getConstantsForViewManager(viewManagerName);

} catch (e) {

viewManagerConfigs[viewManagerName] = null;

}

}

const config = viewManagerConfigs[viewManagerName];

if (config) {

return config;

}

// If we're in the Chrome Debugger, let's not even try calling the sync

// method.

if (!global.nativeCallSyncHook) {

return config;

}

if (

NativeUIManager.lazilyLoadView &&

!triedLoadingConfig.has(viewManagerName)

) {

const result = NativeUIManager.lazilyLoadView(viewManagerName);

triedLoadingConfig.add(viewManagerName);

if (result.viewConfig) {

getConstants()[viewManagerName] = result.viewConfig;

lazifyViewManagerConfig(viewManagerName);

}

}

return viewManagerConfigs[viewManagerName];

},

  • viewManagerConfigs初始化是一个空对象,key-value形式存储、管理这些原生视图配置
  • 我突然发现我错了路线,因为React-native虽然是用js写代码,不过最终都是转换成原生控件,回到主题的第一个代码底部

  return (requireNativeComponent<Props>(

componentNameInUse,

): HostComponent<Props>);

  • 最最关键的是:requireNativeComponent,根据componentName去加载原生组件,找到源码

'use strict';

const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');

const getNativeComponentAttributes = require('./getNativeComponentAttributes');

import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';

const requireNativeComponent = <T>(uiViewClassName: string): HostComponent<T> =>

((createReactNativeComponentClass(uiViewClassName, () =>

getNativeComponentAttributes(uiViewClassName),

): any): HostComponent<T>);

module.exports = requireNativeComponent;

 ((createReactNativeComponentClass(uiViewClassName, () =>

getNativeComponentAttributes(uiViewClassName),

): any): HostComponent<T>)

解析`createReactNativeComponentClass

  • createReactNativeComponentClass传入uiViewClassName即组件name,传入回调函数,返回 getNativeComponentAttributes(uiViewClassName)
  • 找到源码createReactNativeComponentClass

/**

* Copyright (c) Facebook, Inc. and its affiliates.

*

* This source code is licensed under the MIT license found in the

* LICENSE file in the root directory of this source tree.

*

* @format

* @flow strict-local

*/

'use strict';

import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

import type {ViewConfigGetter} from './ReactNativeTypes';

const {register} = ReactNativeViewConfigRegistry;

/**

* Creates a renderable ReactNative host component.

* Use this method for view configs that are loaded from UIManager.

* Use createReactNativeComponentClass() for view configs defined within JavaScript.

*

* @param {string} config iOS View configuration.

* @private

*/

const createReactNativeComponentClass = function(

name: string,

callback: ViewConfigGetter,

): string {

return register(name, callback);

};

module.exports = createReactNativeComponentClass;

  • 跟我预想一样,向register函数传入name和cb,注册成功后触发callback(getNativeComponentAttributes)
  • 找到ReactNativePrivateInterface.js里面的ReactNativeViewConfigRegistry

  get ReactNativeViewConfigRegistry(): ReactNativeViewConfigRegistry {

return require('../Renderer/shims/ReactNativeViewConfigRegistry');

},

  • 再找到register方法

exports.register = function(name: string, callback: ViewConfigGetter): string {

invariant(

!viewConfigCallbacks.has(name),

'Tried to register two views with the same name %s',

name,

);

invariant(

typeof callback === 'function',

'View config getter callback for component `%s` must be a function (received `%s`)',

name,

callback === null ? 'null' : typeof callback,

);

viewConfigCallbacks.set(name, callback);

return name;

};

  • 重点:viewConfigCallbacks.set(name, callback);viewConfigCallbacks是一个Map类型(ES6),key-value数据结构,怎么理解这段代码,看注释:

按名称注册本机视图/组件。

提供了一个回调函数来从UIManager加载视图配置。

回调被延迟直到视图被实际呈现。

  • 至此,加载原生组件逻辑配合之前的UImanager,getViewManagerConfig那块源码就解析完了。
  • 这是我们传入的cb(回调函数),获取原生组件属性

function getNativeComponentAttributes(uiViewClassName: string): any {

const viewConfig = UIManager.getViewManagerConfig(uiViewClassName);

invariant(

viewConfig != null && viewConfig.NativeProps != null,

'requireNativeComponent: "%s" was not found in the UIManager.',

uiViewClassName,

);

// TODO: This seems like a whole lot of runtime initialization for every

// native component that can be either avoided or simplified.

let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;

let nativeProps = viewConfig.NativeProps;

while (baseModuleName) {

const baseModule = UIManager.getViewManagerConfig(baseModuleName);

if (!baseModule) {

warning(false, 'Base module "%s" does not exist', baseModuleName);

baseModuleName = null;

} else {

bubblingEventTypes = {

...baseModule.bubblingEventTypes,

...bubblingEventTypes,

};

directEventTypes = {

...baseModule.directEventTypes,

...directEventTypes,

};

nativeProps = {

...baseModule.NativeProps,

...nativeProps,

};

baseModuleName = baseModule.baseModuleName;

}

}

const validAttributes = {};

for (const key in nativeProps) {

const typeName = nativeProps[key];

const diff = getDifferForType(typeName);

const process = getProcessorForType(typeName);

validAttributes[key] =

diff == null && process == null ? true : {diff, process};

}

// Unfortunately, the current setup declares style properties as top-level

// props. This makes it so we allow style properties in the `style` prop.

// TODO: Move style properties into a `style` prop and disallow them as

// top-level props on the native side.

validAttributes.style = ReactNativeStyleAttributes;

Object.assign(viewConfig, {

uiViewClassName,

validAttributes,

bubblingEventTypes,

directEventTypes,

});

if (!hasAttachedDefaultEventTypes) {

attachDefaultEventTypes(viewConfig);

hasAttachedDefaultEventTypes = true;

}

return viewConfig;

}

  • 至此,一个完整的React-native组件解析从加载、注册、展现整个过程就解析完了。

以上是 React native源码解析(5000字) 的全部内容, 来源链接: utcz.com/a/26922.html

回到顶部