我正在尝试对嵌套对象的数组进行排序。它正在使用静态选择的键,但我不知道如何动态获取它。
到目前为止,我已经有了这段代码
sortBy = (isReverse=false) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
const valueA = (((a || {})['general'] || {})['fileID']) || '';
const valueB = (((b || {})['general'] || {})['fileID']) || '';
if(isReverse) return valueB.localeCompare(valueA);
return valueA.localeCompare(valueB);
})
}));
}
这时,键是硬编码的['general']['orderID']
,但我希望通过向keys
函数中添加sortBy
参数来使该部分具有动态性:
sortBy = (keys, isReverse=false) => { ...
keys
是带有嵌套键的数组。对于上面的示例,它将为['general', 'fileID']
。
要使其动态化,需要采取哪些步骤?
注意:子对象可以是未定义的,因此我正在使用a || {}
注2:我正在使用es6。没有外部软件包。
答案 0 :(得分:4)
您可以循环遍历所有键以获取值,然后像进行比较
sortBy = (keys, isReverse=false) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
const clonedKey = [...keys];
let valueA = a;
let valueB = b
while(clonedKey.length > 0) {
const key = clonedKey.shift();
valueA = (valueA || {})[key];
valueB = (valueB || {})[key];
}
valueA = valueA || '';
valueB = valueB || '';
if(isReverse) return valueB.localeCompare(valueA);
return valueA.localeCompare(valueB);
})
}));
}
答案 1 :(得分:3)
除putting bugs in your code外,当前接受的答案对您没有多大帮助。使用简单的功能deepProp
将减轻痛苦的重复-
const deepProp = (o = {}, props = []) =>
props.reduce((acc = {}, p) => acc[p], o)
现在没有太多噪音-
sortBy = (keys, isReverse = false) =>
this.setState ({
files: // without mutating the previous state!
[...this.state.files].sort((a,b) => {
const valueA = deepProp(a, keys) || ''
const valueB = deepProp(b, keys) || ''
return isReverse
? valueA.localeCompare(valueB)
: valueB.localeCompare(valueA)
})
})
尽管如此,这实际上并没有改善程序。它充满了复杂性,更糟的是,这种复杂性将在需要类似功能的任何组件中重复出现。 React包含功能样式,因此此答案从功能角度解决了问题。在这篇文章中,我们将sortBy
写为-
sortBy = (comparator = asc) =>
this.setState
( { files:
isort
( contramap
( comparator
, generalFileId
)
, this.state.files
)
}
)
您的问题使我们学习了两个强大的功能概念;我们将用它们来回答问题-
不过,我们不要被术语淹没,而应该专注于获得事物运作方式的直觉。起初,看起来我们在检查null时遇到问题。必须处理某些输入可能没有嵌套属性的可能性,使我们的函数混乱。如果我们可以概括这个可能值的概念,那么我们可以进行一些整理。
您的问题专门说您现在不使用外部程序包,但这是一个很好的机会。让我们简要介绍一下data.maybe
软件包-
用于可能不存在的值或可能失败的计算的结构。
Maybe(a)
显式建模Nullable
类型中隐含的效果,因此不存在与使用null
或undefined
相关的问题-像NullPointerException
或{{1 }}。
听起来很合适。我们将首先编写一个函数TypeError
,该函数接受一个对象和一个属性字符串作为输入。直观地讲,safeProp
安全返回对象safeProp
-
p
o
我们将返回一个也许,它指导我们处理结果,而不是简单地返回{em>可能为为空或未定义的值的const { Nothing, fromNullable } =
require ('data.maybe')
const safeProp = (o = {}, p = '') =>
// if o is an object
Object (o) === o
// access property p on object o, wrapping the result in a Maybe
? fromNullable (o[p])
// otherwise o is not an object, return Nothing
: Nothing ()
-
o[p]
现在我们有了一个函数,可以获取复杂程度各异的对象,并保证我们感兴趣的结果-
const generalFileId = (o = {}) =>
// access the general property
safeProp (o, 'general')
// if it exists, access the fileId property on the child
.chain (child => safeProp (child, 'fileId'))
// get the result if valid, otherwise return empty string
.getOrElse ('')
那是战斗的一半。现在,我们可以从复杂的对象转到要用于比较目的的精确字符串值。
在这里我特意避免向您展示console .log
( generalFileId ({ general: { fileId: 'a' } }) // 'a'
, generalFileId ({ general: { fileId: 'b' } }) // 'b'
, generalFileId ({ general: 'x' }) // ''
, generalFileId ({ a: 'x '}) // ''
, generalFileId ({ general: { err: 'x' } }) // ''
, generalFileId ({}) // ''
)
的实现,因为这本身就是很有价值的一课。当模块承诺功能 X 时,我们假定我们具有功能 X ,而忽略模块黑盒中发生的情况。数据抽象的重点是将隐患隐藏起来,以便程序员可以在更高层次上思考问题。
询问数组如何工作可能会有所帮助?从数组中添加或删除元素时,它如何计算或调整Maybe
属性? length
或map
函数如何产生一个 new 数组?如果您以前从未想过这些事情,那没关系!数组是一个方便的模块,因为它使程序员无需担心这些问题。它只是按广告起作用。
无论模块是由JavaScript提供还是由第三方(例如npm)提供,还是您自己编写模块,都适用。如果Array不存在,则可以使用等效的便利将其实现为我们自己的数据结构。我们模块的用户可以 获得有用的功能,而不会增加额外的复杂性。当意识到程序员是他/她自己的用户时,就发生了错觉:当您遇到棘手的问题时,编写一个模块以使自己摆脱复杂的束缚。 Invent your own convenience!
我们稍后会在答案中展示Maybe的基本实现,但现在,我们只需要完成排序即可。
我们从两个基本比较器开始,filter
用于升序排序,asc
用于降序排序-
desc
在React中,我们不能改变先前的状态,相反,我们必须创建 new 状态。因此,要对不可变进行排序,我们必须实现const asc = (a, b) =>
a .localeCompare (b)
const desc = (a, b) =>
asc (a, b) * -1
,它不会使输入对象发生突变-
isort
当然,const isort = (compare = asc, xs = []) =>
xs
.slice (0) // clone
.sort (compare) // then sort
和a
有时是复杂的对象,因此,不能直接调用b
或asc
。下面,desc
将使用一个函数contramap
转换我们的数据,在将数据传递给另一个函数之前,g
-
f
使用另一个比较器const contramap = (f, g) =>
(a, b) => f (g (a), g (b))
const files =
[ { general: { fileId: 'e' } }
, { general: { fileId: 'b' } }
, { general: { fileId: 'd' } }
, { general: { fileId: 'c' } }
, { general: { fileId: 'a' } }
]
isort
( contramap (asc, generalFileId) // ascending comparator
, files
)
// [ { general: { fileId: 'a' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'e' } }
// ]
,我们可以看到另一个方向的排序工作-
desc
现在为您的React组件isort
( contramap (desc, generalFileId) // descending comparator
, files
)
// [ { general: { fileId: 'e' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'a' } }
// ]
编写方法。该方法实质上简化为sortBy
,其中this.setState({ files: t (this.state.files) })
是程序状态的不变转换。这样做很好,因为复杂度可以在难以测试的组件中保持在 之外,而可以驻留在易于测试的通用模块中-
t
这使用了像您原始问题中那样的布尔开关,但是由于React包含功能模式,所以我认为作为高阶函数会更好-
sortBy = (reverse = true) =>
this.setState
( { files:
isort
( contramap
( reverse ? desc : asc
, generalFileId
)
, this.state.files
)
}
)
如果不能保证您需要访问的嵌套属性为sortBy = (comparator = asc) =>
this.setState
( { files:
isort
( contramap
( comparator
, generalFileId
)
, this.state.files
)
}
)
和general
,我们可以制作一个泛型函数来接受属性的 list 并可以查找任何深度的嵌套属性-
fileId
上面,我们使用const deepProp = (o = {}, props = []) =>
props .reduce
( (acc, p) => // for each p, safely lookup p on child
acc .chain (child => safeProp (child, p))
, fromNullable (o) // init with Maybe o
)
const generalFileId = (o = {}) =>
deepProp (o, [ 'general', 'fileId' ]) // using deepProp
.getOrElse ('')
const fooBarQux = (o = {}) =>
deepProp (o, [ 'foo', 'bar', 'qux' ]) // any number of nested props
.getOrElse (0) // customizable default
console.log
( generalFileId ({ general: { fileId: 'a' } } ) // 'a'
, generalFileId ({}) // ''
, fooBarQux ({ foo: { bar: { qux: 1 } } } ) // 1
, fooBarQux ({ foo: { bar: 2 } }) // 0
, fooBarQux ({}) // 0
)
软件包,该软件包使我们能够使用潜在值。该模块导出函数以将普通值转换为Maybe,反之亦然,以及导出许多适用于潜在值的有用操作。但是,没有什么强迫您使用此特定实现。这个概念很简单,您可以在几十行中实现data.maybe
,fromNullable
和Just
,我们将在此答案的后面部分进行介绍-
在repl.it上运行下面的完整演示
Nothing
这种方法的优点应该显而易见。代替了一个很难编写,阅读和测试的大型复杂函数,我们结合了几个更易于编写,阅读和测试的较小函数。较小的函数具有在程序的其他部分中使用的附加优点,而较大的复杂函数可能仅在一部分中可用。
最后,const { Just, Nothing, fromNullable } =
require ('data.maybe')
const safeProp = (o = {}, p = '') =>
Object (o) === o
? fromNullable (o[p])
: Nothing ()
const generalFileId = (o = {}) =>
safeProp (o, 'general')
.chain (child => safeProp (child, 'fileId'))
.getOrElse ('')
// ----------------------------------------------
const asc = (a, b) =>
a .localeCompare (b)
const desc = (a, b) =>
asc (a, b) * -1
const contramap = (f, g) =>
(a, b) => f (g (a), g (b))
const isort = (compare = asc, xs = []) =>
xs
.slice (0)
.sort (compare)
// ----------------------------------------------
const files =
[ { general: { fileId: 'e' } }
, { general: { fileId: 'b' } }
, { general: { fileId: 'd' } }
, { general: { fileId: 'c' } }
, { general: { fileId: 'a' } }
]
isort
( contramap (asc, generalFileId)
, files
)
// [ { general: { fileId: 'a' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'e' } }
// ]
被实现为高阶函数,这意味着我们不仅限于由sortBy
布尔值切换的升序和降序排序;可以使用任何有效的比较器。这意味着我们甚至可以编写一个专门的比较器,使用自定义逻辑处理平局决胜局,或者先比较reverse
,然后比较year
,然后比较month
,等等;高阶函数极大地扩展了您的可能性。
我不喜欢虚空承诺,因此我想告诉你,设计自己的机制day
并不困难。这也是数据抽象的不错的一课,因为它向我们展示了模块如何具有自己的关注点。模块的导出值是访问模块功能的唯一方法。该模块的所有其他组件都是私有的,并且可以根据其他要求自由更改或重构-
Maybe
然后我们将在模块中使用它。我们只需要更改导入(// Maybe.js
const None =
Symbol ()
class Maybe
{ constructor (v)
{ this.value = v }
chain (f)
{ return this.value == None ? this : f (this.value) }
getOrElse (v)
{ return this.value === None ? v : this.value }
}
const Nothing = () =>
new Maybe (None)
const Just = v =>
new Maybe (v)
const fromNullable = v =>
v == null
? Nothing ()
: Just (v)
module.exports =
{ Just, Nothing, fromNullable } // note the class is hidden from the user
),但其他一切都照常进行,因为我们的模块的公共API与-
require
有关如何使用对比度图的更多直觉,也许还有一些意想不到的惊喜,请浏览以下相关答案-
答案 2 :(得分:2)
您可以使用循环从对象中提取嵌套的属性路径:
const obj = {
a: {
b: {
c: 3
}
}
}
const keys = ['a', 'b', 'c']
let value = obj;
for (const key of keys) {
if (!value) break; // stop once we reach a falsy value. Optionally you can make this a tighter check accounting for objects only
value = value[key];
}
console.log(`c=${value}`);
然后,您可以将上面的函数包装到一个帮助器中:
function getPath(obj, keys) {
let value = obj;
for (const key of keys) {
if (!value) break; // stop once we reach a falsy value. Optionally you can make this a tighter check accounting for objects only
value = value[key];
}
return value;
}
并在获取值时使用它:
sortBy = (isReverse = false, keys = []) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
const valueA = getPath(a, keys) || '';
const valueB = getPath(b, keys) || '';
// ...
})
}));
}
答案 3 :(得分:2)
一种方法是在新的keys
参数上使用reduce(),如下所示:
sortBy = (keys, isReverse=false) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
const valueA = (keys.reduce((acc, key) => (acc || {})[key], a) || '').toString();
const valueA = (keys.reduce((acc, key) => (acc || {})[key], b) || '').toString();
if (isReverse) return valueB.localeCompare(valueA);
return valueA.localeCompare(valueB);
})
}));
}
答案 4 :(得分:1)
要使用任意数量的键,您可以创建一个可以与.reduce()
重用的函数,以深入遍历嵌套对象。我还将密钥作为最后一个参数,以便可以使用“ rest”和“ spread”语法。
const getKey = (o, k) => (o || {})[k];
const sorter = (isReverse, ...keys) => (a, b) => {
const valueA = keys.reduce(getKey, a) || '';
const valueB = keys.reduce(getKey, b) || '';
if (isReverse) return valueB.localeCompare(valueA);
return valueA.localeCompare(valueB);
};
const sortBy = (isReverse = false, ...keys) => {
this.setState(prevState => ({
files: prevState.files.sort(sorter(isReverse, ...keys))
}));
}
我还将sort函数移到了自己的const
变量中,并使其返回了一个使用isReverse
值的新函数。
答案 5 :(得分:0)
按以下方式比较排序函数中的元素:
let v= c => keys.reduce((o,k) => o[k]||'',c)
return (isReverse ? -1 : 1) * v(a).localeCompare(v(b));
赞一下:
sortBy = (keys, isReverse=false) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
let v=c=>keys.reduce((o,k) => o[k]||'',c)
return (isReverse ? -1 : 1)*v(a).localeCompare(v(b));
})
}));
}
以下是此想法如何工作的示例:
let files = [
{ general: { fileID: "3"}},
{ general: { fileID: "1"}},
{ general: { fileID: "2"}},
{ general: { }}
];
function sortBy(keys, arr, isReverse=false) {
arr.sort((a,b,v=c=>keys.reduce((o,k) => o[k]||'',c)) =>
(isReverse ? -1 : 1)*v(a).localeCompare(v(b)) )
}
sortBy(['general', 'fileID'],files,true);
console.log(files);
答案 6 :(得分:0)
这也处理路径通过将其转换为字符串解析为非字符串值的情况。否则.localeCompare
可能会失败。
sortBy = (keys, isReverse=false) => {
this.setState(prevState => ({
files: prevState.files.sort((a, b) => {
const valueA = getValueAtPath(a, keys);
const valueB = getValueAtPath(b, keys);
if(isReverse) return valueB.localeCompare(valueA);
return valueA.localeCompare(valueB);
})
}));
}
function getValueAtPath(file, path) {
let value = file;
let keys = [...path]; // preserve the original path array
while(value && keys.length) {
let key = keys.shift();
value = value[key];
}
return (value || '').toString();
}