Difference between revisions of "Vue.js"

From Colettapedia
Jump to navigation Jump to search
m (Ccoletta moved page Modern Web Development to Vue.js)
 
(25 intermediate revisions by one other user not shown)
Line 1: Line 1:
==2019 Tech Stack==
+
 
===Vue.js===
+
==Vue.js==
====Using Vue CLI====
+
===Files===
 +
====index.html====
 +
* has <code><div id="app"</code> and script includes main.js
 +
====main.js====
 +
* contains instantiation of Vue instance
 +
<pre>
 +
import Vue from 'vue';
 +
import App from './App.vue';
 +
import store from './store';
 +
 
 +
new Vue( {
 +
    store,
 +
    render: h => h(App)
 +
}).$mount( '#app' );
 +
</pre>
 +
 
 +
====App.vue====
 +
* top level component into which we compose other components
 +
* App-level arg array contains: <code>const app = new Vue({</code>
 +
** el: "#app" - el stands for element, maps back to the div in index.html
 +
** data
 +
** computed - contains methods. One-off computations in our dataset. Get access to the data using $this
 +
** filters - also contains methods that take arguments. Pass arguments to filter method inside the mustache using the pipe separator
 +
** methods - can take an argument
 +
** lifecycle methods
 +
*** beforeCreate
 +
*** mounted() fires when your app attaches to the DOM, a good time to fetch some data
 +
*** beforeDestroy()
 +
====router/index.js====
 +
* Entrypoint into Vue Router
 +
====store/index.js====
 +
* Entrypoint into Vuex
 +
 
 +
===Directives===
 +
* directive inside HTML tags start with v-
 +
* <code>v-bind:____="someDataStoreObj</code> - bind keeps something up-to-date with some data store property
 +
* <code>v-if="varName"</code>
 +
* <code>v-for:"i in datastoreobj"</code>
 +
* <code>v-on:click="someMethod"</code> - binds a function to button
 +
* <code><input v-model="message"></code> - direct two-way binding between input and app state
 +
===Component===
 +
* How you create a new HTML tag, you must register them
 +
* Template can only return one top level element, if you need to return two, wrap within a div.
 +
* <code>computed</code> properties of a component are just like regular data members
 +
** Can be declared methods, but call as if they were attributes.
 +
* Component scaffold has template, script, and style
 +
* <code>export default { </code>
 +
** props: ['id, 'age', 'weight] }
 +
** name = "component_name"
 +
** components: [ subcomponent1, subcomponent2 ]
 +
* <code><style scoped></code>
 +
 
 +
===Vue Instance===
 +
 
 +
==Vuex==
 +
===Cheat sheet===
 +
* dispatch actions, commit mutations
 +
===Concepts===
 +
* State management library
 +
* Extract shared state out of components and manage it in a global singleton
 +
* Use when you do a lot of passing data from one component to another through props.
 +
* When you don't want your data all over the place
 +
* Vuex stores are REACTIVE, meaning when vue components retrieves state from it, they will reactively and efficiently update the store's state changes.
 +
* You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. Can log every mutation, take state snapshots, or perform time travel debugging.
 +
* Modules - encapsulating/separating out logic for different components
 +
* E.g., whenever <code>store.state.count</code> changes, it will cause the computed property to re-evaluate, and trigger associated DOM updates.
 +
* By using <code>Vue.use(Vuex)</code> and passing the <code>store</code> augument to the root instance, any component can get at the store by calling <code>this.$store</code>
 +
 
 +
===Flux pattern concepts===
 +
* flux pattern concept created by Facebook
 +
* All you technically need is an event system
 +
** Unidirectional dataflow, because all actions go through the same dispatcher
 +
** All components will know when data has changed and con update appropriately
 +
*# All starts with an action
 +
*# Calls a dispatcher to propagate an event
 +
*# Puts the result into a store
 +
*# Store emits an event to tell components that are subscribed to it that the data has changed
 +
*# The views consume the data from the store
 +
* Many implementations. Redux is another example of Flux pattern but for React framework
 +
 
 +
===Four types of entities in the modules===
 +
 
 +
==== <code>state</code> ====
 +
* put data members in here as an object
 +
 
 +
==== <code>getters</code> ====
 +
* When you need to compute a derived state based on a store state.
 +
** like do a filter
 +
* all getters go in computed properties.
 +
* Getters are like computed properties for stores: will only re-evaluate when some of its dependencies have changed.
 +
* Get things out of the store with these methods, otherwise you will need a computed property in your component that pulls directly: <code>this.$store.state.foo</code>
 +
* getter methods take a single argument <code>state</code>
 +
* Can also pass arguments to getters by returning a function
 +
** <code>getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id)}</code>
 +
* Can use getters via <code>this.$store.getters.doneTodos</code> or mapGetters helper
 +
 
 +
==== <code>mutations</code> ====
 +
* always synchronous - you can't capture the "before" and "after" snapshots if mutations are asynchronous.
 +
* list out the different possible ways the state can change.
 +
* Vuex mutations are similar to events.
 +
* Each mutation has a string type and a handler.
 +
* Mutation handler methods takes one or two arguments, <code>state</code>, and optionally a <code>payload</code> which can be a scalar or an object.
 +
* YOU CANNOT DIRECTLY CALL A MUTATION HANDLER, instead invoke <code>store.commit</code>
 +
 
 +
==== <code>actions</code> ====
 +
* Similar to mutations except:
 +
** Instead of mutating the state, they commit the mutations
 +
** Contains arbitrary asynchronous operations.
 +
* Called from components to commit a mutation
 +
* Can be synchronous OR synchronous
 +
* Can do multiple mutations at once
 +
* All actions do is call the dispatcher with some parameters and an operation
 +
* Actions method take two arguments
 +
** <code>context</code> (similar to <code>state</code> object, which exposes the same set of properties/methods on the store instance)
 +
*** Use ES2015 argument destructuring to pull a member out of an object so you don't have to keep using . to call a method on an object
 +
** <code>payload</code>
 +
* Can daisy chain, a.k.a "compose" actions
 +
<pre>
 +
actions: {
 +
  async actionA ({ commit }) {
 +
    commit('gotData', await getData())
 +
  },
 +
  async actionB ({ dispatch, commit }) {
 +
    await dispatch('actionA') // wait for `actionA` to finish
 +
    commit('gotOtherData', await getOtherData())
 +
  }
 +
}
 +
</pre>
 +
 
 +
 
 +
===Vocabulary===
 +
* Dispatch actions versus commit mutations
 +
* <code>const jokes = await fetch( 'jokes.json' )</code>
 +
* Commit a mutation
 +
 
 +
===Vuex helper methods===
 +
* helper methods return objects
 +
* Use the elipsis <code>...</code> "object spread operator" from ES2015 (Python equivalent is <code>**</code>) if we ever want to use helper methods in combination with other local methods.
 +
 
 +
====<code>mapState</code>====
 +
* <code>import { mapState } from 'vuex'</code>
 +
* FOUR ways to map store state to component state
 +
*# Arrow function: <code>count: state => state.count</code>
 +
*# Map a property to a string if you want to rename <code>countAlias: 'count'</code>
 +
*# Or just pass the string
 +
*# A normal function to mix local "this" state with store state: <code>countPlusLocalState (state) {return state.count + this.localCount}</code>
 +
 
 +
====<code>mapGetters</code>====
 +
* Put inside components <code>computed</code> properties
 +
 
 +
====<code>mapMutations</code>====
 +
* Put inside component's <code>methods</code> properties
 +
* Example:  map `this.increment()` to `this.$store.commit('increment')`
 +
** <code> methods: { ...mapMutations([ 'increment', 'incrementBy']) }</code>
 +
 
 +
====<code>mapActions</code>====
 +
* Like <code>mapMutations</code>, put inside component's <code>methods</code> properties
 +
* Example:  map `this.increment()` to `this.$store.dispatch('increment')`
 +
** <code> methods: { ...mapActions([ 'increment', 'incrementBy']) }</code>
 +
 
 +
===Code===
 +
 +
====App.vue====
 +
<pre>
 +
const store = new Vuex.Store({
 +
 
 +
})
 +
</pre>
 +
 
 +
===store/index.js===
 +
<pre>
 +
import Vuex from 'vuex';
 +
import ModuleName from './modules/ModulName'
 +
 
 +
// Load Vuex
 +
Vue.use( Vuex );
 +
 
 +
// Create store
 +
export default new Vuex.store( {
 +
    modules: {
 +
        ModuleName
 +
    }
 +
}
 +
);
 +
 
 +
</pre>
 +
 
 +
==Vue Router==
 +
* Single Page App (SPA)
 +
 
 +
=== Basic Directions ===
 +
 
 +
# Use <router-link> and <router-view> tags inside your component
 +
# Import route components
 +
# Define routes and map components to routes (each object is called a "route record")
 +
# Instantiate router instance and pass the routes to the constructor
 +
# Inject the router to make the whole app router aware
 +
 
 +
=== Knobs, dials, and best practices ===
 +
* Now you get <code>this.$router</code> and <code>this.$route</code> inside of the component
 +
* <code>router-link</code> automatically gets the <code>.router-link-active</code> class when target route is matched.
 +
* Clicking on a <code>router-link</code> is totally equivalent to calling <code>this.$router.push( { name: 'search', params: { job_handle: '0123456789ABCDEFFEDCBA9876543210'} } )</code>
 +
* Can have multiple <code><router-view></code>s with different names within a single path, and inside the routes definition component's attribute you can use a viewname: component mapping. The key for the unnamed view in this mapping is <code>default</code>
 +
* When <code>props: true</code> inside route definition, route parameters get defined as a property on the component, so now you don't have to explicitly use <code>this.$route.params</code>
 +
* Use VueRouter in history mode, add a catchall route to prevent weirdness related to direct navigation within single-page-apps, and use a try_files directive in your nginx.conf as a backstop: <code>location / { try_files $uri $uri/ /index.html; }</code>
 +
 
 +
 
 +
=== Dynamic Route Matching ===
 +
 
 +
==== Pulling parameters out of URL fragments ====
 +
 
 +
* Use dynamic segment in the route path
 +
* When a route is matched the value of the dynamic segment will be exposed as <code>this.$route.params</code>
 +
** E.g., pattern /user/:username matches to path /user/evan and this.$route.params is object <code> username: 'evan'</code>
 +
 
 +
==== Nested Routes ====
 +
* Use children attribute when defining routes, level of nesting can be arbitrary
 +
* Children components can have their own nested <code><router-view></code> tag
 +
** Child component will be rendered inside parent component's <router-view> tag when route is matched
 +
 
 +
==== Asterisk ====
 +
* Matching priority is given by route definition
 +
* When using an asterisk, a param named pathMatch is automatically added to $route.params.
 +
 
 +
=== Navigation Guard methods for reactive components and data fetching ===
 +
*If the route is the same and only your dynamic component is changing AND you need for reactive things to happen, you MUST define a beforeRouteUpdate method inside your component definition to make the reactive
 +
<nowiki>  beforeRouteUpdate (to, from, next) {
 +
    // react to route changes...
 +
    // don't forget to call next()
 +
  }</nowiki>
 +
* to: Route: the target Route Object being navigated to.
 +
* from: Route: the current route being navigated away from.
 +
* next: Function: this function must be called to resolve the hook.
 +
** next()
 +
** next(false) - abort the navigation
 +
** next( 'someOtherRoute' ) - redirect to a different location
 +
** next( error ) - where error is of type Error and can be resolved on callbacks such as router.onError()
 +
<nowiki> router.beforeEach((to, from, next) => {
 +
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
 +
  else next()
 +
})</nowiki>
 +
 
 +
==Vue CLI==
 
# <code>npm install -g @vue/cli</code>
 
# <code>npm install -g @vue/cli</code>
 
# <code>vue --version</code>
 
# <code>vue --version</code>
 
# <code>vue ui</code>
 
# <code>vue ui</code>
===Vuex.js===
+
# Create new project, include vuex and vue router components
===Vue Router===
+
# Run Project task 1: serve
* Single page App
+
# Run Project task 2 (build):
 
 
  
 
==2018 Tech Stack==
 
==2018 Tech Stack==

Latest revision as of 19:56, 5 January 2021

Vue.js

Files

index.html

  • has <div id="app" and script includes main.js

main.js

  • contains instantiation of Vue instance
import Vue from 'vue';
import App from './App.vue';
import store from './store';

new Vue( {
    store,
    render: h => h(App)
}).$mount( '#app' );

App.vue

  • top level component into which we compose other components
  • App-level arg array contains: const app = new Vue({
    • el: "#app" - el stands for element, maps back to the div in index.html
    • data
    • computed - contains methods. One-off computations in our dataset. Get access to the data using $this
    • filters - also contains methods that take arguments. Pass arguments to filter method inside the mustache using the pipe separator
    • methods - can take an argument
    • lifecycle methods
      • beforeCreate
      • mounted() fires when your app attaches to the DOM, a good time to fetch some data
      • beforeDestroy()

router/index.js

  • Entrypoint into Vue Router

store/index.js

  • Entrypoint into Vuex

Directives

  • directive inside HTML tags start with v-
  • v-bind:____="someDataStoreObj - bind keeps something up-to-date with some data store property
  • v-if="varName"
  • v-for:"i in datastoreobj"
  • v-on:click="someMethod" - binds a function to button
  • <input v-model="message"> - direct two-way binding between input and app state

Component

  • How you create a new HTML tag, you must register them
  • Template can only return one top level element, if you need to return two, wrap within a div.
  • computed properties of a component are just like regular data members
    • Can be declared methods, but call as if they were attributes.
  • Component scaffold has template, script, and style
  • export default {
    • props: ['id, 'age', 'weight] }
    • name = "component_name"
    • components: [ subcomponent1, subcomponent2 ]
  • <style scoped>

Vue Instance

Vuex

Cheat sheet

  • dispatch actions, commit mutations

Concepts

  • State management library
  • Extract shared state out of components and manage it in a global singleton
  • Use when you do a lot of passing data from one component to another through props.
  • When you don't want your data all over the place
  • Vuex stores are REACTIVE, meaning when vue components retrieves state from it, they will reactively and efficiently update the store's state changes.
  • You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. Can log every mutation, take state snapshots, or perform time travel debugging.
  • Modules - encapsulating/separating out logic for different components
  • E.g., whenever store.state.count changes, it will cause the computed property to re-evaluate, and trigger associated DOM updates.
  • By using Vue.use(Vuex) and passing the store augument to the root instance, any component can get at the store by calling this.$store

Flux pattern concepts

  • flux pattern concept created by Facebook
  • All you technically need is an event system
    • Unidirectional dataflow, because all actions go through the same dispatcher
    • All components will know when data has changed and con update appropriately
    1. All starts with an action
    2. Calls a dispatcher to propagate an event
    3. Puts the result into a store
    4. Store emits an event to tell components that are subscribed to it that the data has changed
    5. The views consume the data from the store
  • Many implementations. Redux is another example of Flux pattern but for React framework

Four types of entities in the modules

state

  • put data members in here as an object

getters

  • When you need to compute a derived state based on a store state.
    • like do a filter
  • all getters go in computed properties.
  • Getters are like computed properties for stores: will only re-evaluate when some of its dependencies have changed.
  • Get things out of the store with these methods, otherwise you will need a computed property in your component that pulls directly: this.$store.state.foo
  • getter methods take a single argument state
  • Can also pass arguments to getters by returning a function
    • getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id)}
  • Can use getters via this.$store.getters.doneTodos or mapGetters helper

mutations

  • always synchronous - you can't capture the "before" and "after" snapshots if mutations are asynchronous.
  • list out the different possible ways the state can change.
  • Vuex mutations are similar to events.
  • Each mutation has a string type and a handler.
  • Mutation handler methods takes one or two arguments, state, and optionally a payload which can be a scalar or an object.
  • YOU CANNOT DIRECTLY CALL A MUTATION HANDLER, instead invoke store.commit

actions

  • Similar to mutations except:
    • Instead of mutating the state, they commit the mutations
    • Contains arbitrary asynchronous operations.
  • Called from components to commit a mutation
  • Can be synchronous OR synchronous
  • Can do multiple mutations at once
  • All actions do is call the dispatcher with some parameters and an operation
  • Actions method take two arguments
    • context (similar to state object, which exposes the same set of properties/methods on the store instance)
      • Use ES2015 argument destructuring to pull a member out of an object so you don't have to keep using . to call a method on an object
    • payload
  • Can daisy chain, a.k.a "compose" actions
actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // wait for `actionA` to finish
    commit('gotOtherData', await getOtherData())
  }
}


Vocabulary

  • Dispatch actions versus commit mutations
  • const jokes = await fetch( 'jokes.json' )
  • Commit a mutation

Vuex helper methods

  • helper methods return objects
  • Use the elipsis ... "object spread operator" from ES2015 (Python equivalent is **) if we ever want to use helper methods in combination with other local methods.

mapState

  • import { mapState } from 'vuex'
  • FOUR ways to map store state to component state
    1. Arrow function: count: state => state.count
    2. Map a property to a string if you want to rename countAlias: 'count'
    3. Or just pass the string
    4. A normal function to mix local "this" state with store state: countPlusLocalState (state) {return state.count + this.localCount}

mapGetters

  • Put inside components computed properties

mapMutations

  • Put inside component's methods properties
  • Example: map `this.increment()` to `this.$store.commit('increment')`
    • methods: { ...mapMutations([ 'increment', 'incrementBy']) }

mapActions

  • Like mapMutations, put inside component's methods properties
  • Example: map `this.increment()` to `this.$store.dispatch('increment')`
    • methods: { ...mapActions([ 'increment', 'incrementBy']) }

Code

App.vue

const store = new Vuex.Store({

})

store/index.js

import Vuex from 'vuex';
import ModuleName from './modules/ModulName'

// Load Vuex
Vue.use( Vuex );

// Create store
export default new Vuex.store( {
    modules: {
        ModuleName
    }
}
);

Vue Router

  • Single Page App (SPA)

Basic Directions

  1. Use <router-link> and <router-view> tags inside your component
  2. Import route components
  3. Define routes and map components to routes (each object is called a "route record")
  4. Instantiate router instance and pass the routes to the constructor
  5. Inject the router to make the whole app router aware

Knobs, dials, and best practices

  • Now you get this.$router and this.$route inside of the component
  • router-link automatically gets the .router-link-active class when target route is matched.
  • Clicking on a router-link is totally equivalent to calling this.$router.push( { name: 'search', params: { job_handle: '0123456789ABCDEFFEDCBA9876543210'} } )
  • Can have multiple <router-view>s with different names within a single path, and inside the routes definition component's attribute you can use a viewname: component mapping. The key for the unnamed view in this mapping is default
  • When props: true inside route definition, route parameters get defined as a property on the component, so now you don't have to explicitly use this.$route.params
  • Use VueRouter in history mode, add a catchall route to prevent weirdness related to direct navigation within single-page-apps, and use a try_files directive in your nginx.conf as a backstop: location / { try_files $uri $uri/ /index.html; }


Dynamic Route Matching

Pulling parameters out of URL fragments

  • Use dynamic segment in the route path
  • When a route is matched the value of the dynamic segment will be exposed as this.$route.params
    • E.g., pattern /user/:username matches to path /user/evan and this.$route.params is object username: 'evan'

Nested Routes

  • Use children attribute when defining routes, level of nesting can be arbitrary
  • Children components can have their own nested <router-view> tag
    • Child component will be rendered inside parent component's <router-view> tag when route is matched

Asterisk

  • Matching priority is given by route definition
  • When using an asterisk, a param named pathMatch is automatically added to $route.params.

Navigation Guard methods for reactive components and data fetching

  • If the route is the same and only your dynamic component is changing AND you need for reactive things to happen, you MUST define a beforeRouteUpdate method inside your component definition to make the reactive
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
  • to: Route: the target Route Object being navigated to.
  • from: Route: the current route being navigated away from.
  • next: Function: this function must be called to resolve the hook.
    • next()
    • next(false) - abort the navigation
    • next( 'someOtherRoute' ) - redirect to a different location
    • next( error ) - where error is of type Error and can be resolved on callbacks such as router.onError()
 router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

Vue CLI

  1. npm install -g @vue/cli
  2. vue --version
  3. vue ui
  4. Create new project, include vuex and vue router components
  5. Run Project task 1: serve
  6. Run Project task 2 (build):

2018 Tech Stack