android 原生应用集成 react-native

一、前言

继上次尝试了《ios 原生应用集成 react-native》之后。这次尝试 android 原生应用的集成。android 也分两种语言,kotlin 和 java。这里就尝试 java 就好了,不和 ios 一样尝试两种语言。全屏模式用 ReactRootView 组件,窗口模式使用 Fragment 组件做承载。有没有点眼熟,这个在 web 里代表空标签。vue 少见点, react 应该挺常见的。也有专门的 api,document.createDocumentFragment

这里也不多说,主要是记录下官网《ntegration with Existing Apps》教程里没有的。大部分还是跟着官网走就可以。老样子,也是建立在正常的 react-native android 环境能运行的前提下,才能继续。按照官网《Setting up the development environment》说的,JDK,Android Studio 该装的装好。

还是那句话,app 开发的同学应该一看就懂了,我这还是以一个只懂前端的视角记录一下。

二、全屏集成

创建按钮很显眼,就不用多说。这里说下两个模版,第一行第二个 Empty Activity,据说是新模板,开发语言只有 kotlin。第二行第二个 Empty Views Activity,据说是旧模板,开发语言可以选 kotlin 和 java,这里选 java。至于最下面还有个选择 build 语言的,选择第一个 Kotlin DSL(build.gradle.kts) 就可以了,反正三个我都看了都和官网示例对没完全对上。

1. MyReactActivity.java

创建一份 MyReactActivity.java 文件,如果是新项目的话,会有一份 MainActivity.java,放在一起就好了。

// 记得声明自己应用
package com.example.myapplicationrn;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.KeyEvent;

import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.soloader.SoLoader;

import java.util.List;

public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {

    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SoLoader.init(this, false);

        mReactRootView = new ReactRootView(getApplication());
        List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
        // 有一些第三方可能不能自动链接,对于这些包我们可以用下面的方式手动添加进来:
        // packages.add(new MyReactNativePackage());
        // 同时需要手动把他们添加到`settings.gradle`和 `app/build.gradle`配置文件中。

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setCurrentActivity(this)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackages(packages)
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // 注意这里的MyReactNativeApp 必须对应"index.js"中的
        // "AppRegistry.registerComponent()"的第一个参数
        mReactRootView.startReactApplication(mReactInstanceManager, "reactnativeandroid", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostPause(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostResume(this, this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mReactRootView != null) {
            mReactRootView.unmountReactApplication();
        }
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostDestroy(this);
            mReactInstanceManager.destroy();
        }
    }

    @Override
    public void onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }
}

其实就是官网示例,然后整合成一份了。再把一些依赖 import 写上,不然完全新手看一堆依赖缺失还是挺不好的。

Read More

使用 babel ast 做代码转换

前言

现在对 ast(abstract syntax tree 虚拟语法树)应该也不陌生了。像 vue react 开发所写的非浏览器标准的代码,最后都是转化成符合标准的代码,都是中间靠这个处理。也包括小程序,react-native 这些也是要经过同样处理转换。

ast 三板斧:parse(解析)-> transform(转换)-> generate(生成)。
听起来有点像变形金刚,我在想法术里的变身是不是原理也是这样:实体拆散成原子,原子转换变形,然后再重新组合。哈哈。

ast 工具其实很多,babel 应该是前端接触较多较早的一个,毕竟很长一段时间内都是它在帮着处理前端的编译。babel 对于上面三板斧拆成了三个工具:@babel/parser@babel/traverse@babel/generator
乍看之下,其它两个都对得上,好像 transform 不太对。其实转换规则是自己定的,babel 也不知道使用者要转成啥,所以它提供了一个 traverse 工具,用来方便获取节点。因为 ast 是一串层级深又复杂的对象,很难自己手动找到想要的节点,所以提供了一个遍历工具。

当然有个遍历工具还是不够,最好还是搭配一个很牛网站:https://www.astexplorer.net 可以直接左边输入源代码,右边看结构树。从这个网站里可以看到其实还有很多别的 ast 工具,每个工具都有自己的节点定义。

实践

现在尝试进行一段代码的转换:

// input:
import Input from './Input';
export const Component = (props) => <div><Input onChange={props.onChange} /></div>;

// output:
import { Input } from './Input';
const map = {
  run: () => {}
};
export default function Component(props) {
  return <div><Input onChange={map[props.method]} /></div>;
}

就是,1、把 import default 导出变成解构导出,2、把 export 分散导出变成 default 导出,3、把事件的触发函数变成调用一个事件映射的属性。

Read More

react-native ios 流水账

示例工程:sample project

一、前言

现在跨端的开发采取了前端的 DSL,也变成前端人员必备的开发技能之一了。

当前:

  1. react-native,weex,快应用(严格讲是跨手机)这种使用前端语言,视图部分交由原生渲染,所以 css 都是酌情支持。交互部分交由另外启动的 js 引擎来执行。
  2. 各类小程序这种使用前端语言,视图部分由 webview 渲染,所以大部分 css 都是支持。交互部分交由另外启动的 js 引擎来执行。
  3. fluter,自己整的渲染器和逻辑解析器,开发语言也是自己一套。硬要说也是和小程序一类吧,只不过小程序用的终端落地方案是现成的,fluter 都自己弄新的。

有了跨端解决方案,就有了跨端写法统一解决方案。比如:uni-app taro mpvue,chamelon,就是采用 vue 或者 react 的技术栈,然后可以编译成 rn,小程序等的编译产物。主打一个殊归同途。

前两种其实都是大同小异,都是前端 DSL,只不过是可用的标签和样式不一,一通百通,看看文档就可以了。fluter 都是完全不同的语言,有接触到再说。

二、启动

react-native ios 开发的第一个难点就是搭环境。参考官网《environment-setup》。

里面提到可以用沙盒模式开发,也就是先跳掉一系列的系统环境安装。但是我想这只适合体验一下,如果真的要开发 react-native,还是得配好一系列环境。何况中文官网还说了:

译注:沙盒环境大量依赖于国外网络环境,也不能直接安装第三方原生组件。不建议国内用户使用

所以还是老老实实从 Xcode 安装起吧。其中可能需要科学上网才能安装完,比如这个 CocoaPods,这个 ios 依赖包管理工具,就要下载很久。

ruby 版本

mac 默认的 ruby 版本是 2.6.8,当前 react-native 中用到的 ruby 版本是 2.7.5,需要更新。可以参考网上大佬的文章《React-native初始化项目(Installing Ruby Gems)》。

ios 依赖

在准备好工具后,就会开始初始化项目。会很久,安装完前端的依赖后,会进行 ios 的依赖的安装。这一步可能会有各种失败,比如安装 hermes 引擎就很容易失败。
这时候不用重新创建项目,确保外层的前端 node_modules 安装完后,进入 ios 文件夹执行 pod install,或者在根目录执行 npx pod-install,重试一直到完成。

Read More

ios 原生应用集成 react-native

前言

其实官方文档《Integration with Existing Apps》讲得挺详细,但是有点面向 ios 开发,对于 ios 开发的同学应该很容易看懂吧。不熟悉的我摸索了很久,记录一下。

重要!!!

先参考官网《Setting up the development environment》把 react-native 的开发环境搭建起来,因为基本如出一辙。对于 ios 而言, react-native 像是它的一部分子应用,本质上还是原生应用。
因为就是要用到原生的东西,所以不要用 Expo 这种方式。这个是方便开发用的,最后应该要回到原生来。

能成功启动 react-native,想必也搞定了 CocoaPods 这个麻烦的工具,用来下载原生相关依赖的。

创建 ios app

Xcode Version 14.3 (14E222b)

1、创建一个 ios app

2、填写相关信息

Product Name:顾名思义,这个应用的名字,随便填,自己记住。
Team:没有就先跳过,苹果开发要求的,上线要用到。
Organization Identifier:组织标识,公司或者产品网址倒着写,随便填。
Interface:有 SwiftUI 和 Storyboard 可选,选择 Storyboard。
Language:选 Swift 或 Objective-C 都可以。

3、操作 xcode

上图为例

先点击中间的“分屏按钮”,编辑器就会变成两个编辑区,一个打开 ViewController.m 文件,一个打开 Main 文件。虽然显示是 Main 文件,但硬盘上可以看到是叫 Main.storyboard 文件。

然后点击右边的“组件库按钮”,打开组件库,拖拽一个 button 组件到 storyboard 编辑区,也就是生成一个按钮到应用上。

最后点击左边的“运行按钮”,看到弹出来的手机模拟器上有自己加的 button 就算成功了。

Read More

react 用 vite 构建 & 用 qiankun 做微前端

工程不经常改动,就不建仓库了,弄个压缩包完事。
代码压缩包:micro-qiankun.tar.gz

背景

文章不是为了讲怎么用 react & vite & qiankun 结合搭建。搭建也不复杂,直接看示例代码就可以了。主要是这其中遇到的一个问题:

qiankun.js:17 [import-html-entry]: error occurs while executing normal script <script type=”module”>

详情如图:

记录下这个问题的产生及解决。

介绍

qiankun.js 是一个微前端框架。对于微前端,之前也尝试过《用 webpack ModuleFederationPlugin 搭建微前端》。

两者差别的话:

  1. 如果用 vite,那 webpack5 MF 肯定用不了(废话~)。
  2. 对于 qiankun,是不论框架的,基座和各个子应用可以完全不同框架,因为用的沙箱机制,可以可以云云(详看官网介绍)。
  3. 而 webpack MF 其实也不是为了微前端出来,而是共享一部分功能出来,恰好能用作微前端。最终形式是暴露出一份 js 文件给主应用引用。那就最好基座和子应用都是同框架。

个人理解:

  • 如果是成熟的几个不同项目,要揉在一起,但是彼此关联又不是很大,可以用 qiankun。
  • 如果是本身就是同一个大项目,要拆成不同模块分工维护。有很多共享的数据,可以用 webpack ModuleFederationPlugin。

Read More