5000字的React-native源码解析

2021-03-11 14:45:53 浏览数 (1)

写在开头
  • 近期公众号主攻下React-native,顺便我也复习下React-native,后续写作计划应该是主攻Node.js和跨平台方向、架构、Debug为主
  • 如果你感兴趣,建议关注下公众号,系统的学习下,推荐阅读之前我的的年度原创文章集合:https://mp.weixin.qq.com/s/RsvI5AFzbp3rm6sOlTmiYQ
正式开始
  • 环境准备:Node、Watchman、Xcode 和 CocoaPods & XCode ,稳定的代理工具(如果没有稳定的代理工具,基本上可以考虑放弃了)
  • 生成项目
代码语言:javascript复制
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文件
代码语言:javascript复制
/**
 * @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组件
代码语言:javascript复制
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这个库,默认导出的内容.
  • 即下面这段代码
代码语言:javascript复制
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
} from 'react-native';
  • 打开react-native源码
代码语言:javascript复制
'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
  • 例如
代码语言:javascript复制
  get AppRegistry(): AppRegistry {
    return require('./Libraries/ReactNative/AppRegistry');
  },
  • 图片
代码语言:javascript复制
  get Image(): Image {
    return require('./Libraries/Image/Image');
  },
拿Image组件源码示例
  • 找到./Libraries/Image/Image源码
  • 脚手架应该根据是react-native run ios 还是 安卓,选择加载对应js,我们找到Image.ios.js文件,只有200行,今天重点主攻下
  • 默认暴露
代码语言:javascript复制
module.exports = ((Image: any): React.AbstractComponent<
  ImagePropsType,
  React.ElementRef<typeof RCTImageView>,
> &
  ImageComponentStatics);
  • Image对象
  • Image组件真正展示的
代码语言:javascript复制
  return (
    <RCTImageView
      {...props}
      ref={forwardedRef}
      style={style}
      resizeMode={resizeMode}
      tintColor={tintColor}
      source={sources}
    />
  );
  • 找到RCTImageView,ImageViewNativeComponent.js这个文件
代码语言:javascript复制
let ImageViewNativeComponent;

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

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

  • 真正展示的是ImageViewNativeComponent,关于上面这段源码我查阅了一些的外文资料和其他源码,最终发现了一个注释
代码语言:javascript复制
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就是图片展示最终的一环,我们去看看是什么
忽略类型等其它空值警告判断,直入主题
代码语言:javascript复制
  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
代码语言:javascript复制
'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
代码语言:javascript复制
 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写代码,不过最终都是转换成原生控件,回到主题的第一个代码底部
代码语言:javascript复制
  return (requireNativeComponent<Props>(
    componentNameInUse,
  ): HostComponent<Props>);
  • 最最关键的是:requireNativeComponent,根据componentName去加载原生组件,找到源码
代码语言:javascript复制
'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;

❝最重要的加载原生组件的代码找到了 ❞

代码语言:javascript复制
 ((createReactNativeComponentClass(uiViewClassName, () =>
    getNativeComponentAttributes(uiViewClassName),
  ): any): HostComponent<T>)
解析`createReactNativeComponentClass
  • createReactNativeComponentClass传入uiViewClassName即组件name,传入回调函数,返回getNativeComponentAttributes(uiViewClassName)
  • 找到源码createReactNativeComponentClass
代码语言:javascript复制
/**
 * 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
代码语言:javascript复制
  get ReactNativeViewConfigRegistry(): ReactNativeViewConfigRegistry {
    return require('../Renderer/shims/ReactNativeViewConfigRegistry');
  },
  • 再找到register方法
代码语言:javascript复制
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数据结构,怎么理解这段代码,看注释:
代码语言:javascript复制
按名称注册本机视图/组件。
提供了一个回调函数来从UIManager加载视图配置。
回调被延迟直到视图被实际呈现。
  • 至此,加载原生组件逻辑配合之前的UImanager,getViewManagerConfig那块源码就解析完了。
  • 这是我们传入的cb(回调函数),获取原生组件属性
代码语言:javascript复制
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组件解析从加载、注册、展现整个过程就解析完了。

写在最后

  • 本文gitHub源码仓库:https://github.com/JinJieTan/chunchao,记得给个star
  • 我是Peter,架构设计过20万人端到端加密超级群功能的桌面IM软件,现在是一名前端架构师。 如果你对性能优化有很深的研究,可以跟我一起交流交流,今天这里写得比较浅,但是大部分人都够用,之前问我的朋友,我让它写了一个定时器定时消费队列,最后也能用。哈哈 另外欢迎收藏我的资料网站:前端生活社区:https://qianduan.life,感觉对你有帮助,可以右下角点个在看,关注一波公众号:[前端巅峰]

0 人点赞