写在开头
- 近期公众号主攻下
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
就是图片展示最终的一环,我们去看看是什么
忽略类型等其它空值警告判断,直入主题
代码语言: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
'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;
代码语言:javascript复制❝最重要的加载原生组件的代码找到了 ❞
((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组件解析从加载、注册、展现整个过程就解析完了。
写在最后
- 本文gitHub源码仓库:
https://github.com/JinJieTan/chunchao
,记得给个star
哦 - 我是Peter,架构设计过20万人端到端加密超级群功能的桌面IM软件,现在是一名前端架构师。
如果你对性能优化有很深的研究,可以跟我一起交流交流,今天这里写得比较浅,但是大部分人都够用,之前问我的朋友,我让它写了一个定时器定时消费队列,最后也能用。哈哈
另外欢迎收藏我的资料网站:前端生活社区:
https://qianduan.life
,感觉对你有帮助,可以右下角点个在看
,关注一波公众号:[前端巅峰
]