I'm really confused with how data works in single file components for VueJS. Within the file, say test.vue, as I understand, you would write out a script something like this:
export default {
name: 'Testapp',
data () {
return {
msg: 'sample message'
}
}
}
then elsewhere, say in a file called vuescript.js I would put something like following in and call it from an html file:
import Vue from 'vue'
import VApp from './test.vue'
var vueApp = new Vue({
el: '#app',
render: h => h(VApp)
})
Now how do I access that template object's data? What I'd like is to have code elsewhere that fetches data from a server and can be shared across multiple components, so I'd have a portion of data that is common across and updates from a single repository, but also I'd be able to have unshared data residing within a component for certain things like settings and other meta data.
BLUF: I'm kind of stuck after looking around a bit on how data is accessed / is handled within Vue single file components.
Data inside components should only be accessed outside of themselves in 2 ways. Either an event propagated the data upwards to a parent which can then decide if it needs to be passed to another component as a prop. Or it is stored in Vuex and accessed through getters and mutations.
Links
Component Props
Events
Vuex Example
If you want your data property to be shared by multiple components, you can use mixins.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
https://v2.vuejs.org/v2/guide/mixins.html
Related
I don't need to pass data between a parent component to a child one or the opposite, I need something like php/c static variables.
I want my sfc (single file component) to have some data that is shared among all instances in in the page.
As far as I understand that's why in sfc we define data as a function
export default {
data(){
return {
// props here
};
}
}
while in page scripts we can define it as an object
const app = new Vue({
data: {
// props here
},
}
That's because since we can have multiple instances of a sfc in the page defining its data as a function make each instance to execute in and get its own data, while with page script we can have a singe instance.
I need to define some of my sfc data to be shared between component instances, while other data to be per-instance.
Is there a way to do this?
That depends on the data to be defined, its complexity, and purpose.
If these are 2 or 3 readonly variables, they can be set as global properties using Vue.prototype (Vue 2) or app.config.globalProperties (Vue 3). I'm not sure, because in your example you use Vue 2 syntax.
If the data should be reactive, you can set up a simple state management as explained in the Vue documentation: Simple state management.
If the data is more complex than that, the next step will be Vuex.
Following #Igor answer I looked after the simple state management and found the ref() method that creates reactive primitive values.
In my specific use case I needed to share among all the sfc instances just an array, so in my sfc I had:
const reactive_array = ref([]);
export default {
data() {
return {
shared_array: reactive_array,
};
},
};
I have a page which is being generated by my app in java (SSR) depending on some data (e.g. a publisher for some entity). I would like to create some sort of a reusable Vue component that would call an API method and request some data about the entity that is currently opened. Also in some cases there could be more than one such component on one page.
The only thing I cannot really figure out being a most-of-the-time backend developer - is how to tell a component which entity I'm trying to get. The only solution that comes to my mind is to generate the parent <div class="my-vue-component"><div> with an additional attribute, e.g. <div class="my-vue-component" publisher-id="123"><div>.
But I cannot find if there is a way to access that attribute from inside the Vue instance. (Please note that I don't have a fixed id for this div as there can be many such components on the same page referring to different data).
Any kind of advice is appreciated.
As stated in the previous answer, you will need to use props. Although since you will pass down data to multiple components and the data can change, there should be a way to respond to those changes.
For that, you will have to bind the prop with a reactive variable in your page/parent component.
So your SSR code should look like
<my-vue-component :publisher-id="openId"></blog-post>
And inside your page/parent component will reside the openId, which you can change as needed, and your component will re-render if prop passed to it changes.
export default {
data(){
return {
openId:1
}
}
}
It seems like you are looking for components-props.
You can define a prop like
Vue.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
Your SSR code should then be generating:
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>
Inside the component methods you can access the passed in value using this.postTitle
I'm currently looking at implementing Context into one of our apps over Redux, but, I can't seem to find any information on what would be the best structure for large scale apps?
Redux has a defined way to create reducers, actions, etc. With Context, all I've found are the generic "create a provider, put state and methods all on the same file, and then use a consumer".
TL;DR Is there a way to build a hiarchy that is beneficial for long term, and large scale applications with React Context?
Edit: I guess this is incorrect to think of them having a similar structured relationship. Unfortunately, I'm not able to use Redux because of AEM's limitations. Context does work however, so I wanted to hopefully be able to build some structure with that.
First of all, I don't think there is necessarily a right or wrong answer to this question, but I will just give you my two cents.
I am currently refactoring a web application which serves several millions of sessions per month and am testing a redux and context version on internal stage servers.
Important notices:
I am using a mono-store approach
It's not an app which constantly has global store updates
To the folder structure. I like to keep my store in the root of the project. For a react app based on react-create-react-app that would be the /src and it basically consists of the following files:
index.js // everything gets "bundled" here
initialState.js // provides the store with intial state e.g. from server, cache etc.
methods/*.js // contains split methods based on the part of the app that they are used in (if it can be split into separate parts)
Ergo my index.js is as simple as:
import React from 'react';
import storeMethods from './methods';
import initialState from './initialState';
// to start of experimenting with context
// i would keep all read and write key value
// pairs right here and split as the codebase
// grows and you realize you need more space
export const store = {
...initialState,
...storeMethods
}
export const StoreContext = React.createContext(store)
storeMethods is a bundled export from all methods in the methods/ folder. Basically it's just another object of containing keys which values are functions like so:
export const methods = {
showNavBar: function() {
this.setState({ navBarOpen: true })
}
}
initialState is as much as the representation of key value pairs that are required to render the base content of the app and or never change. Basically some global settings. Initialstate coming from the server, is being added to the store in the constructor of my App, right before I bind the lexical scope.
The store get's thrown into the state of the relevant outermost React Component and is used as the app state, where I bind the store's scope to the React Components lexical scope.
Then I have a higher order component withContextConsumer which is used to wrap any React component which needs access to the state. The HOC distributes the subscribed keys down as props to the wrapped component and can be consumed as read only or write.
No matter how you end up using Context, don't forget, that any Consumer will have it's render method automatically called if the Context Store is being updated. To avoid that on a simple oldProps !== newProps level, you can use PureComponents. For more complex diffs you can use the lifecyclemethod shouldComponentUpdate
edit
Basic App Structure
App.js:
import React, { PureComponent } from 'react'
import { StoreContext, store } from './store'
import { bindScopeToFunction } from './helpers'
class App extends PureComponent {
constructor(props) {
super(props)
const { initialState = {} } = props
const boundStore = bindScopeToFunction(store, this)
this.state = {...boundStore, ...initialState}
}
render () {
return(
<StoreContext.Provider value={this.state}>
// in here you render all your app
// routing, childcomponents etc
// in any component where you need access
// to the global store
// wrap it in <StoreContext.Consumer> it has
// the whole store as render prop
</StoreContext.Provider>
)
}
}
Working basic example can be found here https://codesandbox.io/s/pm85w4y6xm
I am searching for the best approach to change Vue variable value outside of component. I'm using Vue webpack as well.
I have created a project using vue webpack.
Inside its default App.vue file, I have a variable. For example, let's take showModal and its default value is false.
Then I built it in a single javascript file.
<button>Register</button> {{-- event button --}}
<div id="guest"></div> {{-- Here I'm rendering my template --}}
<script src="{{ asset('js/vue-js/guest.js') }}"></script> {{-- builded Javascript file --}}
And the problem is that I want to change my showModal variable to true, but the event button it is not on my component template.
What is the best approach to accomplish this?
If you want to access a vue component outside of vue you could register it to the window object and access it then from anywhere.
window.myVueComponent = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Now you can access it from anywhere else with
window.myVueComponent.myValue = 123
But this "style" is called global namespace pollution for reasons.
;)
I think it is better to extend your vue app so that the button is also within the vue-handled components.
Firstly, best approach wise it's prevalent to think about the relationships between your existing components and their relationships. So for instance if the information your trying to pass will be used in a direct sibling or further down the chain you could choose props.
If your dealing with two components that share no direct relationship other than there current state you will need to extrapolate to either using the repository pattern or Vuex (flux like state management library) where we can then pass a reference to state or into properties in the repository pattern.
FooRepository.js
export default class FooRepository {
SomeRef
ManyRef = []
addRef(name) {
this.someRef = name;
}
addRefs(names){
this.ManyRef.push(names);
}
}
The above can be instantiated in your App Layer and shared between your components using an instance property https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
Dependent on your apps size it might be time to include Vuex where we can save a reference directly into our state and use it in a simmilar manner as the repo pattern. Though as it's an officially supported package the setup and use is much simpler:
const store = new Vuex.Store({
state: {
ref: null
},
mutations: {
saveRef (state, compRef) {
state.ref = compRef
}
}
})
This is a very basic store that shows how we could save a reference into it, we can then access this reference in our components once we've registered the store inside main. This can be done using this.$store.state.ref. These two approaches are considered the best approach over simple simple props and or something like the event emitter for components that share no direct relationship.
Create a new Vue instance just for emitting, call it global event manager.
global.event = new Vue()
when you want to emit an event ( like modal )
global.event.$emit('close-modal', (optional msg))
when you want to close modal :
// In your component
created(){
var App = this;
global.event.$on('close-modal', (optional msg)=>{
this.showModal = false;
})
}
Similarly do it for opening the modal. If you are using the vue CDN (normal js file of vue), instead of global.event use window.event while creating and only event while using. In browser if a variable which is undeclared is used then it refers to the window object.
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0