I have a collection of static data that I want to access in some of my Vue components. Example:
COLORS = Object.freeze({
RED: 1,
GREEN: 2,
BLUE: 3,
})
FLAVOURS = Object.freeze({
VANILLA: 'vanilla',
CHOCOLATE: 'chocolate',
})
I'm working with single file components.
I want to access those constants both in component template and in JS code (i.e. in data()).
I don't want them to be reactive.
If possible, I want them to be instantiated only once (not copying each constant into each component instance).
I don't currently use Vuex, but I'll consider it if it leads to more elegant solution.
I tried to solve my problem using mixin:
// ColorMixin.js
export const COLORS = Object.freeze({
RED: 1,
GREEN: 2,
})
export const ColorMixin = {
created() {
this.COLORS = COLORS
}
}
Then, in my component I have to use that mixin and also the constants:
<template>
<input name="red" :value="COLORS.RED" />
<input name="green" :value="COLORS.GREEN" />
</template>
<script>
import {COLORS, ColorMixin} from './ColorMixin.js'
export default {
mixins: [ColorMixin],
data() {
return {
default_color: COLORS.RED,
}
}
}
</script>
This works, but it seems kind of repetitive. Is there a more elegant solution for my problem?
How about just using a global mixin ?
// import your constants
Vue.mixin({
created: function () {
this.COLORS = COLORS;
}
})
You do not just import the exported var from your Color.js file into correct SFC ?
COLORS = Object.freeze({
RED: 1,
GREEN: 2,
BLUE: 3,
});
FLAVOURS = Object.freeze({
VANILLA: 'vanilla',
CHOCOLATE: 'chocolate',
});
export {COLORS, FLAVOURS};
and then in your SFC
<template>
<input name="red" :value="default_color" />
</template>
<script>
import {COLORS, FLAVOURS} from './Color.js';
export default {
data() {
return {
default_color: COLORS.RED,
default_flavour: FLAVOURS.CHOCOLATE,
}
}
}
</script>
Or just create a Vuex store to save this datas and use it directly from each SFC
Related
vue is throwing this message:
Vue received a Component which was made a reactive object. This can
lead to unnecessary performance overhead, and should be avoided by
marking the component with markRaw or using shallowRef instead of
ref.
<template>
<component v-for="(el, idx) in elements" :key="idx" :data="el" :is="el.component" />
</template>
setup() {
const { getters } = useStore()
const elements = ref([])
onMounted(() => {
fetchData().then((response) => {
elements.value = parseData(response)
})
})
return { parseData }
}
is there a better way to do this?
First, you should return { elements } instead of parseData in your setup i think.
I solved this issue by marking the objects as shallowRef :
import { shallowRef, ref, computed } from 'vue'
import { EditProfileForm, EditLocationForm, EditPasswordForm} from '#/components/profile/forms'
const profile = shallowRef(EditProfileForm)
const location = shallowRef(EditLocationForm)
const password = shallowRef(EditPasswordForm)
const forms = [profile, location, password]
<component v-for="(form, i) in forms" :key="i" :is="form" />
So you should shallowRef your components inside your parseData function. I tried markRaw at start, but it made the component non-reactive. Here it works perfectly.
you could manually shallowCopy the result
<component v-for="(el, idx) in elements" :key="idx" :data="el" :is="{...el.component}" />
I had the same error. I solved it with markRaw. You can read about it here!
my code :
import { markRaw } from "vue";
import Component from "./components/Component.vue";
data() {
return {
Component: markRaw(Component),
}
For me, I had defined a map in the data section.
<script>
import TheFoo from '#/TheFoo.vue';
export default {
name: 'MyComponent',
data: function () {
return {
someMap: {
key: TheFoo
}
};
}
};
</script>
The data section can be updated reactively, so I got the console errors. Moving the map to a computed fixed it.
<script>
import TheFoo from '#/TheFoo.vue';
export default {
name: 'MyComponent',
computed: {
someMap: function () {
return {
key: TheFoo
};
}
}
};
</script>
I had this warning while displaying an SVG component; from what I deduced, Vue was showing the warning because it assumes the component is reactive and in some cases the reactive object can be huge causing performance issues.
The markRaw API tells Vue not to bother about reactivity on the component, like so - markRaw(<Your-Component> or regular object)
I also meet this problem today,and here is my solution to solve it:
setup() {
const routineLayoutOption = reactive({
board: {
component: () => RoutineBoard,
},
table: {
component: () => RoutineTable,
},
flow: {
component: () => RoutineFlow,
},
});
}
I set the component variant as the result of the function.
And in the ,bind it like compoennt()
<component
:is="routineLayoutOption[currentLayout].component()"
></component>
new to Vue - learning as I go about exploring.
I'm trying to use a custom object as data source for a v-for situation. So far, not working - I admit I'm not sure how to access data by name in a Vue component.
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import VueKonva from "vue-konva";
Vue.use(VueKonva);
import RoomBuilder from "#/model/RoomBuilder";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App),
data: () => ({
roomBuilder: new RoomBuilder()
})
}).$mount("#app");
The custom object, which may not be wired right into the data provider, is roomBuilder.
Then, in the component where I want to reference this object, I have this code:
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-rect ref="background" :config="backgroundConfig"></v-rect>
<v-rect
v-for="room in roomBuilder.getRooms()"
:key="room.id"
:config="room.config"
></v-rect>
</v-layer>
</v-stage>
</template>
<script>
export default {
name: "RoomGrid",
data() {
return {
stageSize: {
width: 1000,
height: 1000
},
backgroundConfig: {
width: 50,
height: 50,
fill: "#2B2B2B"
}
};
},
mounted() {
this.$nextTick(function() {
window.addEventListener("resize", this.fitStageIntoParentContainer);
this.fitStageIntoParentContainer();
});
},
methods: {
fitStageIntoParentContainer() {
const parentElement = this.$parent.$el;
const background = this.$refs.background.getNode();
background
.setAttr("width", parentElement.clientWidth)
.setAttr("height", parentElement.clientHeight);
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss"></style>
The two POJOs are:
RoomBuilder.js
import Room from "#/model/Room";
class RoomBuilder {
construct() {
this.rooms = [new Room(1)];
}
drawOnto(konvaStage) {
console.log(konvaStage);
}
getRooms() {
return this.rooms;
}
}
export default RoomBuilder;
and
Room.js
class Room {
construct(id) {
this.id = id;
this.config = {
x: 10,
y: 10,
fill: "white",
width: 10,
height: 10
};
}
}
export default Room;
Thanks, and go easy on me. I'm typically a server-side guy! I clearly have wired something wrong... have tried
v-for="room in this.$data.roomBuilder.getRooms()"
v-for="room in roomBuilder.getRooms()"
etc.
Clearly there's some magic afoot I don't understand!
Thank you!
Wondering what is the best practice for such code here and what is the difference when cloning an object inside script tag or doing it in data property:
<script>
import {cloneDeep} from "lodash";
import {INVITE_USER_FORM_FIELDS} from './data';
const FORM_FIELDS = cloneDeep(INVITE_USER_FORM_FIELDS);
export default {
name: "ModalInviteCreate",
data() {
return {
FORM_FIELDS,
};
},
OR
<script>
import {cloneDeep} from "lodash";
import {INVITE_USER_FORM_FIELDS} from './data';
export default {
name: "ModalInviteCreate",
data() {
return {
FORM_FIELDS: cloneDeep(INVITE_USER_FORM_FIELDS),
};
},
The difference is the data method will run every time you create a new instance of that component. If you never need to recompute a deep clone, then option 1 is preferable since the extra clones are a waste. If you're bothering to create a deep clone though, I'm guessing it's so your components can safely mutate the object without modifying the original. So option 2 is probably the best choice, otherwise all of the component instances would all share the same object.
Here's an example to illustrate, see the console.logs.
const fakeDeepClone = name => {
console.log(`creating data for component ${name}`);
return { name };
}
const aData = fakeDeepClone('a');
const componentA = {
template: '<div>name: {{name}}</div>',
data() {
return aData
}
}
const componentB = {
template: '<div>name: {{name}}</div>',
data() {
return fakeDeepClone('b')
}
}
var app = new Vue({
el: '#app',
components: {
componentA,
componentB
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>
</div>
(I'm new to vue and nuxt).
I currently have a <HeaderImage> component in my layouts/default.vue and would like to have each page to pass a different image url to that component.
Right now I'm using vuex $store for that purpose (but would love if there were a simpler way to pass the data), but I'm trying to figure out where in my pages/xyz.vue I should be using the mutation this.$store.commit('headerImg/setHeaderImage', 'someImage.jpg')
All of the examples I can find only use mutations on user events.
What you are trying to do probably doesn't have a particularly simple solution and how I would do it is use a store state element that is set by the component when it is loaded. The component would commit a mutation in the store that alters the state element. The layout would then use that state element through a getter to set the image url. Here is how I'd code that. In the store state i'd have an array of class names, let's call it 'headState', and an element that would be assigned one of those class names, called 'headStateSelect:
//store/index.js
state: {
headState: ['blue', 'red', 'green'],
headStateSelect : ''
}
In your component you can use fetch, or async fetch to commit a mutation that will set 'headStateSelect' with one of the 'headState' elements.
//yourComponent.vue
async fetch ({ store, params }) {
await store.commit('SET_HEAD', 1) //the second parameter is to specify the array position of the 'headState' class you want
}
and store:
//store/index.js
mutations: {
SET_HEAD (state, data) {
state.headStateSelect = state.headState[data]
}
}
In the store we should also have a getter that returns the 'headStateSelect' so our layout can easily get it.
getters: {
head(state) {
return state.headStateSelect
}
}
finally, in the layout we can use the computed property to get our getter:
//layouts/default.vue
computed: {
headElement() {
return this.$store.getters.head
}
}
and the layout can use the computed property to set a class like so:
//layouts/default.vue
<template>
<div :class="headElement">
</div>
</template>
The div in the layout will now be set with the class name 'red' (ie. store.state.headState[1]) and you can have a .red css class in your layout file that styles it however you want, including with a background image.
For now I've settled on creating it like this:
~/store/header.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = () => ({
headerImage: 'default.jpg'
})
const mutations = {
newHeaderImage(state, newImage) {
state.headerImage = newImage
}
}
export default {
namespaced: true,
state,
mutations
}
``
~/layouts/default.vue
<template>
<div id="container">
<Header />
<nuxt />
</div>
</template>
<script>
import Header from '~/components/Header'
export default {
components: {
Header
}
}
</script>
``
~/components/Header.vue
<template>
<header :style="{ backgroundImage: 'url(' + headerImage + ')'}" class="fixed">
<h1>Header Text</h1>
</header>
</template>
<script>
computed: {
var image = this.$store.state.header.headerImage
return require('~/assets/img/' + image)
}
</script>
``
~/pages/customHeader.vue
<template>
<main>
...
</main>
</template>
<script>
export default {
head() {
this.$store.commit('header/newHeaderImage', 'custom-header.jpg')
return {
title: this.title
}
}
}
</script>
But something feels off about putting the mutation in head() Is that correct?
And the next issue I am facing is how to return the header to default.jpg if a page doesn't change the state (which makes me think this is all the wrong approach).
I'm trying to use Chart.js with Vue.js and this is what I got it is compiling but I don't see anything displayed on the GUI.
This is my file DonutChart.vue:
<template>
// NOT SURE IF SOMETHING SHOULD GO HERE
</template>
<script>
import {Bar} from 'vue-chartjs'
// import the component - chart you need
export default Bar.extend({
mounted () {
// Overwriting base render method with actual data.
this.renderChart({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [
{
label: 'News reports',
backgroundColor: '#3c8dbc',
data: [12, 20, 12, 18, 10, 6, 9, 32, 29, 19, 12, 11]
}
]
},)
}
});
</script>
This is the parent component, ´Usage.vue´:
<template>
<h1>USAGE</h1>
<st-donut-chart></st-donut-chart>
</template>
<script>
import Vue from 'vue';
import Filter from './shared/filter/Filter';
import DonutChart from './DonutChart'
export default new Vue({
name: 'st-usage',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
components: {
'st-filter': Filter,
'st-donut-chart': DonutChart,
}
});
</script>
DonutChart.vue and Usage.vue are on the same directory:
vue-chartjs author here.
Well it's a bit confusing for beginners. However vue-chartjs is utilizing Vue.extend().
That's why you're have to extent the imported component.
Step 1
Create your own component and extend the base chart. This way you have more control over everything.
Your DonutChart.vue was nearly right. But you have to remove the <template> from your component. Because of the Vue.extend() you're extending the base component. So you have access to the props, methods etc. defined there. However there is no way of extending templates. So if you let the template tag in your component, it will overwrite the template defined in the base chart, that you're extending. Thats why you can't see anything ;)
YourChart.vue:
<script>
// Import the base chart
import {Bar} from 'vue-chartjs'
// Extend it
export default Bar.extend({
props: ['chartdata', 'options'],
mounted () {
// Overwriting base render method with actual data and options
this.renderChart(this.chartdata, this.options)
}
})
</script>
Now you have the Chart Component. You can put more login in there, of define some styles or options.
Step 2
Import it and feed it the data.
Like you did :)
Update
With version 3 of vue-chartjs the creation has changed. Now its more vue-like.
<script>
// Import the base chart
import {Bar} from 'vue-chartjs'
// Extend it
export default {
extends: Bar,
props: ['chartdata', 'options'],
mounted () {
// Overwriting base render method with actual data and options
this.renderChart(this.chartdata, this.options)
}
}
</script>
Or you can use mixins
<script>
// Import the base chart
import {Bar} from 'vue-chartjs'
// Extend it
export default {
mixins: [Bar],
props: ['chartdata', 'options'],
mounted () {
// Overwriting base render method with actual data and options
this.renderChart(this.chartdata, this.options)
}
}
</script>
So now I got it working:
import DonutChart from './DonutChart'
export default ({ //<= Notice change here
name: 'st-usage',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
components: {
'st-filter': Filter,
'line-example':LineExample,
'st-donut-chart':DonutChart,
}
});