为什么`slice`方法不能用于数组属性?

huangapple go评论61阅读模式
英文:

Why the slice method doesn't work on array prop?

问题

我有以下定义Pinia存储的代码:

import { ref, computed, shallowRef } from 'vue'
import { defineStore } from 'pinia'

export const usePokemonStore = defineStore('pokemons', () => {
    // 定义pokemons列表状态
    const pokemonsList = ref([]);
    const pokemonsLoaded = ref([]);
    const pokemonsLoadedNames = ref([]);

    // 计算属性
    const pokemonsListLength = computed(() => pokemonsList.value.length);
    const pokemonsLoadedLength = computed(() => pokemonsLoaded.value.length);

    // 动作
    async function getList() {
        const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=100000&offset=0');
        const data = await res.json();

        pokemonsList.value = data["results"];
    }

    async function loadPokemon(name) {
        const URI = `https://pokeapi.co/api/v2/pokemon/${name}`

        const res = await fetch(URI);
        const data = await res.json();

        pokemonsLoadedNames.value.push(data["name"])
        pokemonsLoaded.value.push(data)
    }

    async function loadPokemons(offset, limit){
        // 基本的限制检查
        limit = limit > pokemonsListLength ? pokemonsListLength : limit;
        limit = limit < 0 ? 10 : limit

        // 基本的偏移检查
        offset = offset < 0 ? 0 : offset;
        offset = offset > pokemonsListLength ? 0 : offset

        for (let i = offset; i < offset + limit; i++){

            // 如果已加载了该宝可梦,跳过请求
            if (pokemonsLoadedNames.value.includes(pokemonsList.value[i].name)) {
                continue;
            }

            // 根据名称请求宝可梦
            loadPokemon(pokemonsList.value[i].name)
        }
    }

    return {
        pokemonsList, 
        pokemonsLoaded, 
        pokemonsListLength, 
        
        pokemonsLoadedLength, 
        pokemonsLoadedNames, 
        
        getList,
        loadPokemon,
        loadPokemons
    }
})

我有以下组件,它使用存储来获取宝可梦:

<template>
    <div class="pokedex">
        <PokemonImage class="pokemon-figure" :pokemon="" />
        <ul v-if="pokemonsToShow" class="pokemon-showcase">
            <li class="pokemon-item" v-for="pokemon in pokemonsToShow">
                <PokemonCard :pokemon="pokemon" />
            </li>
        </ul>
        <div class="navigation">
            <button v-show="page !== 1" @click="pageChange(-1)">上一页</button>
            <button @click="pageChange(1)">下一页</button>
        </div>
        {{ page }}
    </div>
</template>

<script setup>
import { onBeforeMount, ref, computed, watch } from 'vue';
import { usePokemonStore } from '../stores/pokemon';
import PokemonCard from '../components/PokemonCard.vue';
import PokemonImage from '../components/PokemonImage.vue';
const pokeStore = usePokemonStore();

const page = ref(1)

const pokemonsToShow = ref([])

// 基于页面计算偏移和限制
const limit = computed(() => 20);
const offset = computed(() => page.value * limit.value - limit.value);

// 初始加载
onBeforeMount(async () => {
    await pokeStore.getList()
    await pokeStore.loadPokemons(0, limit.value)
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(0, pokeStore.pokemonsLoadedLength)
})

const pageChange = async (step) => {
    page.value = page.value + step
    await pokeStore.loadPokemons(offset.value, limit.value)

    const start = offset.value;
    const end = offset.value + limit.value;
    console.log(start, end)
    console.log(pokeStore.pokemonsLoaded)
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(start, end)
    console.log(pokemonsToShow.value)
}
</script>

现在,当用户点击页面按钮时,page.value 被更新,因此偏移和限制的计算值也被更新(实际上只有偏移被更新),这样,如果页面是新的,我可以从那里加载新的宝可梦,通过调用 pokeStore.loadPokemons(offset.value, limit.value) 函数并在 pageChange 函数中等待它。但是现在我想改变 pokemonsToShow,所以我想从存储的已加载宝可梦数组中获取一个切片,但每次我尝试切片该数组时都没有返回任何内容,尽管当我使用 console.log(pokeStore.pokemonsLoaded) 打印数组时,该数组显示为已更新为新值,并且范围是正确的。

我期望数组正确切片,因为如果我在此函数调用中放入静态值:

pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(2, 4)

出奇不意地它可以工作,但不使用动态计算的值。

英文:

I have the following code which defines a Pinia storage:

import { ref, computed, shallowRef } from &#39;vue&#39;
import { defineStore } from &#39;pinia&#39;

export const usePokemonStore = defineStore(&#39;pokemons&#39;, () =&gt; {
    // define the pokemons list state
    const pokemonsList = ref([]);
    const pokemonsLoaded = ref([]);
    const pokemonsLoadedNames = ref([]);

    // computed
    const pokemonsListLength = computed(() =&gt; pokemonsList.value.length)
    const pokemonsLoadedLength = computed(() =&gt; pokemonsLoaded.value.length)

    // actions
    async function getList() {
        const res = await fetch(&#39;https://pokeapi.co/api/v2/pokemon?limit=100000&amp;offset=0&#39;);
        const data = await res.json();

        pokemonsList.value = data[&quot;results&quot;];
    }

    async function loadPokemon(name) {
        const URI = `https://pokeapi.co/api/v2/pokemon/${name}`

        const res = await fetch(URI);
        const data = await res.json();

        pokemonsLoadedNames.value.push(data[&quot;name&quot;])
        pokemonsLoaded.value.push(data)
    }

    async function loadPokemons(offset, limit){
        // basic check for limits
        limit = limit &gt; pokemonsListLength ? pokemonsListLength : limit;
        limit = limit &lt; 0 ? 10 : limit

        // basic check for offset
        offset = offset &lt; 0 ? 0 : offset;
        offset = offset &gt; pokemonsListLength ? 0 : offset

        for (let i = offset; i &lt; offset+limit; i++){

            // if the pokemon is already loaded skips the request for it
            if (pokemonsLoadedNames.value.includes(pokemonsList.value[i].name)) {
                continue;
            }

            // requests the pokemon given a name
            loadPokemon(pokemonsList.value[i].name)
        }
    }

    return {
        pokemonsList, 
        pokemonsLoaded, 
        pokemonsListLength, 
        
        pokemonsLoadedLength, 
        pokemonsLoadedNames, 
        
        getList,
        loadPokemon,
        loadPokemons
    }
})

And I have the following component which makes use of that storage to get the pokemons:

&lt;template&gt;
    &lt;div class=&quot;pokedex&quot;&gt;
        &lt;PokemonImage class=&quot;pokemon-figure&quot; pokemon=&quot;&quot; /&gt;
        &lt;ul v-if=&quot;pokemonsToShow&quot; class=&quot;pokemon-showcase&quot;&gt;
            &lt;li class=&quot;pokemon-item&quot; v-for=&quot;pokemon in pokemonsToShow&quot;&gt;
                &lt;PokemonCard :pokemon=&quot;pokemon&quot; /&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
        &lt;div class=&quot;navigation&quot;&gt;
            &lt;button v-show=&quot;page !== 1&quot; @click=&quot;pageChange(-1)&quot;&gt;Previous Page&lt;/button&gt;
            &lt;button @click=&quot;pageChange(1)&quot;&gt;Next Page&lt;/button&gt;
        &lt;/div&gt;
        {{ page }}
    &lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { onBeforeMount, ref, computed, watch } from &#39;vue&#39;;
import { usePokemonStore } from &#39;../stores/pokemon&#39;
import PokemonCard from &#39;../components/PokemonCard.vue&#39;
import PokemonImage from &#39;../components/PokemonImage.vue&#39;
const pokeStore = usePokemonStore();

const page = ref(1)

const pokemonsToShow = ref([])

// offset and limit calculate based on the page
const limit = computed(() =&gt; 20 );
const offset = computed(() =&gt; page.value * limit.value - limit.value);

// initial load
onBeforeMount(async () =&gt; {
    await pokeStore.getList()
    await pokeStore.loadPokemons(0, limit.value)
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(0, pokeStore.pokemonsLoadedLength)
})

const pageChange = async (step) =&gt; {
    page.value = page.value + step
    await pokeStore.loadPokemons(offset.value, limit.value)

    const start = offset.value;
    const end = offset.value + limit.value;
    console.log(start, end)
    console.log(pokeStore.pokemonsLoaded)
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(start, end)
    console.log(pokemonsToShow.value)
}
&lt;/script&gt;

Now when the user clicks on the page button the page.value is updated so that the computed values for the offset and the limit are also updated (in reality only the offset updates) that way if the page is new I can load new pokemons from that which I do by calling the pokeStore.loadPokemons(offset.value, limit.value) function and awaiting for that inside the pageChange function. But now I want to change the pokemonsToShow so I want to get a slice of the array of loaded pokemons in the storage but every time I try to slice that array I get back nothing, even though when I print the array using console.log(pokeStore.pokemonsLoaded) the array shows as updated with the new values, and the ranges are correct.

I'm expecting the array to slice correctly since if I put static values in this function call:

    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(2, 4)
}

It works for some reason, but not with the values calculated dinamically

答案1

得分: 0

这涉及到console.log()的一个棘手问题。 console.log(pokeStore.pokemonsLoaded)将显示获取数据的结果,即使实际上console.log在获取完成之前执行。这是因为许多浏览器显示对象数据的“实时”视图。

https://developer.mozilla.org/en-US/docs/Web/API/Console/log#logging_objects

不要使用console.log(obj),使用console.log(JSON.parse(JSON.stringify(obj))...许多浏览器提供一个实时视图,会随着值的更改不断更新。这可能不是您想要的。

因此,很可能在切片数组时实际上数组尚未更新。我还认为这是真的,因为即使您等待这个调用:await pokeStore.loadPokemons(...),该函数也不等待它的loadPokemon()调用。由于没有等待,该函数会在获取完成之前立即执行并返回到组件代码。

我认为如果您等待该调用,一切应该开始工作。

async function loadPokemons(offset, limit){
  .
  .
  .
  await loadPokemon(pokemonsList.value[i].name)
}
英文:

This is a tricky thing about console.log().
console.log(pokeStore.pokemonsLoaded) will show you the result of the fetched data even if console.log is in reality executed before the fetch is done. This is due to the fact that many browsers show a "live" view of object data.

https://developer.mozilla.org/en-US/docs/Web/API/Console/log#logging_objects
> Don't use console.log(obj), use console.log(JSON.parse(JSON.stringify(obj))) ... many browsers provide a live view that constantly updates as values change. This may not be what you want.

It is probable then that the array has not actually been updated at the time you slice it. I also believe this is true because even though you await this call: await pokeStore.loadPokemons(...), that function does not await it's call to loadPokemon(). Since there is no await, the function immediately finishes executing before the fetch has finished and returns to your component code.

I believe if you do await that call, everything should start working

async function loadPokemons(offset, limit){
  .
  .
  .
  await loadPokemon(pokemonsList.value[i].name)
}


</details>



huangapple
  • 本文由 发表于 2023年2月9日 02:22:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75390146.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定