I need to have a reactive global variable in Vue. The variable is simply a boolean that tells me whether the user is on a mobile device. I have tried so many things but this is the last thing I tried:
Vue.prototype.$testIsMobile = false
const mobileMediaMatch = window.matchMedia('(max-width: 768px)')
Vue.prototype.$testIsMobile = mobileMediaMatch.matches
window.addEventListener('resize', function () {
console.log("resizeeeee: " mobileMediaMatch.matches)
Vue.prototype.$testIsMobile = mobileMediaMatch.matches
}, true)
Now this will get triggered when I reszie my screen because I can see the text resizeeeee
getting logged repeatedly to the console. The problem is that when I use the variable $testIsMobile
in other components, the variable is not reactive. It does not re-render the page accordingly until I refresh the page manually. How can I make this variable fully reactive so that any component can use it and it contains the correct value?
Here is an example of how I use it in a component:
<div>--{{$testIsMobile}}--</div>
CodePudding user response:
You are not declaring a reactive property.
let a = true
Vue.prototype.$test = a
a = false
This is how you expect things to work based on the OP. Vue has no way to tell if a value changed.
Declare it in the data
property. Upon initializing a Vue instance, Vue has added its reactivity magic and can now tell if the value changed
Docs: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
Since Vue doesn’t allow dynamically adding root-level reactive properties, you have to initialize Vue instances by declaring all root-level reactive data properties upfront, even with an empty value:
var vm = new Vue({ data: { // declare message with an empty value message: '' }, template: '<div>{{ message }}</div>' }) // set `message` later vm.message = 'Hello!'
If you don’t declare message in the data option, Vue will warn you that the render function is trying to access a property that doesn’t exist.
There are technical reasons behind this restriction - it eliminates a class of edge cases in the dependency tracking system, and also makes Vue instances play nicer with type checking systems. But there is also an important consideration in terms of code maintainability: the data object is like the schema for your component’s state. Declaring all reactive properties upfront makes the component code easier to understand when revisited later or read by another developer.
CodePudding user response:
Although there's quite a bit of overlap,
- detecting mobile devices is mostly about detecting touch devices
- matching media queries is about testing if a CSS media query currently matches
If you want to detect touch devices, I suggest isMobile (or similar). It's not reactive, because (normally) the device does not change. (e.g: if emulating - therefore changing device on the fly - you need to reload the page to reapply detection.
Vue 2 implementation:
Vue.use({
install(Vue) {
Vue.prototype.$isMobile = isMobile
}
})
new Vue({ el: '#app' })
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ismobilejs@1/dist/isMobile.min.js"></script>
<div id="app">
<pre v-text="JSON.stringify($isMobile, null, 2)"></pre>
</div>
To detect if a media matches in real time, you could roll your own, but it's already done: vue-component-media-queries (for Vue 2):
const { MatchMedia, MediaQueryProvider } = VueComponentMediaQueries;
const baseQueries = [
{ xs: { max: 539 } },
{ sm: { min: 540, max: 767 } },
{ md: { min: 768, max: 991 } },
{ lg: { min: 992, max: 1199 } },
{ xl: { min: 1200, max: 1499 } },
{ xxl: { min: 1500 } },
];
new Vue({
el: "#app",
components: {
MatchMedia,
MediaQueryProvider,
},
data: () => ({
queries: Object.assign(
{
portrait: "(orientation: portrait)",
landscape: "(orientation: landscape)",
},
...Object.entries(Object.assign({}, ...baseQueries)).map(
([key, val]) => ({
[key]: [
val.min ? `(min-width: ${val.min}px)` : "",
val.max ? `(max-width: ${val.max}px)` : "",
]
.filter((o) => o)
.join(" and "),
...(val.min && val.max
? {
["min-" key]: `(min-width: ${val.min}px)`,
["max-" key]: `(max-width: ${val.max}px)`,
}
: {}),
})
)
),
}),
});
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/index.js"></script>
<div id="app">
<media-query-provider :queries="queries">
<match-media #default="matches">
<template v-for="(value, key) in matches">
<div v-if="value" :key="`${key}-true`" :style="{color: '#275'}">
<b v-text="key"></b>
<code v-text="queries[key]"></code>
</div>
<div v-else :key="`${key}-false`" :style="{color: '#930'}">
<b v-text="key"></b>
<code v-text="queries[key]"></code>
</div>
</template>
</match-media>
<hr />
<!-- Simpler syntax (but the same thing, in essence): -->
<match-media v-slot="{ md }">
<div v-if="md">md</div>
<div v-else>not md</div>
</match-media>
</media-query-provider>
</div>
Look at the simpler syntax at the bottom.
Define as many queries as you want. They're all be available on <MediaQuery />
's v-slot
(e.g: #default="{ someQuery }"
)