写在开头

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

正式开始

  • 环境准备:Node、Watchman、Xcode 和 CocoaPods & XCode ,稳定的代理工具(如果没有稳定的代理工具,基本上可以考虑放弃了)
  • 生成项目
npx react-native init Appcd 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组件解析从加载、注册、展现整个过程就解析完了。