问题背景

javascript是一门高度灵活的语言,尤其是其Promise的异步编程模式,是其它语言争相学习的两大异步编程方法之一(另一个是Go语言的协程)。 抛开弱类型这个亦优亦缺的特点不谈,非空检查无疑是javascript里面为数不多的丑陋代码了:

//最简的写法,如果调用链中有一环为空,会departmentName的类型会是什么?
const departmentName = staff&&staff.department&&staff.department.name;
// 比较靠谱点的写法
const departmentName = staff?staff.department?staff.department.name:null:null;

解决方案

作为一门广受欢迎的语言,怎么能没有像Swift和Kotlin这样优秀的空值安全语法呢?

其实js的空值安全语法 可选链式调用"?."空值合并"??",已经做为ECMAScript标准提案被接纳了进ecma262这个标准:

// 可选链式调用?. +++++++++++++++++++++++++++++++++++++++++++++++++
const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);
// Alice只有猫而并没有狗,因此期望输出: undefined

console.log(adventurer.someNonExistentMethod?.());
// 方法someNonExistentMethod并不存在,所以期望输出 output: undefined
// ---------------------------------------------------------------

// 空值合并??,相当于a!=null?a:b的缩写++++++++++++++++++++++++++++++
const foo = null ?? "默认字符串";
console.log(foo);
// 期望输出: "默认字符串"

const baz = 0 ?? 42;
console.log(baz);
// 期望输出: 0
// 注意这里的行为与 a?a:b有一点差异,即0为非空值,更近似a!=null?a:b
// --------------------------------------------------------------

// 组合可选链式调用与空值合并++++++++++++++++++++++++++++++++++++++
const customer = {
  name: "Li Lei",
  details: {
    age: 82,
    location: "Beijin"
	// details 的 address 属性并未定义
  }
};
const customerCity = customer.details?.address?.city ?? "Shanghai";
console.log(customerCity);
// 期望输出:"Shanghai"

// 和函数调用一起使用
function defaultFromCity(){
  console.log("我被逼供了!");
  return "打死你我也不说!";
};
let duration = customer.details?.whereAreYouFrom?.()??defaultFromCity();
// 期望输出:"我被逼供了!"
//          "打死你我也不说!"
customer.details.whereAreYouFrom=()=>"北京";
console.log(customer.details?.whereAreYouFrom?.()??defaultFromCity());
// 期望输出:"北京"
// defaultFromCity并不会被调用,因此未输出"我被逼供了!"
// ---------------------------------------------------------------

虽然还在提案阶段,但是"?."与"??"已经被除IE以外的所有PC端浏览器和大部分移动端浏览器(包括Android和iOS默认浏览器、WebView)所支持。

在React工程中使用

考虑到浏览器兼容性,最好把代码编译为较早版本的js代码,好在Babel或Typescript这两个最优秀的js编译框架,已经双双支持了"?."和"??"。如果要在React或Vue工程中使用,我们只需要引入Babel的插件就可以

  1. 安装babel插件依赖:
# 安装支持"?."语法的插件
yarn add -D @babel/plugin-proposal-optional-chaining
# 安装支持"??"语法的插件
yarn add -D @babel/plugin-proposal-nullish-coalescing-operator
  1. 配置babel插件
// 在工程根目录下的babel.config.js文件中引入插件
module.exports = {
  "plugins": [
    "@babel/plugin-proposal-nullish-coalescing-operator", //支持??
    "@babel/plugin-proposal-optional-chaining",           //支持?.
  ],
};
// 注意没有babel.config.js则创建此文件,有则添加"plugins":[]中的内容

如果是Vue工程,或者React工程,且package.json里面"scripts"是通过react-scripts来运行start、build等命令,则到此已经配置完成,我们可以开始在js代码和jsx里面使用"?."或"??"操作符了:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
    const a = {};
    //试试注释下面这行看看效果
    a["b"] = {c: "Hello!"};
    return (
      <div className="App">
        <button type="primary">{a["b"]?.c ?? "Ops!"}</button>;
      </div>;
    );
}

如果package.json里面"scripts"使用craco来运行start、build等命令(Ant Design模引用less时的推荐),配置过程也与上面完全一致。

如果使用了react-app-rewired

如果package.json里面"scripts"使用react-app-rewired来运行start、builder等命令,就需要在react-app-rewired的配置文件config-overrides中添加useBabelRc()的override:

// config-overrides.js
const { override, useBabelRc } = require('customize-cra');
// 配合 react-app-rewired 插件,用来覆盖内置的 webpack 配置
module.exports = override(
  // 原来的其它配置
  // ...
  // 新增:支持babel配置文件+++++++++
  useBabelRc()
  // ------------------------------
);

当然也可以直接使用react-app-rewired的addBabelPlugin函数代替babel配置文件("2.配置babel插件"替换为以下配置):

// config-overrides.js
const { override, fixBabelImports, addBabelPlugin } = require('customize-cra');

// 配合 react-app-rewired 插件,用来覆盖内置的 webpack 配置
module.exports = override(
  // 动态引入 antd 的 css 样式文件
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  addBabelPlugin("@babel/plugin-proposal-optional-chaining"), //支持?.
  addBabelPlugin("@babel/plugin-proposal-nullish-coalescing-operator") //支持??
  // -------------------------------------------------------------------
);

参考

05-12 07:50