5000字的React-native源码解析

写在开头

  • 近期公众号主攻下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

5000字的React-native源码解析_第1张图片

注意 0.60 版本之后的主项目文件是.xcworkspace,不是.xcodeproj。
  • 然后用xCode打开build,成功后模拟器就会出现APP,打开即可进入

5000字的React-native源码解析_第2张图片

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

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

正式开始

  • 启动后,发现APP这样

5000字的React-native源码解析_第3张图片

  • 我们打开主入口的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 (
    <>
      
      
        
          
{global.HermesInternal == null ? null : ( Engine: Hermes )} Step One Edit App.js to change this screen and then come back to see your edits. See Your Changes Debug Learn More Read the docs to discover what to do next: ); }; 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 = _HostComponentInternal;

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环境下,

逐个攻破

  • 首先是组件

5000字的React-native源码解析_第4张图片

  • 其次是API

5000字的React-native源码解析_第5张图片

  • 然后是Plugins

5000字的React-native源码解析_第6张图片

  • 然后是Prop types

5000字的React-native源码解析_第7张图片

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

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

  • 例如
  get AppRegistry(): AppRegistry {
    return require('./Libraries/ReactNative/AppRegistry');
  },
  • 图片
  get Image(): Image {
    return require('./Libraries/Image/Image');
  },

拿Image组件源码示例

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

5000字的React-native源码解析_第8张图片

  • 脚手架应该根据是react-native run ios 还是 安卓,选择加载对应js,我们找到Image.ios.js文件,只有200行,今天重点主攻下
  • 默认暴露
module.exports = ((Image: any): React.AbstractComponent<
  ImagePropsType,
  React.ElementRef,
> &
  ImageComponentStatics);
  • Image对象

  • Image组件真正展示的
  return (
    
  );
  • 找到RCTImageView,ImageViewNativeComponent.js这个文件
let ImageViewNativeComponent;

if (global.RN$Bridgeless) {
  ImageViewNativeComponent = codegenNativeComponent(
    'RCTImageView',
  );
} else {
  ImageViewNativeComponent = requireNativeComponent(
    'RCTImageView',
  );
}

module.exports = (ImageViewNativeComponent: HostComponent);
  • 真正展示的是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(
    componentNameInUse,
  ): HostComponent);
  • 还是 要先看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,
    moveToIndices: Array,
    addChildReactTags: Array,
    addAtIndices: Array,
    removeAtIndices: Array,
  ) => 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(
    componentNameInUse,
  ): HostComponent);
  • 最最关键的是:requireNativeComponent,根据componentName去加载原生组件,找到源码
'use strict';

const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');
const getNativeComponentAttributes = require('./getNativeComponentAttributes');
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';

const requireNativeComponent = (uiViewClassName: string): HostComponent =>
  ((createReactNativeComponentClass(uiViewClassName, () =>
    getNativeComponentAttributes(uiViewClassName),
  ): any): HostComponent);

module.exports = requireNativeComponent;
最重要的加载原生组件的代码找到了
 ((createReactNativeComponentClass(uiViewClassName, () =>
    getNativeComponentAttributes(uiViewClassName),
  ): any): HostComponent)

解析`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组件解析从加载、注册、展现整个过程就解析完了。

你可能感兴趣的:(前端,javascript,node.js,java,后端)