在快应用中链式取值的正确姿势
开发中,链式取值是非常正常的操作,如:
res.data.goods.list[0].price
但是对于这种操作报出类似于Uncaught TypeError: Cannot read property 'goods' of undefined
这种错误也是再正常不过了,如果说是 res 数据是自己定义,那么可控性会大一些,但是如果这些数据来自于不同端(如前后端),那么这种数据对于我们来说我们都是不可控的,因此为了保证程序能够正常运行下去,我们需要对此校验:
if (res.data.goods.list[0] && res.data.goods.list[0].price) {
// your code
}
如果再精细一点,对于所有都进行校验的话,就会像这样:
if (res && res.data && res.data.goods && res.data.goods.list && res.data.goods.list[0] && res.data.goods.list[0].price){
// your code
}
不敢想象,如果数据的层级再深一点会怎样,这种实现实在是非常不优雅,那么如果优雅地来实现链式取值呢?
方法一:通过函数解析字符串(lodash 的 _.get 方法)
首先,我们需要安装 lodash
npm install lodash --save
然后就可以在 ux 文件中使用_.get 方法了
<template>
<div class="wrapper">
<!-- grandson -->
<text>{{
_get(this, "parent.children[0].grandson[0].name", "default")
}}</text>
<!-- default -->
<text>{{
_get(this, "parent.non_children[0].grandson[0].name", "default")
}}</text>
</div>
</template>
<script>
import _ from "lodash";
export default {
data: {
parent: {
name: "parent",
children: [
{
name: "children",
grandson: [
{
name: "grandson",
},
],
},
],
},
},
onInit() {
console.log(
"use lodash",
_.get(this, "parent.children[0].grandson[0].name", "default")
); //grandson
console.log(
"use lodash",
_.get(this, "parent.non_children[0].grandson[0].name", "default")
); //default
},
//混入_.get方法,使_.get能够在template标签中使用
_get: _.get,
};
</script>
除了直接使用 lodash 的_.get 方法,我们也可以自己封装一个函数来解析字符串
/**
* get.js
*/
export function _get(obj, props, def) {
if (obj == default || obj == null || typeof props !== 'string') return def;
const temp = props.split('.');
const fieldArr = [].concat(temp);
temp.forEach((e, i) => {
if (/^(\w+)\[(\w+)\]$/.test(e)) {
//解析obj[xxx]
const matchs = e.match(/^(\w+)\[(\w+)\]$/);
const field1 = matchs[1];
const field2 = matchs[2];
const index = fieldArr.indexOf(e);
fieldArr.splice(index, 1, field1, field2);
}
})
return fieldArr.reduce((pre, cur) => {
if (pre === default) return pre
const target = pre[cur] || def;
if (target instanceof Array) {
return [].concat(target);
}
if (target instanceof Object) {
return Object.assign({}, target)
}
return target;
}, obj)
}
然后就可以直接在 ux 文件中使用了
<template>
<div class="wrapper">
<!-- grandson -->
<text>{{
_get(this, "parent.children[0].grandson[0].name", "default")
}}</text>
<!-- default -->
<text>{{
_get(this, "parent.non_children[0].grandson[0].name", "default")
}}</text>
</div>
</template>
<script>
import { _get } from "path to get.js";
export default {
private: {
parent: {
name: "parent",
children: [
{
name: "children",
grandson: [
{
name: "grandson",
},
],
},
],
},
},
onInit() {
console.log(
"use get.js file",
_get(this, "parent.children[0].grandson[0].name", "default")
); //grandson
console.log(
"use get.js file",
_get(this, "parent.non_children[0].grandson[0].name", "default")
); //default
},
//混入_get方法,使_get能够在template标签中使用
_get,
};
</script>
方法二:使用 Proxy
除了通过解析字符串的方式读取属性,也可以将源对象加一层代理,在 handler 中对读取操作做一些处理
/**
* get.js
*/
export function _get(obj, path = []) {
return new Proxy(() => { }, {
get(target, property) {
return _get(obj, path.concat(property))
},
apply(target, self, args) {
let val = obj;
for (let i = 0; i < path.length; i++) {
if (val === null || val === default) break;
val = val[path[i]]
}
if (val === null || val === default) {
val = args[0]
}
return val;
}
})
}
<template>
<div class="wrapper">
<!-- grandson -->
<text>{{ _get(parent).children[0].grandson[0].name("default") }}</text>
<!-- default -->
<text>{{ _get(parent).non_children[0].grandson[0].name("default") }}</text>
</div>
</template>
<script>
import { _get } from "path to get.js";
export default {
private: {
parent: {
name: "parent",
children: [
{
name: "children",
grandson: [
{
name: "grandson",
},
],
},
],
},
},
onInit() {
console.log(
"use proxy",
_get(this.parent).children[0].grandson[0].name("default")
); //grandson
console.log(
"use proxy",
_get(this.parent).non_children[0].grandson[0].name("default")
); //default
},
//混入_get方法,使_get能够在template标签中使用
_get,
};
</script>
注意,不管是否传入默认值,末尾都要加上()
作为函数调用,以触发apply
捕捉器
方法三:可选链
第三种方式是使用 ES 的新语法,这种方式需要借助 babel 来使用。首先检查你的项目依赖中的babel
版本,如果你的 babel 版本<7,那么得先解决 babel 版本升级的问题。如果是 babel7 以上的版本,可以添加以下devDependencies
依赖:
@babel/plugin-proposal-optional-chaining
然后在.babelrc 或者 babel.config.js 中这加入这个插件:
{
"plugins": ["@babel/plugin-proposal-optional-chaining"]
}
之后就可以愉快地使用了!
<template>
<div class="wrapper">
<!-- grandson -->
<text>{{ parent?.children?.[0]?.grandson?.[0]?.name || "default" }}</text>
<!-- default -->
<text>{{
parent?.non_children?.[0]?.grandson?.[0]?.name || "default"
}}</text>
</div>
</template>
<script>
export default {
private: {
parent: {
name: "parent",
children: [
{
name: "children",
grandson: [
{
name: "grandson",
},
],
},
],
},
},
onInit() {
console.log(
"use optional chaining",
this.parent?.children?.[0]?.grandson?.[0]?.name || "default"
); //grandson
console.log(
"use optional chaining",
this.parent?.non_children?.[0]?.grandson?.[0]?.name || "default"
); //default
},
};
</script>
另外,如果使用的是最新版本的快应用开发工具,可以无须配置直接使用可选链的语法
注意:可选链可以直接在 template 标签中使用