Ответы пользователя по тегу Vue.js
  • Как поднять данные из дочернего компонента в главный через ещё один промежуточный компонент vue 3?

    @GrayHorse
    Передавай в пропс по цепочке объект-состояние с реативной переменной, либо с функцией коллбеком.

    Пример передачи объекта с реактивной переменной:

    App.vue
    <template>
      <div>App: {{numRef}}</div>
      <hr>
      <Minus :state="state"/>
    </template>
    
    <script setup>
    import {ref, watchEffect} from "vue";
    import Minus from "./Minus.vue";
    
    const numRef = ref(0);
    const state = {numRef};
    
    watchEffect(() => {
      console.log(numRef.value);
    });  
    </script>


    Minus.vue
    <template>
      <div>{{state.numRef}} - <button @click="onClick">Minus</button></div>
      <Plus :state="state"/>
    </template>
    
    <script setup>
    import Plus from "./Plus.vue";
    const props = defineProps(["state"]);
    function onClick() {
      props.state.numRef.value--;
    }
    </script>


    Plus.vue
    <template>
      <div>{{state.numRef}} - <button @click="onClick">Plus</button></div>
    </template>
    
    <script setup>
    const props = defineProps(["state"]);
    function onClick() {
      props.state.numRef.value++;
    }
    </script>


    Либо объяви реактивную переменную в отдельном JS файле и импортируй ее в нужных компонентах.
    Ответ написан
    Комментировать
  • Как залить приложение на gh-pages?

    @GrayHorse
    Настрой GitHub Actions в файле .github/workflows/blank.yml.

    Можно сделать так, чтобы проект автоматически билдился, а результат сборки хостился на GitHub Pages, просто после пуша коммита на GitHub.

    Вот, например, инструкция-пример такого репозитория (тоже, кстати, Vue.js проект):
    https://github.com/AlttiRi/vue-gh-pages-deploy-config
    Ответ написан
    Комментировать
  • Как отобразить русские символы в данных, полученных посредством API?

    @GrayHorse
    JSON.parse

    ---
    JSON.parse('\"\\u0422\\u0435\\u0441\\u0442\\u043e\\u0432\\u0430\\u044f\"')

    "Тестовая"
    Ответ написан
    Комментировать
  • ElementUI + Vue3. Почему не работает v-model на форме с чекбоксами? Как подправить?

    @GrayHorse
    v-model это не то, что по-умолчанию есть во все компонентах. Это как раз наоборот.

    Если у компонента есть v-model, то скорее всего об это будет явно сказано в доках.
    Не сказано, значит нет.

    > Как это исправить?
    > Если вручную вешать обработчики... То это работает

    Сам и ответил.
    Ответ написан
    Комментировать
  • Как сделать прогрессбар с процентами для получения ответа c json по api?

    @GrayHorse
    Можно использовать TransformStream.
    Кода будет поменьше, чем если создавать "прокси" ReadableStream. Единственное отличие, что TransformStream "ленивый". Т.е. если закомментировать await newResponse.blob(); onProgress не будет вызываться, хотя данные были загружены.

    Собственно вот онлайн демо.

    const response = await fetch("https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg");
    total.value    = parseInt(response.headers.get("content-length")) || 0;
    
    function onProgress(chunk) {
        loaded.value += chunk.length;
    }
    const ts = new TransformStream({
        transform(chunk, controller) {
            onProgress(chunk);
            controller.enqueue(chunk);
        },
    });
    response.body.pipeThrough(ts);
    const newResponse = new Response(ts.readable);
    
    const blob = await newResponse.blob();


    Имей в виду, что "content-length" может отсутствовать.

    А также данные могут быть сжаты, если есть заголовок "content-encoding", который, к слову, по-умолчанию будет недоступен для кода при кросс-доменных запросах.

    И при "content-encoding" в "content-length" значение всегда указано для сжатого респонса, а в коллбеке будут уже не сжатые данные. Т.е. loaded в таком случае будет больше значения total ("content-length")

    ---

    "Прокси" ReadableStream:
    const reader = response.body.getReader();
    const readableStream = new ReadableStream({
        async start(controller) {
            while (true) {
                const {done, value} = await reader.read();
                if (done) { break; }
                onProgress(value);
                controller.enqueue(value);
            }
            controller.close();
            reader.releaseLock();
        },
        cancel() {
            void reader.cancel();
        }
    });
    const newResponse = new Response(readableStream);
    Ответ написан
    3 комментария
  • Как вывести обновляемое время?

    @GrayHorse
    Вот

    export default {
      data() {
        return {
          dateNow: new Date(),
          timerId: null
        };
      },
      mounted() {
        this.timerId = setInterval(() => {
          this.dateNow = new Date();
        }, 1000);
      },
      beforeUnmount() {
        clearInterval(this.timerId);
      }
    };


    Или так

    import {ref, onUnmounted} from "vue";
    
    const dateNow = ref(new Date());
    const timerId = setInterval(() => {
      dateNow.value = new Date();
    }, 1000);
    
    onUnmounted(() => {
      clearInterval(timerId);
    });
    Ответ написан
    Комментировать
  • Как создать не реактивные данные?

    @GrayHorse
    Берешь и создаешь обычным способом, без использования ref, reactive и т.д.:

    const a = [1, 2, 3];

    Также toRaw, чтобы получить оригинальный объект из реактивного:

    const a = [1, 2, 3];
    const r = ref(a);
    const b = toRaw(r.value);

    Изменения a и/или b не будут вызывать рендер, а изменения r.value — будут.

    Хотя если использовать markRaw над a, то даже r.value перестанет быть реативным.

    Поисковик выдает много всего

    Две ссылки:
    https://vuejs.org/api/reactivity-core.html
    https://vuejs.org/api/reactivity-advanced.html
    Ответ написан
    Комментировать
  • Как в Vue вывести элементы из массива по одному в html, раз в секунду?

    @GrayHorse
    sfc.vuejs.org demo
    <template>
      <div v-for="i of items" :key="i">{{i}}</div>
    </template>
    
    <script setup>
    import {ref, computed, onMounted} from "vue";
    
    const data = ["1q", "2w", "3e", "4r", "5t", "6y"];
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
      
    const items = ref([]);
    onMounted(async () => {
      for (const value of data) {
        items.value.push(value);
        await sleep(1000);
      }
    });
    </script>


    Или так

    const limit = ref(0);
    const items = computed(() => {
      return data.slice(0, limit.value);
    });
    onMounted(async () => {
      while (limit.value < data.length) {
        limit.value++;
        await sleep(1000);
      }
    });
    Ответ написан
    4 комментария
  • Хочу лучше разобраться с vue sfc, не могу найти нормальных примеров использования, где найти демки?

    @GrayHorse
    Устанавливаешь Vite получаешь готорый Hello World с SFC + script setup
    https://qna.habr.com/answer?answer_id=2119668#answ...

    Несколько онлайн примеров из моих предыдущих ответов:
    https://qna.habr.com/answer?answer_id=2120126#answ...
    https://qna.habr.com/answer?answer_id=2109080#answ...
    https://qna.habr.com/answer?answer_id=2097528#answ...
    Ответ написан
    Комментировать
  • Как корректно импортировать функцию typescript(2698) vue 3?

    @GrayHorse
    Spread types may only be created from object types.


    И что здесь спреадится? undefined.

    В этом закосе по хуки реакта (useSomething) делается так — возвращается объект, чьи поля присваиваются другому объкту путем оператора спреад.

    Примеры можно посмотреть в VueUse, например:
    github.com/vueuse/.../useMemory/index.ts#L41-L53
    Ответ написан
    Комментировать
  • Почему не работает реактивность VUE3 CompositionAPI?

    @GrayHorse
    Во-первых, код можно было бы и отформатировать как-нибудь: первый вариант, второй.

    Не работает, потому что для каждого компонента при вывозе useModal() создаются свои личные isShowModal, openModal, closeModal переменные.

    Если не пытаться сделать из Vue подобие React Hooks, то вот: рабочий вариант.
    (Из расчета, что компонент будет в одном экземпляре.)

    Формальности ради: если компонент планируется использовать больше одного раза, то modal-state.js нужно оформить в виде класса, и передавать экземпляр* этого класса компоненту в виде пропса.
    Сейчас же modal-state.jsпо сути синглтон.

    *Который все равно нужно будет хранить где-то (в каком-то js файле), если планируется доступ из нескольких компонентов.
    Ответ написан
  • Как установить и настроить Vue.js?

    @GrayHorse
    Как установить и настроить Vue.js?

    С использованием Vite:

    npm create vite@latest my-vue-app -- --template vue
    cd my-vue-app
    npm install
    npm run dev


    Получишь готовый, запущенный проект за несколько секунд.

    https://vitejs.dev/guide/#scaffolding-your-first-v...

    С yarn (npm install --global yarn) вместо npm это будет заметно быстрее:

    yarn create vite my-vue-app --template vue
    cd my-vue-app
    yarn install
    yarn run dev


    Чтобы localhost:3000 открывался в браузере автоматически, нужно добавить server.open в vite.config.js

    Например:
    import {defineConfig} from "vite";
    import vue from "@vitejs/plugin-vue";
    
    export default defineConfig({
      plugins: [
        vue(),
      ],
      server: {
        open: "/"
      }
    });


    И, как написано в HelloWorld.vue, в случае VSCode следует установить соответствующий плагин для код ассистанса, подсветки синтаксиса Vue 3.
    Ответ написан
    4 комментария
  • Как мне на фронте получить буфер от изображения?

    @GrayHorse
    Так просто вызови метод arrayBuffer у это объекта.
    Всё.

    event.target.files[0] — это объект класса File, который в свою очередь наследуется от класса Blob.
    Ответ написан
    Комментировать
  • Как взаимодействовать между компонентами через Promise?

    @GrayHorse
    А не лучше иметь фукцию, которая будет делать запрос и отбрабатывать результат и записывает его в реактивную переменную?

    Один компонент вызывает эту функцию.

    Другой же компонет использует эту реактивную переменную для своих целей. ("Зависит от нее")

    Демо: https://sfc.vuejs.org/#eyJBc...

    core.js
    import {ref, readonly} from "vue";
    
    const todo = ref(null);
    const todoId = ref(1);
    
    export async function fetchTodo() {
      const resp = await fetch("https://jsonplaceholder.typicode.com/todos/" + todoId.value++);
      todo.value = await resp.json();
    };
    
    // Using of `readonly` is optional, I use it just in case.
    // You can just export `todoId`, `todo` (above) as is.
    const _todoId = readonly(todoId);
    const _todo   = readonly(todo);
    export {
      _todoId as todoId,
      _todo   as todo
    };


    App.vue
    <template>
      <Button/>
      <div v-if="todo">
        {{todo}}
      </div>	
    </template>
    
    <script setup>
      import Button from "./Button.vue";
      import {todo} from "./core.js";
    </script>


    Button.vue
    <template>
      <button @click="fetchTodo">fetchTodo({{todoId}})</button>  
    </template>
    
    <script setup>
      import {fetchTodo, todoId} from "./core.js";
    </script>
    Ответ написан
  • Как совместить сортировку и фильтрацию?

    @GrayHorse
    Развлечения ради.
    Composition API [RU] + Script Setup [RU].

    Собственно, демо: https://sfc.vuejs.org/#

    Код:
    <script setup>
      import {ref, computed, watchEffect} from "vue";
    
      const phones = ref([
        { title: "iPhone 12", company: "Apple", price: 65000 },
        { title: "Galaxy S20", company: "Samsung", price: 63000 },
        { title: "Galaxy A10", company: "Samsung", price: 38000 },
        { title: "iPhone 10", company: "Apple", price: 45000 },
        { title: "Xiaomi Redmi 8", company: "Xiaomi", price: 42000 },
      ]);
    
    
      const companySearch = ref("");
      const filteredList = computed(() => {
        if (!companySearch.value) {
          return phones.value;
        }
        const search = companySearch.value.trim().toLowerCase();
        return phones.value.filter(elem => elem.company.toLowerCase().startsWith(search)); // .includes(search)
      });
    
    
      const sortParam = ref("");
      
      const sortByTitle   = (p1, p2) => p1.title.localeCompare(p2.title, undefined, {sensitivity: "base"});
      const sortByCompany = (p1, p2) => p1.company.localeCompare(p2.company, undefined, {sensitivity: "base"});
      const sortByPrice   = (p1, p2) => p1.price - p2.price;
    
      const sortedList = ref([]);
      watchEffect(() => {
        const array = filteredList.value; //const array = [...filteredList.value];
        if (sortParam.value === "title") {
          sortedList.value = array.sort(sortByTitle);
        } else if (sortParam.value === "company") {
          sortedList.value = array.sort(sortByCompany);
        } else if (sortParam.value === "price") {
          sortedList.value = array.sort(sortByPrice);
        } else {
          sortedList.value = array;
        }
      });
    </script>


    Версия с инвертированием сортировки по второму клику (ссылка на демо в комменте):
    <script setup>
      import {ref, computed} from "vue";
    
      const phones = ref([
        { title: "iPhone 12", company: "Apple", price: 65000 },
        { title: "Galaxy S20", company: "Samsung", price: 63000 },
        { title: "Galaxy A10", company: "Samsung", price: 38000 },
        { title: "iPhone 10", company: "Apple", price: 45000 },
        { title: "Xiaomi Redmi 8", company: "Xiaomi", price: 42000 },
      ]);
    
    
      const companySearch = ref("");
      const filteredList = computed(() => {
        if (!companySearch.value) {
          return phones.value;
        }
        const search = companySearch.value.trim().toLowerCase();
        return phones.value.filter(elem => elem.company.toLowerCase().startsWith(search)); // .includes(search)
      });
    
      /** @type {import("vue").Ref<("title"|"company"|"price")>} */
      const orderBy = ref("title");
      const orders = ref({ // if `true` — an order is reversed
        title: false,
        company: false,
        price: false,
      });
      const isOrderReversed = computed(() => orders.value[orderBy.value]);
      function toggleOrder() {
        orders.value[orderBy.value] = !orders.value[orderBy.value];
      }
      /** @param {"title"|"company"|"price"} value */
      function setOrderBy(value) {
        if (orderBy.value === value) {
          toggleOrder();
        }
        orderBy.value = value;
      };
    
      const {compare} = new Intl.Collator(undefined, {sensitivity: "base"});
      function comparator(pre, cur) {
        const k = isOrderReversed.value ? -1 : 1;    
        if (orderBy.value === "title") {
          return compare(pre.title, cur.title) * k;
        } else if (orderBy.value === "company") {
          return compare(pre.company, cur.company) * k;
        } else if (orderBy.value === "price") {
          return (pre.price - cur.price) * k;
        }
        return 0;
      }
      const sortedList = computed(() => { 
        return filteredList.value.sort(comparator);
      });
    </script>
    Ответ написан
    2 комментария
  • Из за чего ошибка при использовании примера из документации vue?

    @GrayHorse
    Копирую его в проект и при вызове метода на кнопку получаю в консоли:


    Что? Какого метода? Кнопку?

    Нашел пример в документации. Копирую его в проект


    Куда его можно скопировать (неправильно), если это уже по факту полноценное рабочее приложение на Vue.js:

    <!DOCTYPE html>
    <html>
    <head>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
    <div id="mount-point"></div>
    <script>
        const Profile = {
            template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
            data() {
                return {
                    firstName: 'Walter',
                    lastName: 'White',
                    alias: 'Heisenberg'
                }
            }
        }
        Vue.createApp(Profile).mount('#mount-point')
    </script>
    </body>
    </html>


    Что это и почему?

    Это исключение.
    Vue.js не был подключен.
    Ответ написан
    Комментировать
  • Как сделать свойство объекта, переданного в props, реактивным (Vue 3)?

    @GrayHorse
    toRefs

    <script setup>
    import {toRefs} from "vue";
    const props = defineProps({
      name: {
        default: "unknown",
        type: String
      }
    });
    const {name} = toRefs(props);
    </script>
    Ответ написан
    Комментировать
  • Как передать массив а не proxy Vue3?

    @GrayHorse
    Ответ написан
    Комментировать
  • Как в Vue3 создать глобальную переменную?

    @GrayHorse
    Как в Vue3 создать глобальную переменную?

    Берешь и создаешь. Это не Vue 2.

    Или ты не используешь Composition API и script setup?

    Объявил что надо в обычных .js файлах, потом используешь это в любых .vue компонентах, нужно лишь заимпортировать.

    Собственно лайф демо:
    https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHRlbX...

    main.js
    import {computed, ref} from "vue";
    
    export const todos = ref([]);
    export const activeTodos = computed(() => {
      return todos.value.filter(todo => !todo.done);
    });
    
    let id = 0;
    export function addTodo(name, done = false) {
      todos.value.push({name, done, id: id++});
    };
    export function removeTodo(todo) {
      todos.value = todos.value.filter(_todo => _todo.id !== todo.value.id);
    };
    export function toggleTodo(todo) {
      todo.value.done = !todo.value.done;
    };
    export function clearTodos() {
      todos.value = [];
    };


    App.vue
    <script setup>
    import {computed, ref, onMounted} from "vue";
    import Todo from "./Todo.vue";
    import {todos, activeTodos, clearTodos, addTodo} from "./main.js";
      
    const showAll = ref(true);
    const selectedTodos = computed(() => {
      return showAll.value ? todos.value : activeTodos.value;
    });
    function onEnter(event) {
      addTodo(event.currentTarget.value);
      event.currentTarget.value = "";
    }
      
    onMounted(() => {
      addTodo("Task 01", true);
    	addTodo("Task 02");
    	addTodo("Task 03");
    });
    </script>


    Todo.vue
    <script setup>
    import {toRefs} from "vue";
    import {toggleTodo, removeTodo} from "./main.js";  
    const props = defineProps(["todo"]);
    const {todo} = toRefs(props);  
    
    function onClick() {
      toggleTodo(todo);
    }
    function onRightClick() {
      removeTodo(todo);
    }    
    </script>
    Ответ написан
    Комментировать
  • Как использовать computed вместе с v-model?

    @GrayHorse
    В смысле?

    Дефолтное значение в инпуте, появляющемся по условию:
    https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHRlbXBsYXR...

    <template>  
      <label>
        <input v-model="checked" type="checkbox">
        Show input
      </label>
      <br>
      <input v-if="checked" v-model="msg">
    </template>
    
    <script setup>
    import {ref} from "vue";
    
    const msg = ref("Hello World!");
    const checked = ref(false);
    </script>


    На Vue 2 все аналогично.

    Оно просто работает.
    Ответ написан
    Комментировать