Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
182 views
in Technique[技术] by (71.8m points)

javascript - How can you robustly update a store based on another store's value in Svelte?

I'm writing my first app in Svelte today, and I've come across a basic logic issue when trying to create a non-trivial system of stores.

As the question title says, I am struggling with finding a way to orchestrate store updates in which one store is updated in a certain way, dependant upon the current values of another store.

The docs state that its possible to get the state value of a store by using get like so:

import { get } from 'svelte/store';
const value = get(store);

But the docs also state:

Generally, you should read the value of a store by subscribing to it and using the value as it changes over time. Occasionally, you may need to retrieve the value of a store to which you're not subscribed. get allows you to do so. This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.

This makes me wonder if I should avoid this pattern of usage where I grab the state value of the store using "get" without a real subscription, but I am not sure how else I can coordinate updates to multiple stores.


To help explain, I've written a few examples of ways that I think I could do this, but all of these examples don't seem ideal.

In my example, I'm using an app where you have to shoot a target, but the targets HP goes down by the amount of the first type of ammo that is in your revolver's chamber.

The app begins with an input listener.

#############################################################
# Step 1: Create an input listener
#############################################################

const keyboardListener = (() => {
    const subscribers = new Map();
    document.addEventListener('keyup', event => {
        if (event.code === 'Space') {
            [...subscribers].forEach(sub => sub({ eventType: 'fire' }));
        }
    });
    function subscribe(fn) {
        this.subscribers.set(fn, fn);
        return () => {
            this.subscribes.delete(fn);
        };
    }
    return {
        subscribe,
    };
})();

Now lets try to create a store...

#############################################################
# Step 2a: Store architecture pattern 1: The problem
#############################################################

export const target = writable({
    name: 'cow',
    hp: 100,
});

export const ammo = writable([
    {
        type: 'armourPiercing',
        damage: 20,
    },
    {
        type: 'explosive',
        damage: 50,
    },
    {
        type: 'flash',
        damage: 1,
    }
]);

keyboardListener.subscribe(() => {

    // If we have ammo, get the first ammo and reduce the cows HP.
    target.set(currentTargetValue => ({
        ...currentTargetValue,
        hp: currentTargetValue.hp - ammoDamage // Where does ammoDamage come from?
    }));
});

The problem with the store above, is that I'm not sure how to get the value of the ammo store while in the update callback function for the target store.

I've realised that I can overcome this by just having the app store as one, big, monolitic thing. Then I can always get all of its state when doing an update.

#############################################################
# Step 2b: Store architecture pattern 2: One big store
# (So we can access the current value of both stores during
# the update)
#############################################################

export const wholeAppStore = writable({
    target: {
        name: 'cow',
        hp: 100,
    },
    ammo: [
        {
            type: 'armourPiercing',
            damage: 20,
        },
        {
            type: 'explosive',
            damage: 50,
        },
        {
            type: 'flash',
            damage: 1,
        }
    ]
});

keyboardListener.subscribe(() => {

    // If we have ammo, get the first ammo and reduce the cows HP.
    wholeAppStore.set(currentWholeAppStoreValue => {
        const newAmmo = JSON.parse(JSON.stringify(currentWholeAppStoreValue.ammo));
        const firstAmmo = newAmmo.pop();
        const newTarget = JSON.parse(JSON.stringify(currentWholeAppStoreValue.target));
        newTarget.hp -= firstAmmo.damage;
        return {
            target: newTarget,
            ammo: newAmmo,
        };
    });
});

But having one big monolithic app store doesn't seem sensible. Its awkward to write updates for, and I wonder if it is probably not great performance-wise either.

So lets try using the get function.

#############################################################
# Step 2c: Store architecture pattern 3: Use "get"?
#############################################################

export const target = writable({
    name: 'cow',
    hp: 100,
});

export const ammo = writable([
    {
        type: 'armourPiercing',
        damage: 20,
    },
    {
        type: 'explosive',
        damage: 50,
    },
    {
        type: 'flash',
        damage: 1,
    }
]);

keyboardListener.subscribe(() => {

    const ammoValue = get(ammo);
    const firstAmmo = [...ammoValue].pop();
    const ammoDamage = firstAmmo.damage;

    // If we have ammo, get the first ammo and reduce the cows HP.
    target.set(currentTargetValue => ({
        ...currentTargetValue,
        hp: currentTargetValue.hp - ammoDamage;
    }));
});

But I am unsure about this approach due to the messaging in the documentation. Plus, as I have only used get to pluck the value, the ammo store has not been updated to remove the ammo from the revolvers chamber. Maybe we should just do it with two separate update callbacks.

#############################################################
# Step 2d: Store architecture pattern 4:
# Use messy global vars?
#############################################################

export const target = writable({
    name: 'cow',
    hp: 100,
});

export const ammo = writable([
    {
        type: 'armourPiercing',
        damage: 20,
    },
    {
        type: 'explosive',
        damage: 50,
    },
    {
        type: 'flash',
        damage: 1,
    }
]);

keyboardListener.subscribe(() => {

    let firstAmmo; // "Global" var
    ammo.set(currentAmmoValue => {
        const newAmmo = JSON.parse(JSON.stringify(currentAmmoValue));
        firstAmmo = newAmmo.pop();
        return newAmmo;
    });
    const ammoDamage = firstAmmo.damage;

    // If we have ammo, get the first ammo and reduce the cows HP.
    target.set(currentTargetValue => ({
        ...currentTargetValue,
        hp: currentTargetValue.hp - ammoDamage;
    }));
});

This works I think, but it feels hacky.

So you see, I am having trouble finding a good pattern for a larger app store architecture. And of course, this is actually a relatively simple app - a real world app would of course be much more complex.

So I am wondering - are there any established patterns out there for managing large, inter-related stores in Svelte?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
等待大神答复

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...