乐趣区

5000字的Reactnative源码解析

写在开头

  • 近期公众号主攻下 React-native, 顺便我也复习下 React-native, 后续写作计划应该是主攻 Node.js 和跨平台方向、架构、Debug 为主
  • 如果你感兴趣,建议关注下公众号,系统的学习下,推荐阅读之前我的的年度原创文章集合:https://mp.weixin.qq.com/s/RsvI5AFzbp3rm6sOlTmiYQ

正式开始

  • 环境准备: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

注意 0.60 版本之后的主项目文件是.xcworkspace,不是.xcodeproj。

  • 然后用 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 组件解析从加载、注册、展现整个过程就解析完了。
退出移动版