我有一个数组,需要添加/删除元素,我想使用Set
来完成这个操作,因为它具有add
、has
和delete
这些方法。
const [tags, setTags] = React.useState(new Set())
如果我想向tags
添加一些内容,我该如何使用setTags
来实现呢?还是说我只需调用tags.add()
即可?
如果我想对tags
进行添加操作,应该使用tags.add()
而不是setTags
。
我有一个数组,需要添加/删除元素,我想使用Set
来完成这个操作,因为它具有add
、has
和delete
这些方法。
const [tags, setTags] = React.useState(new Set())
如果我想向tags
添加一些内容,我该如何使用setTags
来实现呢?还是说我只需调用tags.add()
即可?
如果我想对tags
进行添加操作,应该使用tags.add()
而不是setTags
。
Set
是可变的,如果你仅仅调用 const newSet = set.add(0)
,React 不会触发新的渲染,因为先前和现在进行浅比较时总是会断言为 true
你可以使用 spread
运算符,在每次更新之间改变引用,同时仍然保留 Set 的所有行为
添加元素
const [state, setState] = useState(new Set())
const addFoo = foo =>{
setState(previousState => new Set([...previousState, foo]))
}
您仍然可以使用add
方法,因为它返回更新后的集合
const addFoo = foo =>{
setState(prev => new Set(prev.add(foo)))
}
移除元素
移除
操作要稍微复杂一些。首先你需要将其转换成数组,然后使用filter
函数并展开结果。
const removeFoo = foo =>{
setState(prev => new Set([...prev].filter(x => x !== foo)))
}
为了清晰明了
const removeFoo = foo =>{
setState(prev =>{
return prev.filter(x => x !== foo)
})
}
const removeFoo = foo =>{
setState(prev => {
prev.delete(foo);
return new Set(prev);
})
}
removeFoo
函数中使用Set
内部的spread/filter?为什么不使用delete
方法?const removeFoo = foo => setState(prev => new Set([...prev.delete(foo)]));
- WebBrotherconst addFoo = foo => setState(prev => (new Set(prev)).add(foo))
和
const removeFoo = foo => setState(prev => (new Set(prev)).delete(foo))
- Dustin Gaudetnew Set(prev).delete(foo)
- Set.add()
的返回值是一个 Set,但 Set.delete()
的返回值是一个布尔值。 - nickdos你需要创建一个新的集合,否则React将不知道它需要重新渲染。类似以下内容将起作用。
setTags(tags => new Set(tags).add(tag))
我按照以下方法进行,我的React Native组件运行得非常好。
const DummyComponent = () => {
const [stateUserIds, setStateUseIds] = useState(new Set());
....
....
const handleUserSelected = user => {
// Since we cannot mutate the state value directly better to instantiate new state with the values of the state
const userIds = new Set(stateUserIds);
if (userIds.has(user.userId)) {
userIds.delete(user.userId);
} else {
userIds.add(user.userId);
}
setStateUseIds(userIds);
};
....
....
return (
<View>
<FlatList
data={dummyUsers}
renderItem={({item, index}) => {
const selected = stateUserIds.has(item.userId);
return (
<View style={{flex: 2}}>
<Switch
isSelected={selected}
toggleSwitch={() => handleUserSelected(item)}
/>
</View>
);
}}
keyExtractor={(item, index) => item.userId.toString()}
/>
</View>)
}
希望这篇文章能够帮助到有相同使用场景的人。请参考下面的Code Sandbox示例。const useSetState = (initial) => {
const [set, setSet] = useState(new Set(...initial))
return {
add: el =>
setSet((set) => {
if (set.has(el)) return set
set.add(el)
return new Set(set)
}),
delete: el => {
setSet((set) => {
if (!set.has(el)) return set
set.delete(el)
return new Set(set)
})
},
has: el => set.has(el),
clear: () => setSet(new Set()),
[Symbol.iterator]: () => set.values(),
forEach: (fn) => set.forEach(fn),
keys: () => set.keys(),
values: () => set.values(),
get size() {
return set.size
}
}
}
const set = useSetState([])
,并调用 set.add(el)
,set.delete(el)
等方法。我个人发现所有这些解决方案在每次修改时都重新创建集合,这常常是低效的。我编写了下面的钩子来解决这个问题,我认为使用一个任意状态触发重新渲染要比在每次更改时重新创建集合更加优雅:
function useSet<T>(initialValue?: T[]) {
// a peice of state to trigger a re-render whenever the set changes
// imo this is less hacky than re-creating the set on every state change
const [_, setInc] = useState(false)
//use a ref for the set instead
const set = useRef(new Set<T>(initialValue))
const add = useCallback(
(item: T) => {
if (set.current.has(item)) return
setInc((prev) => !prev)
set.current.add(item)
},
[setInc],
)
const remove = useCallback(
(item: T) => {
if (!set.current.has(item)) return
setInc((prev) => !prev)
set.current.delete(item)
},
[setInc],
)
return [set.current, add, remove] as const
}
add
和remove
方法并直接改变引用,那么就会出现问题。change
事件:// set
function handleChange(e, name) {
setSet(prv => e.target.checked
? new Set([...prv, name])
: new Set([...prv].filter(v => v != name)));
}
// object
function handleChange(e, name) {
setSet(prv => ({...prv, [name]: e.target.checked}));
}
// array
function handleChange(e, name) {
setSet(prv => e.target.checked
? (prv.includes(text) ? prv : [...prv, text])
: prv.filter(v2 => v2 != v));
}
初始化:
const [set, setSet] = useState(new Set);
const [set, setSet] = useState({});
const [set, setSet] = useState([]);
setSet(prv => new Set([...prv, text]));
setSet(prv => ({...prv, [text]: true}));
setSet(prv => prv.includes(text) ? prv : [...prv, text]);
setSet(prv => new Set([...prv].filter(v2 => v2 != v)));
setSet(prv => ({...prv, [v]: false}));
setSet(prv => prv.filter(v2 => v2 != v));
[...set].map(v => ...);
Object.keys(set).filter(v => set[v]).map(v => ...);
set.map(...);
这是 code sandbox。
在@Peter Gerdes的答案的基础上,这里提供一个 TypeScript 版本的钩子:
import { useState } from 'react';
const useSetState = <T>(initial: T[]) => {
const [set, setSet] = useState<Set<T>>(new Set(initial));
return {
add: (el: T) =>
setSet((set) => {
if (set.has(el)) return set;
set.add(el);
return new Set(set);
}),
delete: (el: T) => {
setSet((set) => {
if (!set.has(el)) return set;
set.delete(el);
return new Set(set);
});
},
has: (el: T) => set.has(el),
clear: () => setSet(new Set()),
[Symbol.iterator]: () => set.values(),
forEach: (callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any) => set.forEach(callbackfn, thisArg),
keys: () => set.keys(),
values: () => set.values(),
get size() {
return set.size;
},
};
};
export default useSetState;
2023年更新
const set = new Set([...])
一样工作Set
对象用法
const Component = ({ ...props }: Props)
const set = useSet([1, 2, 3]);
// ...
}
// @/lib/use-state.ts
import { useMemo, useRef, useState } from "react";
export function useSet<T>(initialValue: T[]) {
const triggerRender = useState(0)[1];
const set = useRef(new Set<T>(initialValue))
return useMemo(() => ({
add(item) {
if (set.current.has(item)) return
set.current.add(item)
triggerRender(i => ++i)
},
delete(item) {
if (!set.current.has(item)) return
set.current.delete(item)
triggerRender(i => ++i)
},
clear() {
if (set.current.size === 0) return
set.current.clear()
triggerRender(i => ++i)
},
has: (item) => set.current.has(item),
keys: () => set.current.keys(),
values: () => set.current.values(),
forEach: (...args) => set.current.forEach(...args),
[Symbol.iterator]: () => set.current.values(),
get size() { return set.current.size },
}) as Set<T>, [])
}
const [count, setCounter] = useState(0);
const [moreStuff, setMoreStuff] = useState();
const setCount = () => {
setCounter(count + 1);
setMoreStuff(...);
};
Set
存储唯一的值,因此,除非你想编写过滤重复值的代码部分,每次创建一个新的Set
都是可以的。 - Emile Bergeron