问题背景
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的插件就可以
- 安装babel插件依赖:
# 安装支持"?."语法的插件
yarn add -D @babel/plugin-proposal-optional-chaining
# 安装支持"??"语法的插件
yarn add -D @babel/plugin-proposal-nullish-coalescing-operator
- 配置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") //支持??
// -------------------------------------------------------------------
);