[JavaScript, VueJS] Vue 3 Composition API: Ref или Reactive (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Сейчас, когда я пишу эту статью, мы все ближе к релизу Vue 3. На мой взгляд самое интересное наблюдать как воспримут и будут применять его другие разработчики. У меня была возможность поиграть с Vue 3 в последние несколько месяцев, но я знаю есть те, у кого такой возможности не было.
Самое значительное нововведение в новой версии это Composition API. Он дает альтернативный подход к созданию компонентов и очень отличается от существующего Options API. Мне не сложно признать, что когда я впервые увидел его, я его не понял. Однако, по мере применения начал появляться смысл. Может вы не будете переписывать все ваше приложение с использованием Composition API, но статья даст вам возможность подумать как будет происходить создание компонентов и использование композиции.
Я провел пару презентаций недавно, и частым вопросом был: когда я использую Ref, а когда Reactive, для объявления реактивного свойства. У меня не было хорошего ответа, и я посвятил пару недель нахождению ответа, и эта статья результат моих исследований.
Хотел бы отметить, к тому же, что изложенное — мое мнение и пожалуйста не думайте, что надо делать “только так, и не иначе”. Я планирую так использовать Ref и Reactive, пока кто-нибудь не посоветует другое, или пока я сам не найду лучший подход. Я думаю, для понимания любой новой технологии требуется какое-то время, а следом может появится и отработанные методики.
Перед тем как приступить, я допускаю, что вы хотя бы вкратце ознакомились с composition API и понимаете из чего он состоит. Статья фокусируется на различиях ref и reactive, а не на механизме composition API.
Реактивность Vue 2
Чтобы немного ввести вас в курс дела, я вкратце рассмотрю как создать реактивные свойства в приложении на Vue 2. Когда вы хотите, чтобы Vue 2 отслеживал изменения в свойствах, вам надо задекларировать их в объекте, который возвращается функцией data.
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: "Hello, Vue!"
};
}
};
</script>
Под капотом Vue 2 для каждого свойства вызывается Object.defineProperty() создающий геттер и сеттер, чтобы отслеживать изменения. Это простейшее объяснение процесса и я хочу донести мысль: в этом нет магии. Вы не можете создать реактивные свойства где попало и ожидать, что Vue будет отслеживать изменения в них. Вам необходимо задавать реактивные свойства в функции data.
REF и REACTIVE
При работе с Options API нам необходимо следовать некоторым правилам при декларировании реактивных свойств, тоже самое и при работе с Composition API. Вы не можете только создать свойство и ожидать реактивности. В следующем примере я задекларировал свойство title и функция setup() возвращает его, тем самым делая его доступным для шаблона.
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
setup() {
let title = "Hello, Vue 3!";
return { title };
}
};
</script>
Это сработает, но свойство title не будет реактивным. Т.е. если кто-то изменит title, эти изменения НЕ отразятся в Доме. Например, если вы измените title через 5 секунд, код ниже НЕ поменяет Дом.
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
setup() {
let title = "Hello, Vue 3!";
setTimeout(() => {
title = "THIS IS A NEW TITLE";
}, 5000);
return { title };
}
};
</script>
Мы можем импортировать ref и использовать ее, чтобы сделать свойство реактивным. Под капотом Vue 3 создаст Proxy.
<template>
<h1>{{ title }}</h1>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const title = ref("Hello, Vue 3!");
setTimeout(() => {
// может возникнуть вопрос, что за .value ...
// об этом позже
title.value = "New Title";
}, 5000);
return { title };
}
};
</script>
Хочу пояснить, что говоря о Ref и Reactive, мне кажется, есть два разных случая. Первый — создание компонента как в примере выше и вам нужны реактивные свойства. Второй — когда вы создаете функции позволяющие композицию, чтобы использовать их в компонентах и функциях. В статье мы обсудим оба сценария.
REF
При создании свойств простых типов, ref() ваш первый выбор. Это не серебряная пуля, но с это стоит начать. Напомню вам семь примитивных типов в JavaScript:
- String
- Number
- BigInt
- Boolean
- Symbol
- Null
- Undefined
import { ref } from "vue";
export default {
setup() {
const title = ref("");
const one = ref(1);
const isValid = ref(true);
const foo = ref(null);
}
};
В предидущем примере у нас title имеет тип String, значит чтобы сделать свойство реактивным выбираем ref(). Если код ниже вызывает у вас некоторые вопросы не беспокойтесь, у меня были такие же вопросы.
import { ref } from "vue";
export default {
setup() {
const title = ref("Hello, Vue 3!");
setTimeout(() => {
title.value = "New Title";
}, 5000);
return { title };
}
};
Почему мы используем const, если title будет меняться? Почему не использовать let? Если вы выведете title в консоль, вы могли бы ожидать увидеть Hello, Vue 3!, но вместо это отобразится объект:
{_isRef: true}
value: (...)
_isRef: true
get value: ƒ value()
set value: ƒ value(newVal)
__proto__: Object
Функция ref() примет аргумент и вернет реактивный и изменяемый ref объект. Ref объект имеет одно свойство value, ссылающееся на аргумент. Это означает, что если вы хотите получить или изменить значение надо будет использовать title.value, а так как это объект, который не будет меняться, я и объявил его const.
Обращение к REF
Следующий вопрос — а почему в шаблоне мы не обращаемся title.value?
<template>
<h1>{{ title }}</h1>
</template>
Когда Ref возвращается как свойство в контекст отрисовки (объект возвращаемый функцией setup()) и идет обращение к нему в шаблоне, Ref автоматически возвращает value. В шаблоне добавлять .value не нужно.
Вычисляемые свойства — computed properties — работают так же, внутри функции setup() обращаться к ним так .value.
REACTIVE
Мы только что рассмотрели применение ref() для задания реактивности свойств простых типов. А что будет, если мы хотим создать реактивный объект? Мы по прежнему могли бы использовать ref(), но под капотом Vue будет использовать reactive(), так что я буду придерживаться использования reactive().
С другой стороны reactive() не сработает с примитивными типами. Функция reactive() принимает объект и возвращает реактивный прокси оригинала. Это эквивалентно .observable() во Vue 2, и это имя функции было изменено во избежание путаницы с observables в RxJS.
import { reactive } from "vue";
export default {
setup() {
const data = reactive({
title: "Hello, Vue 3"
});
return { data };
}
};
Главное отличие в том, как мы обращаемся к реактивному объекту в шаблоне. В предидущем примере data это объект содержащий свойство title. Вам надо будет обращаться к нему в шаблоне так — data.title:
<template>
<h1>{{ data.title }}</h1>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const data = ref({
title: "Hello, Vue 3"
});
return { data };
}
};
</script>
Разница между REF и REACTIVE в компоненте
Исходя из того, что мы обсудили, ответ казалось бы напрашивается? Используем ref() для простых типов, и reactive() для объектов. Когда я начал создавать компоненты, выяснилось, что это всегда не так, и документации говорится:
Разница между применением ref и reactive может быть, в какой-то мере, сравнима с тем, как вы пишете стандартную логику программы на JavaScript.
Я размышлял над этой фразой и пришел к следующим выводам. По мере роста приложения у меня появились следующие свойства:
export default {
setup() {
const title = ref("Hello, World!");
const description = ref("");
const content = ref("Hello world");
const wordCount = computed(() => content.value.length);
return { title, description, content, wordCount };
}
};
В JavaScript я бы понял, что это все свойства моей страницы. В этом случае я бы сгруппировал бы их в объект page, почему бы не сделать тоже самое сейчас.
<template>
<div class="page">
<h1>{{ page.title }}</h1>
<p>{{ page.wordCount }}</p>
</div>
</template>
<script>
import { ref, computed, reactive } from "vue";
export default {
setup() {
const page = reactive({
title: "Hello, World!",
description: "",
content: "Hello world",
wordCount: computed(() => page.content.length)
});
return { page };
}
};
</script>
Это мой подход к вопросу Ref или Reactive, но было бы не плохо узнать ваше мнение. Делаете-ли вы также? Может это не правильный подход? Пожалуйста комментируйте.
Логика композиции
Вы не сможете ошибиться при использовании ref() или reactive() в ваших компонентах. Обе функции создадут реактивные данные, и если вы понимаете как к ним обращаться в вашей функции setup() и в шаблонах не возникнет каких-либо сложностей.
Когда же вы начнете писать композиционные функции вам надо будет понимать разницу. Я использую примеры из RFC документации, так как одни хорошо иллюстрируют нюансы.
Вам надо создать логику отслеживания положения мыши. Вы также надо иметь возможность использовать эту-же логику в любом компоненте, когда это нужно. Вы создаете композиционную функцию, которая отслеживает координаты x и y, и затем возвращает их клиентскому коду.
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { x, y };
}
Если вы хотите использовать эту логику в компоненте и вызываете эту функцию, то деструктурируйте возвращаемый объект и затем возвращайте x и y координаты в шаблон.
<template>
<h1>Use Mouse Demo</h1>
<p>x: {{ x }} | y: {{ y }}</p>
</template>
<script>
import { useMousePosition } from "./use/useMousePosition";
export default {
setup() {
const { x, y } = useMousePosition();
return { x, y };
}
};
</script>
Это будет работать, но если, поглядев на функцию, вы решили провести рефакторинг и возвращать объект position вместо x и y:
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const pos = {
x: 0,
y: 0
};
function update(e) {
pos.x = e.pageX;
pos.y = e.pageY;
}
// ...
}
Проблема этого подхода в том, что клиент композиционной функции должен иметь ссылку на возвращаемый объект всегда, чтобы реактивность сохранялась. Это означает, что мы не можем деструктурировать объект или применить spread оператор:
// компоте клиент
export default {
setup() {
// потеря реактивности!
const { x, y } = useMousePosition();
return {
x,
y
};
// потеря реактивности!
return {
...useMousePosition()
};
// это единственный способ сохранить реактивность
// надо возвращать `pos` как есть, и обращаться в шаблоне к x и y так: `pos.x` и `pos.y`
return {
pos: useMousePosition()
};
}
};
Но это не означает, что мы не можем в таком случае использовать reactive. Есть функция toRefs(), которая сконвертирует реактивный объект в простой объект, каждое свойство которого это ref соответствующего свойства оригинального объекта.
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
});
// ...
return toRefs(pos);
}
// x & y теперь ref!
const { x, y } = useMousePosition();
Таким образом надо учитывать некоторые аспекты при создании композиционных функций. Если вы понимаете, как клиентский код работает с вашими функциями, то все будет ОК.
ИТОГ
Когда я впервые начал использовать Composition API, я был озадачен вопросом, когда применять ref(), а когда reactive(). Возможно я до сих пор делаю это не правильно, и пока кто-нибудь не скажет мне этого, я буду придерживаться этого подхода. Надеюсь помог прояснить некоторые вопросы, и хотелось бы услышать ваше мнение.
Happy Coding
===========
Источник:
habr.com
===========
===========
Автор оригинала: Dan Vega
===========Похожие новости:
- [JavaScript, Программирование] Эффектное программирование. Часть 1: итераторы и генераторы
- [Разработка веб-сайтов, JavaScript, ReactJS] Почему мы выбрали MobX, а не Redux, и как его использовать эффективнее
- [Open source, JavaScript, Я пиарюсь] Javascript фреймворк разработки бизнес приложений
- [Программирование, TypeScript] Chorda 2.0. Как я потерял и нашел API
- [JavaScript, Тестирование мобильных приложений, ReactJS] Моки не кусаются! Осваиваем мокинг с React Testing Library (перевод)
- Открыты исходные тексты GitHub Docs
- [JavaScript, ReactJS, Программирование, Разработка веб-сайтов] Мифы о useEffect (перевод)
- [Разработка веб-сайтов, JavaScript, Социальные сети и сообщества] Front End Meetup от Facebook Developer Circle: Moscow
- [JavaScript, C++, WebAssembly] Как скрестить Clion, Emscripten и Cmake
- [Конференции] Новая неделя YouTube-стримов: от Vue.js до SIMD-инструкций
Теги для поиска: #_javascript, #_vuejs, #_vuejs, #_vuejs3, #_javascript, #_framework, #_frontend_razrabotka (front-end разработка), #_javascript, #_vuejs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:17
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Сейчас, когда я пишу эту статью, мы все ближе к релизу Vue 3. На мой взгляд самое интересное наблюдать как воспримут и будут применять его другие разработчики. У меня была возможность поиграть с Vue 3 в последние несколько месяцев, но я знаю есть те, у кого такой возможности не было. Самое значительное нововведение в новой версии это Composition API. Он дает альтернативный подход к созданию компонентов и очень отличается от существующего Options API. Мне не сложно признать, что когда я впервые увидел его, я его не понял. Однако, по мере применения начал появляться смысл. Может вы не будете переписывать все ваше приложение с использованием Composition API, но статья даст вам возможность подумать как будет происходить создание компонентов и использование композиции. Я провел пару презентаций недавно, и частым вопросом был: когда я использую Ref, а когда Reactive, для объявления реактивного свойства. У меня не было хорошего ответа, и я посвятил пару недель нахождению ответа, и эта статья результат моих исследований. Хотел бы отметить, к тому же, что изложенное — мое мнение и пожалуйста не думайте, что надо делать “только так, и не иначе”. Я планирую так использовать Ref и Reactive, пока кто-нибудь не посоветует другое, или пока я сам не найду лучший подход. Я думаю, для понимания любой новой технологии требуется какое-то время, а следом может появится и отработанные методики. Перед тем как приступить, я допускаю, что вы хотя бы вкратце ознакомились с composition API и понимаете из чего он состоит. Статья фокусируется на различиях ref и reactive, а не на механизме composition API. Реактивность Vue 2 Чтобы немного ввести вас в курс дела, я вкратце рассмотрю как создать реактивные свойства в приложении на Vue 2. Когда вы хотите, чтобы Vue 2 отслеживал изменения в свойствах, вам надо задекларировать их в объекте, который возвращается функцией data. <template>
<h1>{{ title }}</h1> </template> <script> export default { data() { return { title: "Hello, Vue!" }; } }; </script> Под капотом Vue 2 для каждого свойства вызывается Object.defineProperty() создающий геттер и сеттер, чтобы отслеживать изменения. Это простейшее объяснение процесса и я хочу донести мысль: в этом нет магии. Вы не можете создать реактивные свойства где попало и ожидать, что Vue будет отслеживать изменения в них. Вам необходимо задавать реактивные свойства в функции data. REF и REACTIVE При работе с Options API нам необходимо следовать некоторым правилам при декларировании реактивных свойств, тоже самое и при работе с Composition API. Вы не можете только создать свойство и ожидать реактивности. В следующем примере я задекларировал свойство title и функция setup() возвращает его, тем самым делая его доступным для шаблона. <template>
<h1>{{ title }}</h1> </template> <script> export default { setup() { let title = "Hello, Vue 3!"; return { title }; } }; </script> Это сработает, но свойство title не будет реактивным. Т.е. если кто-то изменит title, эти изменения НЕ отразятся в Доме. Например, если вы измените title через 5 секунд, код ниже НЕ поменяет Дом. <template>
<h1>{{ title }}</h1> </template> <script> export default { setup() { let title = "Hello, Vue 3!"; setTimeout(() => { title = "THIS IS A NEW TITLE"; }, 5000); return { title }; } }; </script> Мы можем импортировать ref и использовать ее, чтобы сделать свойство реактивным. Под капотом Vue 3 создаст Proxy. <template>
<h1>{{ title }}</h1> </template> <script> import { ref } from "vue"; export default { setup() { const title = ref("Hello, Vue 3!"); setTimeout(() => { // может возникнуть вопрос, что за .value ... // об этом позже title.value = "New Title"; }, 5000); return { title }; } }; </script> Хочу пояснить, что говоря о Ref и Reactive, мне кажется, есть два разных случая. Первый — создание компонента как в примере выше и вам нужны реактивные свойства. Второй — когда вы создаете функции позволяющие композицию, чтобы использовать их в компонентах и функциях. В статье мы обсудим оба сценария. REF При создании свойств простых типов, ref() ваш первый выбор. Это не серебряная пуля, но с это стоит начать. Напомню вам семь примитивных типов в JavaScript:
import { ref } from "vue";
export default { setup() { const title = ref(""); const one = ref(1); const isValid = ref(true); const foo = ref(null); } }; В предидущем примере у нас title имеет тип String, значит чтобы сделать свойство реактивным выбираем ref(). Если код ниже вызывает у вас некоторые вопросы не беспокойтесь, у меня были такие же вопросы. import { ref } from "vue";
export default { setup() { const title = ref("Hello, Vue 3!"); setTimeout(() => { title.value = "New Title"; }, 5000); return { title }; } }; Почему мы используем const, если title будет меняться? Почему не использовать let? Если вы выведете title в консоль, вы могли бы ожидать увидеть Hello, Vue 3!, но вместо это отобразится объект: {_isRef: true}
value: (...) _isRef: true get value: ƒ value() set value: ƒ value(newVal) __proto__: Object Функция ref() примет аргумент и вернет реактивный и изменяемый ref объект. Ref объект имеет одно свойство value, ссылающееся на аргумент. Это означает, что если вы хотите получить или изменить значение надо будет использовать title.value, а так как это объект, который не будет меняться, я и объявил его const. Обращение к REF Следующий вопрос — а почему в шаблоне мы не обращаемся title.value? <template>
<h1>{{ title }}</h1> </template> Когда Ref возвращается как свойство в контекст отрисовки (объект возвращаемый функцией setup()) и идет обращение к нему в шаблоне, Ref автоматически возвращает value. В шаблоне добавлять .value не нужно. Вычисляемые свойства — computed properties — работают так же, внутри функции setup() обращаться к ним так .value.
REACTIVE Мы только что рассмотрели применение ref() для задания реактивности свойств простых типов. А что будет, если мы хотим создать реактивный объект? Мы по прежнему могли бы использовать ref(), но под капотом Vue будет использовать reactive(), так что я буду придерживаться использования reactive(). С другой стороны reactive() не сработает с примитивными типами. Функция reactive() принимает объект и возвращает реактивный прокси оригинала. Это эквивалентно .observable() во Vue 2, и это имя функции было изменено во избежание путаницы с observables в RxJS. import { reactive } from "vue";
export default { setup() { const data = reactive({ title: "Hello, Vue 3" }); return { data }; } }; Главное отличие в том, как мы обращаемся к реактивному объекту в шаблоне. В предидущем примере data это объект содержащий свойство title. Вам надо будет обращаться к нему в шаблоне так — data.title: <template>
<h1>{{ data.title }}</h1> </template> <script> import { ref } from "vue"; export default { setup() { const data = ref({ title: "Hello, Vue 3" }); return { data }; } }; </script> Разница между REF и REACTIVE в компоненте Исходя из того, что мы обсудили, ответ казалось бы напрашивается? Используем ref() для простых типов, и reactive() для объектов. Когда я начал создавать компоненты, выяснилось, что это всегда не так, и документации говорится: Разница между применением ref и reactive может быть, в какой-то мере, сравнима с тем, как вы пишете стандартную логику программы на JavaScript.
Я размышлял над этой фразой и пришел к следующим выводам. По мере роста приложения у меня появились следующие свойства: export default {
setup() { const title = ref("Hello, World!"); const description = ref(""); const content = ref("Hello world"); const wordCount = computed(() => content.value.length); return { title, description, content, wordCount }; } }; В JavaScript я бы понял, что это все свойства моей страницы. В этом случае я бы сгруппировал бы их в объект page, почему бы не сделать тоже самое сейчас. <template>
<div class="page"> <h1>{{ page.title }}</h1> <p>{{ page.wordCount }}</p> </div> </template> <script> import { ref, computed, reactive } from "vue"; export default { setup() { const page = reactive({ title: "Hello, World!", description: "", content: "Hello world", wordCount: computed(() => page.content.length) }); return { page }; } }; </script> Это мой подход к вопросу Ref или Reactive, но было бы не плохо узнать ваше мнение. Делаете-ли вы также? Может это не правильный подход? Пожалуйста комментируйте. Логика композиции Вы не сможете ошибиться при использовании ref() или reactive() в ваших компонентах. Обе функции создадут реактивные данные, и если вы понимаете как к ним обращаться в вашей функции setup() и в шаблонах не возникнет каких-либо сложностей. Когда же вы начнете писать композиционные функции вам надо будет понимать разницу. Я использую примеры из RFC документации, так как одни хорошо иллюстрируют нюансы. Вам надо создать логику отслеживания положения мыши. Вы также надо иметь возможность использовать эту-же логику в любом компоненте, когда это нужно. Вы создаете композиционную функцию, которая отслеживает координаты x и y, и затем возвращает их клиентскому коду. import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() { const x = ref(0); const y = ref(0); function update(e) { x.value = e.pageX; y.value = e.pageY; } onMounted(() => { window.addEventListener("mousemove", update); }); onUnmounted(() => { window.removeEventListener("mousemove", update); }); return { x, y }; } Если вы хотите использовать эту логику в компоненте и вызываете эту функцию, то деструктурируйте возвращаемый объект и затем возвращайте x и y координаты в шаблон. <template>
<h1>Use Mouse Demo</h1> <p>x: {{ x }} | y: {{ y }}</p> </template> <script> import { useMousePosition } from "./use/useMousePosition"; export default { setup() { const { x, y } = useMousePosition(); return { x, y }; } }; </script> Это будет работать, но если, поглядев на функцию, вы решили провести рефакторинг и возвращать объект position вместо x и y: import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() { const pos = { x: 0, y: 0 }; function update(e) { pos.x = e.pageX; pos.y = e.pageY; } // ... } Проблема этого подхода в том, что клиент композиционной функции должен иметь ссылку на возвращаемый объект всегда, чтобы реактивность сохранялась. Это означает, что мы не можем деструктурировать объект или применить spread оператор: // компоте клиент
export default { setup() { // потеря реактивности! const { x, y } = useMousePosition(); return { x, y }; // потеря реактивности! return { ...useMousePosition() }; // это единственный способ сохранить реактивность // надо возвращать `pos` как есть, и обращаться в шаблоне к x и y так: `pos.x` и `pos.y` return { pos: useMousePosition() }; } }; Но это не означает, что мы не можем в таком случае использовать reactive. Есть функция toRefs(), которая сконвертирует реактивный объект в простой объект, каждое свойство которого это ref соответствующего свойства оригинального объекта. function useMousePosition() {
const pos = reactive({ x: 0, y: 0 }); // ... return toRefs(pos); } // x & y теперь ref! const { x, y } = useMousePosition(); Таким образом надо учитывать некоторые аспекты при создании композиционных функций. Если вы понимаете, как клиентский код работает с вашими функциями, то все будет ОК. ИТОГ Когда я впервые начал использовать Composition API, я был озадачен вопросом, когда применять ref(), а когда reactive(). Возможно я до сих пор делаю это не правильно, и пока кто-нибудь не скажет мне этого, я буду придерживаться этого подхода. Надеюсь помог прояснить некоторые вопросы, и хотелось бы услышать ваше мнение. Happy Coding =========== Источник: habr.com =========== =========== Автор оригинала: Dan Vega ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:17
Часовой пояс: UTC + 5