js 几种导入依赖排序 import sort

前言

现在前端工程化一般都会加上 eslint + prettier (从零搭建前端工程(下))做格式化,为了团队风格,美观,还有为了减少不必要的冲突,比如 import 文件的顺序。合并代码的时候,冲突无可避免,但是能尽量减少最好。能够统一排序,添加的文件的改动也比较一目了然。相应的工具其实也不少,大同小异,选择适合自己的而用之。

一、eslint 自带的 sort-imports

sort-imports》。不看了,自定义差,还几乎不能 auto fix。

二、eslint 插件 eslint-plugin-import

eslint-plugin-import》。支持自定义配置,支持 auto fix,后面说的都是,不然没啥意义了。示例代码:

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 'latest',
  },
  plugins: ['import'],
  rules: {
    'import/order': [
      'error',
      {
        groups: [
          'index',
          'builtin',
          'external',
          'internal',
          'object',
          'type',
          'unknown',
          ['parent', 'sibling'],
        ],
        pathGroups: [
          {
            pattern: 'react*',
            group: 'builtin',
            position: 'after',
          },
          {
            pattern: '@/*',
            group: 'internal',
            position: 'after',
          },
          {
            pattern: '@*/**',
            group: 'internal',
            position: 'before',
          },
        ],
        pathGroupsExcludedImportTypes: [],
        distinctGroup: false,
        'newlines-between': 'always',
        alphabetize: {
          order: 'asc',
          orderImportKind: 'asc',
          caseInsensitive: true,
        },
        warnOnUnassignedImports: false,
      },
    ],
  },
};

大部分配置都很好理解,就是这个 pathGroupsExcludedImportTypes 实在不明所以。

Issue#2156 有个老哥给出答案:
想要 pathGroups 的配置生效,那么它原本所属的类型就不要出现在 pathGroupsExcludedImportTypes react* 属于 external,而这个属性的默认值是 [‘buildin’, ‘external’],所以配置了 react* 的话,就要重定义这个值。 所以示例代码是空数组。
老哥又提到:But now, it has been applied exactly the opposite way.

囧,确实我理解反了,我以为是要使其生效才要写在里面。而且理解成是 pattern 定义的分类是属于下面 group 的,也就是一开始以为是指定 react* 属于 builtin。而其实不是,而是说 react* 在 builtin 类型的相对位置。= =!

Read More

依赖反转/注入与 IOC 容器 inversify

一、概念

先看看概念释义(百度)- 依赖反转原则:

在面向对象编程领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

我第一个想法就是反转什么?再看看释义(chatgpt):

“反转” 指的是将依赖关系从具体实现转向抽象,这样高层模块就不会直接依赖低层模块的实现细节,而是依赖于抽象接口或抽象类。依赖注入则是实现这一反转原则的一个具体手段,通过将依赖项注入到类中,减少了类与其依赖项之间的耦合。

结合给的例子,虽然是 java 的例子,不过大同小异。

传统实现:

public class UserService {
    private UserRepository userRepository;
    public UserService() {
        this.userRepository = new UserRepository(); // 依赖于具体实现
    }
    public void someServiceMethod() {
        userRepository.someMethod();
    }
}

应用依赖反转原则的实现:

// 定义抽象
public interface UserRepository {
    void someMethod();
}
// 实现具体细节
public class UserRepositoryImpl implements UserRepository {
    public void someMethod() {
        // 实现细节
    }
}
// 高层模块依赖于抽象
public class UserService {
    private UserRepository userRepository;
    // 依赖注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    public void someServiceMethod() {
        userRepository.someMethod();
    }
}

那么依赖注入又是什么,如果接触过 angular 的话,应该会知道这个概念。释义(chatgpt):

依赖注入是一种实现依赖反转的方法,通过将依赖项(如 UserRepository)注入到 UserService 中,而不是由 UserService 自行创建依赖项。依赖注入可以通过多种方式实现,例如:

  • 构造器注入:通过构造函数将依赖项注入类。
  • 属性注入:通过类的属性(setter 方法)注入依赖项。
  • 接口注入:通过实现特定接口注入依赖项(这种方式在实践中不太常见)。

个人理解:依赖反转是一种编程思想,而依赖注入是其原则的一种实现方式。反转了对依赖的使用。

  • 传统:直接在类里面声明使用一个依赖。如果需求上要改动依赖,就得改动调用方,或者重写一个新的调用方,去调用新的一个依赖。
  • 依赖反转:给调用方传递一个依赖,大家约定好依赖要实现某个接口(方法),调用方就直接用。如此一来,如果需要更改依赖方法,就传递一个新的依赖进去就好。调用方的代码就不用改。
    比如:跑自动化测试和正式使用,就可以给某些功能注入不同的 service,来达到适配不同环境。

个人觉得叫依赖注入还好理解点。不过一个是指导思想,一个是实施方案嘛。

Read More

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