#vuejs3 #vue-composition-api
#vuejs3 #vue-composition-api
Вопрос:
спасибо, что прочитали мой пост.
Трудно описать мою проблему.
У меня есть реактивный объект, кэшированный глобально (например, хранилище, и давайте назовем его здесь «хранилище»), чтобы сохранить состояния вместе с некоторыми действиями. Изначально все работает хорошо. Но когда я добавил больше страниц и переключался между страницами, компоненты ломались после повторного рендеринга. Компоненты по-прежнему могут корректно считывать данные из хранилища и вызывать действия для обновления данных хранилища и консоли can.зарегистрируйте «отпечаток пальца», чтобы показать, что это тот же магазин. Но обновленные данные в хранилище больше не обновляются в пользовательском интерфейсе. Я могу утешить.зарегистрируйте данные в хранилище, которое выглядит хорошо, но пользовательский интерфейс заморожен с данными с момента последнего размонтирования.
Ниже я привожу код своего компонента.
<template>
<div class="min-w-full relative" ref="container">
<hi-transition slow>
<center-box
v-if="loading"
class="absolute w-full h-full left-0 top-0 text-primary-light opacity-50"
>
<hi-spinner class="w-1/6 h-1/6" :stroke="2" />LOADING...
</center-box>
<div v-else-if="noResult">
No Result
</div>
<hi-markable
v-else
class="w-full h-full divide-y divide-primary-lighter overflow-y-auto block"
:search="searchValue"
>
<div
v-for="item in displayItems"
:key="item.id"
class="hover:bg-primary-lightest transition-colors duration-500 flex items-center block"
@click="selectItem(item)"
:style="{
'min-height': minItemHeight 'px',
height: itemHeight 'px'
}"
:class="{ active: currentItem === item }"
>
<slot :item="item" />
</div>
</hi-markable>
</hi-transition>
</div>
</template>
<script>
import { inject, ref } from "vue";
import HiSpinner from "@/ui/HiSpinner";
import CenterBox from "@/ui/elements/CenterBox";
import HiTransition from "@/ui/HiTransition";
import HiMarkable from "@/ui/HiMarkable";
import { computed } from "vue";
export default {
name: "HiDataList",
components: { HiMarkable, HiTransition, CenterBox, HiSpinner },
props: {
storeToken: String,
minItemHeight: [Number, String],
autoItemsPerPage: Boolean
},
emits: ["select"],
setup(props, { emit }) {
//this store is a reactive object cached somewhere
const store = inject(props.storeToken);
//to make sure it's the same store
console.log("fingerprint", store.fingerprint);
const { displayItems, currentItem, loading, searchValue, noResult } = store;
const container = ref(null);
const itemHeight = computed(() => {
return props.autoItemsPerPage
? store.autoItemHeight.value
: props.minItemHeight;
});
if (props.autoItemsPerPage) {
store.autoItemsPerPage(container, props.minItemHeight);
}
function selectItem(item) {
console.log(item);
emit("select", item);
store.setCurrentItem(item);
}
return {
displayItems,
selectItem,
container,
itemHeight,
currentItem,
loading,
searchValue,
noResult
};
}
};
</script>
<style scoped>
.active,
.active:hover {
@apply bg-primary-lighter border-primary-light;
}
</style>
Магазин списков
import { computed, reactive, watch, toRefs } from "vue";
import { useElementSize } from "@vueuse/core";
import { watchProps } from "@/utils/reactiveHelpers";
function filter(item, filters) {
const f = (key, filter) => {
const itemVal = item[key];
if (Array.isArray(itemVal)) return itemVal.indexOf(filter) >= 0;
else return itemVal === filter;
};
for (let key in filters) {
const filterVal = filters[key];
if (Array.isArray(filterVal)) {
for (let i = 0; i < filterVal.length; i ) {
if (f(key, filterVal[i])) return true;
}
} else {
return f(key, filterVal);
}
}
return false;
}
const getTime = date => {
if (date.milliseconds) return date.milliseconds;
if (date.seconds) return date.seconds;
if (date.getTime) return date.getTime();
};
const createListStore = (source, settings = {}) => {
const state = reactive({
source: source,
currentPage: 0,
itemsPerPage: 0, //zero means display all
loading: true,
ready: false,
noData: false,
noResult: false,
filters: {},
search: null,
searchables: settings.searchables || [],
sortBy: null,
sortType: "alpha",
desc: false,
currentItem: null,
autoItemHeight: 0,
fingerprint: Math.random() * 10000000000000000
});
// const { itemsPerPage,source,filters,search,searchables,sortBy,sortType,desc } = toRefs(state);
const {
itemsPerPage,
ready,
loading,
noResult,
search,
autoItemHeight
} = toRefs(state);
watchProps(state, "source", v => {
if (typeof v !== "undefined") {
state.ready = true;
state.loading = false;
if (!v.length) state.noData = true;
}
});
const currentPage = computed(() => state.currentPage 1);
// const itemsPerPage = computed(() => state.itemsPerPage);
const totalItems = computed(() => results.asc.length);
const from = computed(() => {
if (totalItems.value === 0) return 0;
return state.currentPage * state.itemsPerPage 1;
});
const to = computed(() => {
const t = from.value displayItems.value.length - 1;
return t > totalItems.value ? totalItems.value : t;
});
const totalPages = computed(() => {
if (totalItems.value === 0 || state.itemsPerPage === 0) return 1;
return Math.ceil(totalItems.value / state.itemsPerPage);
});
const gotoPage = page => {
console.log("gotoPage", page);
state.currentPage = page - 1;
console.log(state.currentPage);
};
const prevPage = () => {
if (state.currentPage > 0) state.currentPage--;
};
const nextPage = () => {
if (state.currentPage < totalPages.value) state.currentPage ;
};
const updateFilters = (filter, val) => {
state.filters[filter] = val;
};
/**
*
* @param column
* @param desc
* @param type "alpha"|"number"|"date"|"time"
*/
const sortBy = (column, desc = false, type = "alpha") => {
state.sortBy = column;
state.desc = desc;
state.sortType = type;
};
function doSearch(item) {
const searchables = state.searchables;
for (let i = 0; i < searchables.length; i ) {
const key = searchables[i];
let value = item[key];
if (value amp;amp; typeof value === "string") {
value = value.toLowerCase();
}
if (value amp;amp; value.indexOf(state.search) >= 0) {
return true;
}
}
return false;
}
const results = reactive({
desc: [],
asc: []
});
function calcResults() {
if (!state.ready || state.noData) return null;
// console.log("re-calc results....");
const hasFilters = Object.keys(state.filters).length > 0;
// console.log(Object.keys(state.filters));
let items = [];
if (hasFilters || (state.search amp;amp; state.search.length)) {
//do filter amp; search
const source = state.source;
for (let i = 0; i < source.length; i ) {
const item = source[i];
// console.log(filter(item, state.filters));
if (hasFilters amp;amp; !filter(item, state.filters)) {
continue;
}
if (state.search amp;amp; state.search.length amp;amp; !doSearch(item)) {
continue;
}
items.push(item);
}
if (!items.length) {
results.desc = results.asc = [];
return null;
}
} else {
items = state.source;
}
if (state.sortBy) {
//do sort
const sort = state.sortBy;
// const desc = state.desc ? -1 : 1;
const type = state.sortType.toLowerCase();
items.sort((a, b) => {
a = a[sort];
b = b[sort];
if (type === "date" || type === "time") {
return getTime(a) - getTime(b);
} else {
if (typeof a === "string") a = a.trim();
if (typeof b === "string") b = b.trim();
if (state.sortType.toLowerCase() === "number") {
return a - b;
} else {
return a.localeCompare(b, "en", { sensitivity: "base" });
}
}
});
}
results.asc = items;
results.desc = [...items].reverse();
// return items;
}
//changed to watch for the wired vue error.
watchProps(
state,
["source", "filters", "search", "searchables", "sortBy", "sortType"],
() => {
calcResults();
state.noResult = results.asc.length === 0;
}
);
const displayItems = computed(() => {
if (!results.asc.length) return [];
const re = state.desc ? results.desc : results.asc;
if (state.itemsPerPage === 0) return re;
const from = state.currentPage * state.itemsPerPage;
const to = from state.itemsPerPage;
return re.slice(from, to);
});
/**
*
* @param elementRef ref
* @param minHeight Number
* @param itemHeightRef ref
*/
const autoItemsPerPage = (elementRef, minHeight) => {
const { height } = useElementSize(elementRef);
// console.log(elementRef);
watch(height, v => {
const items = Math.floor(v / minHeight);
// itemHeightRef.value = v / items;
// console.log(v / items);
state.itemsPerPage = items;
state.autoItemHeight = v / items;
});
};
const setCurrentItem = item => {
console.log("set current", state.fingerprint);
state.currentItem = item;
};
const currentItem = computed(() => state.currentItem);
return {
currentPage,
itemsPerPage,
totalItems,
displayItems,
from,
to,
totalPages,
gotoPage,
prevPage,
nextPage,
ready,
loading,
updateFilters,
sortBy,
search: v => (state.search = v),
searchValue: search,
autoItemsPerPage,
setCurrentItem,
currentItem,
noResult,
autoItemHeight,
fingerprint: state.fingerprint
};
};
export { createListStore };
Поставщик хранилища
import { provide } from "vue";
import { createListStore } from "@/ui/storeProvider/listStore";
const storeDepot = {};
export const provideListStore = (token, source, settings = {}) => {
if (!storeDepot[token]) {
console.log("create new store", token);
storeDepot[token] = createListStore(source, settings);
}
provide(token, storeDepot[token]);
return storeDepot[token];
};
Комментарии:
1. Можете ли вы опубликовать изображение самого магазина? Чтобы обновить ваш DOM, у вас должны быть ссылки или активные атрибуты в yout store, из этой картинки я не могу сказать, есть ли они / нет
2. Привет, Фелипе, спасибо за ваши комментарии. Пожалуйста, ознакомьтесь с обновленным хранилищем в сообщении. На самом деле дело не в самой реактивности. Код работает нормально во время первоначального монтирования. Странное поведение заключается в том, что при повторном монтировании все состояния компонентов остаются такими же, как и при последнем монтировании. Функция вызывает, но не изменяет никаких данных / состояния.