赞
踩
1. 首先声明下环境:因为环境而造成的坑太多了,我都忍不住吐槽N遍了,所以在开始整活之前先搞清楚我们现在用的环境。
2. 首先查看下所使用的 ndoejs 版本 nodejs 16.10.0
- E:\ReactLove\rn_hello\rnHello_CodePush_060>node -v
- v16.10.0
-
- E:\ReactLove\rn_hello\rnHello_CodePush_060>nvm list
-
- * 16.10.0 (Currently using 64-bit executable)
- 12.18.0
- 12.2.0
注意:这里使用的 nvm 是为了方便管理和使用不同的 nodejs,因为不同版本的ReactNative可能会需要不同版本的 nodejs 环境, 否则在构建项目的时候,会发生各种令人吐槽的事情。
3. 查看下 ReactNative 版本
- E:\ReactLove\rn_hello\rnHello_CodePush_060>react-native -v
- react-native-cli: 2.0.1
- react-native: 0.60.0
这里我们可以看到,我们已经安装了 react-native-cli 命令行,然后我们开始准备使用该命令行创建我们新的ReactNative项目,版本是0.60.0。
4. 创建 ReactNative 项目
E:\ReactLove\rn_hello>npx react-native init rnCodePushDemo_060 --version 0.60.0
5. 创建好项目之后,需要修改三个地方,然后才能正常使用,否则直接执行命令 react-native run-android 会报错的,这是经过实战经验所得。
5.1 修改项目根目录下的 babel.config. js 文件
- module.exports = {
- presets: [[
- 'module:metro-react-native-babel-preset',{
- unstable_disableES6Transforms: true
- }
- ]],
- };
5.2 修改项目根目录下的 package.json 文件
- {
- "name": "rnCodePushDemo_060",
- "version": "0.0.1",
- "private": true,
- "scripts": {
- "start": "react-native start",
- "test": "jest",
- "lint": "eslint ."
- },
- "dependencies": {
- "react": "16.8.6",
- "react-native": "0.60.0"
- },
- "devDependencies": {
- "@babel/core": "7.19.3",
- "@babel/runtime": "7.19.4",
- "@react-native-community/eslint-config": "0.0.3",
- "babel-jest": "24.9.0",
- "eslint": "5.16.0",
- "jest": "24.9.0",
- "metro-react-native-babel-preset": "0.59.0",
- "react-test-renderer": "16.8.6"
- },
- "resolutions": {
- "@babel/traverse": "7.16.7"
- },
- "jest": {
- "preset": "react-native"
- }
- }
"devDependencies": {
....
"metro-react-native-babel-preset": "0.59.0",
},
"resolutions": {
"@babel/traverse": "7.16.7"
},
如上红色标注的部分,就是在 package.json 文件中修改的内容。
5.3 修改项目 \node_modules\metro-config\src\defaults 目录下的 blacklist.js 文件中的内容
var sharedBlacklist = [
/node_modules[\/\\]react[\/\\]dist[\/\\].*/,
/website\/node_modules\/.*/,
/heapCapture\/bundle\.js/,
/.*\/__tests__\/.*/
];
黄色部分就是要修改的部分内容,否则在项目直接进行编译和启动的时候,会存现nodejs 服务启动之后就一闪而过,就结束 nodejs 服务了,自然也没法进行项目开发了。
6. 在修改以上三个地方的内容之后,就可以尝试编译和启动React Native项目了
E:\ReactLove\rn_hello\rnCodePushDemo_060>react-native run-android
最终就可以顺利启动项目了, 展示如下:
终端:
nodejs 服务端:
7. 在顺利编译并启动项目之后,我们就开始准备热更新的部分。
热更新其实就是将ReactNative项目中的js打包成一个bundle文件,然后放到服务器上,在应用需要热更新的时候,从服务器上下载这个新的bundle文件,然后将这个bundle文件载入ReactNative框架中,然后进行更新,这样就无须下载新的 Android apk 包,实现业务的更新或者Bug的修改,这也是React Native 被好多大厂所看好的原因,目前除了大厂自研的Android原生热更新框架,下面就是这种类型的热更新是最好的了,其他要不是没有人用,要不就是大厂自己搞着自己玩的,也不开源,没啥意思,能让大家用的也就是React Native。Flutter 除了几家大厂推出来的框架,也不成气候,没啥大意思,估计过几年也就没有前途了,只有用的人多才是有前途的跨平台框架和热更新框架。CodePush 是 微软针对 React Native 推出的热更新插件,可以将新的Bundle文件推送到CodePush 平台上,然后供大家进行下载、加载进行热更新。
8. 在了解了什么是热更新之后,我们正式开始热更新插件的接入:
8.1 首先安装 code-push-cli 命令行工具,如下是全局安装
E:\ReactLove\rn_hello\rnHello_060>npm install -g code-push-cli
8.2 注册 CodePush
E:\ReactLove\rn_hello\rnHello_060>code-push register
这里会弹出一个网站,然后我这里选择使用Github进行登录,然后授权之后,就会弹出包含如下内容的弹框:
- Authentication succeeded
- Please copy and paste this token to the command window:
-
- 558aa5b330e8262fe292a58c4366847a2ff3a3b7
-
- After doing so, please close this browser tab.
将这串字符粘贴到刚才的命令行窗口中,你会得到如下内容:
- Enter your token from the browser: 558aa5b330e8262fe292a58c4366847a2ff3a3b7
-
- Successfully logged-in. Your session file was written to C:\Users\PCWin10\AppData\Local\.code-push.config.
- You can run the code-push logout command at any time to delete this file and terminate your session.
8.3 注册成功后,应该不会再使用这个 code-push register 命令,除非你想推出重新登录。
8.4 向CodePush服务器注册新的 App
- //AppName 就是你要创建的应用名称
- //platform 可以使 ios 或者 Android
- code-push app add <AppName> <platform> react-native
向CodePush服务器注册一个新的App,名称为 CodePushDemo,命令如下:
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push app add CodePushDemo Android react-native
命令执行完成之后,会看到如下结果:
- │ Name │ Deployment Key │
- ├────────────┼───────────────────────────────────────┤
- │ Production │ mcNDEqhhKH2fvm_NNVSzwC1JVPgpaSLFU9WAj │
- ├────────────┼───────────────────────────────────────┤
- │ Staging │ 4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_ │
上面是一套部署的Key, 分为 Staging 开发版本和 Production 版本,我们这里使用的是开发版本。
注册的App 应用情况可以通过 https://appcenter.ms/apps 来查看,如下:
其中,CodePushDemo 就是我们刚刚创建的项目。
8.5 集成 CodePush SDK ,添加 code-push 包
E:\ReactLove\rn_hello\rnCodePushDemo_060>npm install --save react-native-code-push
8.6 如果8.5 没有提示说输入 Deployment Key,那么我们需要手动配置,如果忘记 Deployment Key,可以通过如下命令来查看:
code-push deployment ls CodePushDemo -k
- E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push deployment ls CodePushDemo -k
- (node:8212) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
- (Use `node --trace-warnings ...` to show where the warning was created)
- ┌────────────┬───────────────────────────────────────┬─────────────────────┬──────────────────────┐
- │ Name │ Deployment Key │ Update Metadata │ Install Metrics │
- ├────────────┼───────────────────────────────────────┼─────────────────────┼──────────────────────┤
- │ Production │ mcNDEqhhKH2fvm_NNVSzwC1JVPgpaSLFU9WAj │ No updates released │ No installs recorded │
- ├────────────┼───────────────────────────────────────┼─────────────────────┼──────────────────────┤
- │ Staging │ 4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_ │ No updates released │ No installs recorded │
- └────────────┴───────────────────────────────────────┴─────────────────────┴──────────────────────┘
8.7 手动配置,首先用AndroidStudio 打开 android 项目,然后再 android/settings.gradle 文件中添加如下内容:
- include ':react-native-code-push'
- project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
8.8 在 app/build.gradle 文件中添加如下内容:
- apply from: '../../node_modules/react-native-code-push/android/codepush.gradle'
-
- //下面这行如果有,则不加
- apply from: "../../node_modules/react-native/react.gradle"
8.9 在 MainApplication.java 文件中,添加如下内容:
- private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
- @Override
- public boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
-
- @Override
- protected List<ReactPackage> getPackages() {
- @SuppressWarnings("UnnecessaryLocalVariable")
- List<ReactPackage> packages = new PackageList(this).getPackages();
- // Packages that cannot be autolinked yet can be added manually here, for example:
- // packages.add(new MyReactNativePackage());
- return packages;
- }
-
- @Override
- protected String getJSMainModuleName() {
- return "index";
- }
-
- @Nullable
- @Override
- protected String getJSBundleFile() {
- return CodePush.getJSBundleFile();
- }
-
- };
@Nullable
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
红色标记部分即为要添加的内容,注意引入 import CodePush 类。
8.10 在 res/values/strings.xml 中添加 CodePushDeploymentKey,这里内容填写的就是前面获取到的 StagingKey。
- <resources>
- ...
- <string name="CodePushDeploymentKey">4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_</string>
- </resources>
8.11 在 app/build.gradle 文件中,添加如下内容:
- android {
- ....
-
- buildTypes {
- debug {
- signingConfig signingConfigs.debug
- }
- release {
- // Caution! In production, you need to generate your own keystore file.
- // see https://facebook.github.io/react-native/docs/signed-apk-android.
- //填入 PRODUCTION_KEY
- buildConfigField "String", "CODEPUSH_KEY", '"mcNDEqhhKH2fvm_NNVSzwC1JVPgpaSLFU9WAj"'
- signingConfig signingConfigs.debug
- minifyEnabled enableProguardInReleaseBuilds
- proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
- }
- releaseStaging {
- minifyEnabled enableProguardInReleaseBuilds
- proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
- //填入 STAGING_KEY
- buildConfigField "String", "CODEPUSH_KEY", '"4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_"'
- }
- }
- ...
-
- }
8.12 如上,就已经完成了原生部分的代码,下面开始JavaScript 部分的配置:
8.13 在ReactNative项目中的 App.js 文件中进行如下修改:
- import React from 'react';
- import {
- View,
- Text,
- } from 'react-native';
-
- import CodePush from 'react-native-code-push';
-
- const codePushOptions = {
- checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
- updateDialog: true, //发现可更新时是否显示对话框
- installMode: CodePush.InstallMode.IMMEDIATE,
- };
-
- class App extends React.Component {
-
- render() {
- return (
- <View>
- <Text>版本号1.0</Text>
- <Text>我是1.0版本,希望大家能够喜欢</Text>
- </View>
- );
- }
-
- update = () => {
- CodePush.sync({
- // 安装模式
- // ON_NEXT_RESUME 下次恢复到前台时
- // ON_NEXT_RESTART 下一次重启时
- // IMMEDIATE 马上更新
- installMode: CodePush.InstallMode.IMMEDIATE,
- // 对话框 如果打包和上传没有设置强制更新的话,那么默认就是非强制更新,显示的都是非强制更新配置的内容
- updateDialog: {
- // 是否显示更新描述
- appendReleaseDescription: true,
- // 更新描述的前缀。 默认为"Description"
- descriptionPrefix: '更新内容:',
- // 强制更新按钮文字,默认为continue
- mandatoryContinueButtonLabel: '立即更新',
- // 强制更新时的信息. 默认为"An update is available that must be installed."
- mandatoryUpdateMessage: '必须更新后才能使用',
- // 非强制更新时,按钮文字,默认为"ignore"
- optionalIgnoreButtonLabel: '稍后',
- // 非强制更新时,确认按钮文字. 默认为"Install"
- optionalInstallButtonLabel: '后台更新',
- // 非强制更新时,检查到更新的消息文本
- optionalUpdateMessage: '有新版本了,是否更新?',
- // Alert窗口的标题
- title: '更新提示',
- },
- }).then(r => {
-
- });
- };
-
- async componentWillMount() {
- CodePush.disallowRestart(); //禁止重启
- this.update();//开始检查更新
- }
-
- componentDidMount() {
- CodePush.allowRestart();//加载完了,允许重启
- }
- }
-
- App = CodePush(codePushOptions)(App);
- export default App;
在 index.js 文件中进行如下修改:
- import {AppRegistry} from 'react-native';
- import App from './App';
- import {name as appName} from './app.json';
-
- //添加如下关闭黄色警告
- console.disableYellowBox = true; //关闭全部黄色警告
- console.ignoredYellowBox = ['Warning: BackAndroid is deprecated. Please use BackHandler instead.'];
-
- AppRegistry.registerComponent(appName, () => App);
//添加如下关闭黄色警告
console.disableYellowBox = true; //关闭全部黄色警告
console.ignoredYellowBox = ['Warning: BackAndroid is deprecated. Please use BackHandler instead.'];
8.14 执行 react-native run-android 命令,编译并启动 项目:
E:\ReactLove\rn_hello\rnCodePushDemo_060>react-native run-android
结果如下,即没有进行新的内容修改之前的展示内容:
8.15 对页面的内容进行修改
- <View>
- <Text>版本号1.1</Text>
- <Text>我是1.1版本,此版本新增加了许多新的功能,欢迎体验!</Text>
- </View>
8.16 打包并上传 bundle 文件,命令如下,CodePushDemo 就是前面设置的App项目名称, android 为平台类型。
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android
- E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android
- (node:7820) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
- (Use `node --trace-warnings ...` to show where the warning was created)
- Detecting android app version:
-
- Using the target binary version value "1.0" from "android\app\build.gradle".
-
- Running "react-native bundle" command:
-
- node node_modules\react-native\local-cli\cli.js bundle --assets-dest C:\Users\PCWin10\AppData\Local\Temp\CodePush\CodePush --bundle-output C:\Users\PCWin10\AppData\Local\Temp\CodePush\CodePush\index.android.bundle --dev false --entry-file index.js --platform android
- Loading dependency graph, done.
-
- info Writing bundle output to:, C:\Users\PCWin10\AppData\Local\Temp\CodePush\CodePush\index.android.bundle
- info Done writing bundle output
- info Copying 2 asset files
- info Done copying assets
-
- Releasing update contents to CodePush:
-
- Upload progress:[==================================================] 100% 0.0s
- Successfully released an update containing the "C:\Users\PCWin10\AppData\Local\Temp\CodePush\CodePush" directory to the "Staging" deployment of the "CodePushDemo" app.
-
- E:\ReactLove\rn_hello\rnCodePushDemo_060>
8.17 在上传完成之后,可以查看 CodePush 平台上的发布历史记录,刚才发布的也在其中,命令如下:CodePushDemo 是App 项目的名称
- E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push deployment history CodePushDemo Staging
- (node:14968) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
- (Use `node --trace-warnings ...` to show where the warning was created)
- ┌───────┬───────────────┬─────────────┬───────────┬─────────────┬──────────────────────┐
- │ Label │ Release Time │ App Version │ Mandatory │ Description │ Install Metrics │
- ├───────┼───────────────┼─────────────┼───────────┼─────────────┼──────────────────────┤
- │ v1 │ 8 minutes ago │ 1.0 │ No │ │ No installs recorded │
- └───────┴───────────────┴─────────────┴───────────┴─────────────┴──────────────────────┘
-
- E:\ReactLove\rn_hello\rnCodePushDemo_060>
8.18 在发布到平台完成之后,我们可以开始测试,首先将当前正在运行的该项目App杀掉,然后进行重启,会发现弹出如下弹框:
8.19 我们选择 后台更新,然后过几秒之后,页面就会重新刷新,变成如下内容:
8.20 杀掉App进程,重新启动, 会发现此次进入是没有弹出新的版本提示,说明当前已经是最新的版本。
9. 如何正式部署 Release 版本
因为执行以下命令默认是 Staging 版本的,即推送到 CodePush 平台之后,是在Staging环境中的,而不是 Production 环境的。
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android
但是其实跟发布到哪种环境没有关系,其实都是可以的,我们先考虑 Staging 环境。比如我们先前已经打包好一个 Release 包,给用户安装到手机上了,那么请问,此时客户手机上的 deployment key 是什么类型的呢?如果没有改动,依据前面的是配置,我们在打release 包的时候,会发送报错,修改方式如下:
buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://facebook.github.io/react-native/docs/signed-apk-android. //PRODUCTION_KEY buildConfigField "String", "CODEPUSH_KEY", '"mcNDEqhhKH2fvm_NNVSzwC1JVPgpaSLFU9WAj"' signingConfig signingConfigs.release minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } releaseStaging { minifyEnabled enableProguardInReleaseBuilds signingConfig signingConfigs.release proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" //STAGING_KEY buildConfigField "String", "CODEPUSH_KEY", '"4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_"' matchingFallbacks = ['release', 'debug'] } }
- signingConfigs {
- debug {
- storeFile file('debug.keystore')
- storePassword 'android'
- keyAlias 'androiddebugkey'
- keyPassword 'android'
- }
- release {
- storeFile file(STORE_FILE)
- storePassword STORE_FILE_PASSWORD
- keyAlias KEY_ALIAS
- keyPassword KEY_ALIAS_PASSWORD
- }
- }
- // android/gradle.properties
- STORE_FILE=./keystore/CodePushDemo.keystore
- KEY_ALIAS=shudan-alias
- STORE_FILE_PASSWORD=123456
- KEY_ALIAS_PASSWORD=123456
此时,如果没有修改 res/values/strings.xml 中的内容,那么依旧是 staging key 。
打包 release 包,命令如下:
E:\ReactLove\rn_hello\rnCodePushDemo_060\android>gradlew assembleRelease
然后安装到用户手机上,之后,修改 App.js 内容,再次将 bundle 文件推送到 CodePush 平台,执行的命令如下:
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android
因为默认情况下是推送到 Staging 环境中的,因此此时 CodePush 最新的版本是在Staging环境中的,而用户手机上此时App的也是 Staging key,那么CodePush 服务器和 App 上的key 就能对的上,那么就能收到最新版本的提示,就能热更新。
如此说来,前面在 app/build.gradle 文件中定义的 buildConfigField "String", "CODEPUSH_KEY" 好像是没有用到,全靠的是 res/values/strings.xml 中定义的
<string name="CodePushDeploymentKey">4O616rtg7BGZTaH_ynL0QIZynLjIzQvd0WNn_</string>
这个内容决定了手机App上使用的是那种类型的key, 如果这里采用了Production Key,那么在打包资源和推送到 CodePush 服务器的时候,命令行需要改成如下:
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android -d Production
-d : 表示指定上传的环境,Production 是生产环境,Staging 是开发环境。
结论:关于如何部署最新的版本的问题就看用户手机此时App中的是那种 Key, 此种 key 是配置在
app/res/values/strings.xml 中的 CodePushDeploymentKey 中。
1. 如果是 Staging Key, 那么打包推送的命令是:
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android -d Staging
2. 如果是 Production Key, 那么打包推送的命令是:
E:\ReactLove\rn_hello\rnCodePushDemo_060>code-push release-react CodePushDemo android -d Production
10. 自己部署CodePush服务器
如果不想用微软提供的CodePush服务器来做热更新,那么可以考虑自己搭建环境,具体资料将来自己搞的时候再去查吧,暂时到此为止,知道如何将新的 bundle 文件更新到CodePush 服务器上即可。
11. 最后一个问题
既然是更新的javascript 的内容,那么如果新的内容如果是加载本地的图片资源,那么是否会一起打包到 bundle 文件中呢?
答案是可以的,可以将图片资源一块进行打包到bundle文件中,然后进行热更新。测试代码如下:
- import React from 'react';
- import {
- View,
- Text,
- Image,
- StyleSheet,
- Dimensions
- } from 'react-native';
-
- import CodePush from 'react-native-code-push';
-
- const codePushOptions = {
- checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
- updateDialog: true, //发现可更新时是否显示对话框
- installMode: CodePush.InstallMode.IMMEDIATE,
- };
-
- class App extends React.Component {
-
- render() {
- return (
- <View style={styles.container}>
- <Image style={styles.image} resizeMode={"contain"} source={require("./images/laptop_phone_howitworks.png")}/>
- <Image style={{width: 50, height: 100}} resizeMode={"contain"} source={require("./images/laptop_phone_howitworks.png")}/>
- <Image style={styles.image} resizeMode={"contain"} source={{uri:"https://sample-videos.com/img/Sample-jpg-image-50kb.jpg"}}/>
- <Text>版本号1.4</Text>
- <Text>我是1.4版本,测试本地Staging 是否能够匹配服务器上的Production</Text>
- </View>
- );
- }
-
- update = () => {
- CodePush.sync({
- // 安装模式
- // ON_NEXT_RESUME 下次恢复到前台时
- // ON_NEXT_RESTART 下一次重启时
- // IMMEDIATE 马上更新
- installMode: CodePush.InstallMode.IMMEDIATE,
- // 对话框 如果打包和上传没有设置强制更新的话,那么默认就是非强制更新,显示的都是非强制更新配置的内容
- updateDialog: {
- // 是否显示更新描述
- appendReleaseDescription: true,
- // 更新描述的前缀。 默认为"Description"
- descriptionPrefix: '更新内容:',
- // 强制更新按钮文字,默认为continue
- mandatoryContinueButtonLabel: '立即更新',
- // 强制更新时的信息. 默认为"An update is available that must be installed."
- mandatoryUpdateMessage: '必须更新后才能使用',
- // 非强制更新时,按钮文字,默认为"ignore"
- optionalIgnoreButtonLabel: '稍后',
- // 非强制更新时,确认按钮文字. 默认为"Install"
- optionalInstallButtonLabel: '后台更新',
- // 非强制更新时,检查到更新的消息文本
- optionalUpdateMessage: '有新版本了,是否更新?',
- // Alert窗口的标题
- title: '更新提示',
- },
- }).then(r => {
-
- });
- };
-
- async componentWillMount() {
- CodePush.disallowRestart(); //禁止重启
- this.update();//开始检查更新
- }
-
- componentDidMount() {
- CodePush.allowRestart();//加载完了,允许重启
- }
- }
-
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: "center",
- backgroundColor: "#F5FCFF",
- paddingTop: 50
- },
- image: {
- margin: 30,
- width: Dimensions.get("window").width - 100,
- //height: 365 * (Dimensions.get("window").width - 100) / 651,
- height:100
- }
- });
-
- App = CodePush(codePushOptions)(App);
- export default App;
注意,在第一次学习的时候,发现图片竟然加载不进来,就是怎么也不显示,最后发现是自己写法有问题:
- <Image style={styles.image} resizeMode={"contain"} soure={require("./images/laptop_phone_howitworks.png")} />
-
- <Image soure={{uri: "https://sample-videos.com/img/Sample-jpg-image-50kb.jpg"}} style={styles.image} resizeMode={"contain"}/>
发现没有,是source 写成了 soure,结果自然就不会显示,但是奇葩是尽然不报错,太坑爹,抱着这个问题查了两天。
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。