javascript 如何使用带有react的useState的Set?

ni65a41a  于 2023-01-19  发布在  Java
关注(0)|答案(8)|浏览(169)

我有一个数组,我必须向其中添加/删除元素,我想我将使用Set来完成此操作,因为它具有addhasdelete

const [tags, setTags] = React.useState(new Set())

如果我想给tags添加一些东西,我怎么用setTags来做呢?或者我只调用tags.add()

6kkfgxo0

6kkfgxo01#

根据定义,Set是可变的,如果您仅仅调用const newSet = set.add(0),React不会触发新的渲染,因为以前的和新的之间的浅层比较将始终Assert为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)))
}

正在删除元素

Removing有点麻烦,首先需要将其转换为数组filter,然后将结果展开

const removeFoo = foo =>{
    setState(prev => new Set([...prev].filter(x => x !== foo)))
}

为清楚起见

const removeFoo = foo =>{ 
    setState(prev =>{
        return prev.filter(x => x !== foo)
    })
}
8zzbczxx

8zzbczxx2#

我采用了以下方法,并且与我的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 example

b1uwtaje

b1uwtaje3#

你必须创建一个新的集合,否则react不会知道它需要重新渲染。

setTags(tags => new Set(tags).add(tag))
e37o9pze

e37o9pze4#

如果你想使用一个集合作为你的保存状态(在集合成员的每次改变时重新渲染),你可以把上面描述的技术打包成一个漂亮的小钩子,返回一个行为(几乎...见下文)像集合的对象。
具体来说,我使用以下自定义钩子:

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)等。
然而,由于这个钩子的实现方式,返回的对象在那个示例中并不是一个集合,Object.prototype.toString等不会将它识别为一个集合(而且,我没有实现forEach的thisArg)。如果你真的关心这个,你可以使用代理来实现同样的逻辑,但是对于大多数应用程序来说,这似乎有点过分。

8fsztsew

8fsztsew5#

我决定尝试使用一个对象/数组来代替一个集合,需要注意的是对象的插入顺序是not always preserved,但是如果你有很多动态生成的复选框,并且你收到一个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

xtfmy6hx

xtfmy6hx6#

除了@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;
b4qexyjb

b4qexyjb7#

我个人发现,所有这些解决方案都是在每次修改时重新创建集合,这通常会产生相反的效果。我编写了下面的钩子来解决这个问题,IMO使用一个任意状态来触发重新渲染比在每次修改时重新创建集合要简单得多:

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
}

这里唯一的缺点是,如果使用者选择绕过提供的addremove方法,直接修改ref。

ttvkxqim

ttvkxqim8#

const [count, setCounter] = useState(0);
const [moreStuff, setMoreStuff] = useState();

const setCount = () => {
    setCounter(count + 1);
    setMoreStuff(...);

};

相关问题