Vue Family bucket -Vue-router&Vuex
About the routing and data management of Vue, I will briefly summarize the most frequently asked questions in the interview:
- How does routing work?
- Hash and History modes used in the project? What else is there besides these two? Under what circumstances?
- What happens when I go live in History mode
- If you know redux, will you compare the difference between vuex and Redux?
- Let’s talk about how VUEX works
Come on, old iron, feel good students, leave your likes and attention ah
Vue-Router
data
- Vue-router
- Vuex
introduce
The Vue Router is the official route manager of vue. js. Its deep integration with the vue.js core makes building a single page application a breeze. The functions included are:
- Nested routing/viewing diagrams
- Modular, component-based routing configuration
- Route parameters, queries, and wildcards
- View transition effect based on vue. js transition system
- Fine-grained navigation control
- Links with automatically activated CSS classes
- HTML5 history mode or hash mode, automatically demoted in IE9
- Custom scroll bar behavior
start
Creating a single page application with vue.js + Vue Router is very simple. With vue.js, we can already compose applications by combining components. When you add a Vue Router, what we need to do is map the components to routes and tell the Vue Router where to render them
The installation
npm i vue-router -S
In the main. In js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) Copy the code
Recommended use: Vue Add Router (remember to submit in advance)
The basic use
router.js
import Vue from 'vue'
/ / 1. Imported
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
//2. The modularization mechanism uses Router
Vue.use(Router)
//3. Create the router object
const router = new Router({
routes: [{path: '/home'.component: Home
},
{
path: '/about'.component: About
}
]
})
export default router;
Copy the code
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
// 4. Mount the root instance
router,
render: h= > h(App)
}).$mount('#app')
Copy the code
After completing the above configuration
App.vue
<template>
<div id="app">
<div id="nav">
<! -- Use router-link component to navigate -->
<! -- Specify the connection by passing the TO attribute -->
<! -- Router-link is rendered as an A tag by default -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
</div>
<! -- Route exit -->
<! -- Routing matching components will be rendered here -->
<router-view/>
</div>
</template>
Copy the code
Open your browser and switch the Home and About hyperlinks to see what happens
After routing
When configuring a route, you can add a name to the route. Then you can access the route dynamically based on the name
const router = new Router({
routes: [{path: '/home'.name:"home".component: Home
},
{
path: '/about'.name:'about'
component: About
}
]
})
Copy the code
To link to a named route, pass an object to the to property of the router-link:
<router-link :to="{name:'home'}">Home</router-link> |
<router-link :to="{name:'about'}">About</router-link> |
Copy the code
Dynamic route matching
It is often necessary to map all routes that a pattern matches to the same component. For example, we have a User component that is used to render all users with different ids. We can do this by using the “dynamic segment” in the vue-Router’s routing path
User.vue
<template> <div> <h3> user page </h3> </div> </template> <script> export default {}; </script> <style lang="scss" scoped> </style>Copy the code
The routing configuration
const router = new Router({
routes:[
{
path: '/user/:id'.name: 'user'.component: User,
},
]
})
Copy the code
<router-link :to="{name:'user',params:{id:1}}">User</router-link> |
Copy the code
access
http://localhost:8080/user/1
http://localhost:8080/user/2
See the effect
When a route is matched, the parameter value is set to this.$route.params, which can be used in each component. Therefore, we can update the User template to output the ID of the current User:
<template>
<div>
<h3>{{$route.params.id}}</h3>
</div>
</template>
Copy the code
Responds to changes in route parameters
As a reminder, when using routing parameters, such as navigating from /user/1 to /user/2 ‘, the original component instance is reused. Because both routes render the same component, reuse is more efficient than destroying and creating. However, this also means that the component’s lifecycle hooks are no longer called.
To respond to changes in routing parameters when reusing components, you can simply watch the $route object:
$route object watch: {$route(to, from) {console.log(to.params.id); }}, * /
// Or use the navigation guard
beforeRouteUpdate(to,from,next){
// Check the route change
// Make sure to call next, otherwise it will block the route change
next();
}
Copy the code
404 routing
const router = new Router({
routes: [//....
// If the reason is not matched, the 404 page is displayed
{
path: The '*'.component: () = > import('@/views/404')}]})Copy the code
When using wildcard routes, ensure that the routes are in the correct order, that is, the routes containing wildcards should be placed last. The route {path: ‘*’} is usually used for client 404 errors
When a wildcard is used, a parameter called pathMatch is automatically added to $route.params. It contains the parts of the URL that are wildcard matched:
{
path: '/user-*'.component: () = > import('@/views/User-admin.vue')}this.$route.params.pathMatch // 'admin'
Copy the code
Matching priority
Sometimes, a path can match multiple routes. In this case, the preference of the routes is based on the definition order of the routes: The one that is defined first has the highest preference.
Query parameters
Like address that appear on this: http://localhos:8080/page? id=1&title=foo
const router = new Router({
routes: [//....
{
name:'/page'.name:'page'.component:() = >import('@/views/Page.vue')}]})Copy the code
<router-link :to="{name:'page',query:{id:1,title:'foo'}}">User</router-link> |
Copy the code
Go to http://localhos:8080/page? Id = 1 & title = foo view the Page
Page.vue
< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 20px; font-size: 13px! Important; white-space: inherit! Important; () {// check the route information object console.log(this.$route); }, } </script> <style lang="scss" scoped> </style>Copy the code
Route redirection and aliases
Example redirects from/to /home:
const router = new Router({
mode: 'history'.routes: [
/ / redirection
{
path: '/'.redirect: '/home'
}
{
path: '/home'.name: 'home'.component: Home
},
]
})
Copy the code
The target of the redirect can also be a named route:
const router = new VueRouter({
routes: [{path: '/'.redirect: { name: 'name'}}}])Copy the code
The alias
{
path: '/home'.name: 'home'.component: Home,
alias: '/alias'
}
Copy the code
Names, names only When users visit http://loacalhost:8080/alias, display Home components
The “alias” feature gives you the freedom to map UI structures to arbitrary urls, rather than being limited to configured nested structures.
Routing components pass parameters
Using a $route in a component makes it highly coupled to its corresponding route, limiting its flexibility by making the component usable only at certain urls.
Use props to decouple components from routes:
Replace coupling with $route
{
path: '/user/:id'.name: 'user'.component: User,
props:true
},
Copy the code
User.vue
<template> <div> <h3> user page {{$route.params.id}}</h3> user page {{{id}}</h3> </h3> user page {{id}}</h3> </h3> </div> </template> <script> <script> export default{ / /... props: { id: { type: String, default: '' }, }, } </script>Copy the code
Props can also be a function
{
path: '/user/:id'.name: 'user'.component: User,
props: (route) = >({
id: route.params.id,
title:route.query.title
})
}
Copy the code
User.vue
< the template > < div > < h3 > user page {{id}} - {{title}} < / h3 > < / div > < / template > < script > export default {/ /... props: { id: { type: String, default: '' }, title:{ type:String } }, }; </script>Copy the code
Programmatic navigation
In addition to using
to create a tag to define the navigation link, we can write code to do this using the instance method of the router.
Note: Within the Vue instance, you can pass
The router. Push.
declarative | programmatic |
---|---|
<router-link :to="..." > |
router.push(...) |
The argument to this method can be a string path or an object describing the address. For example,
/ / string
this.$router.push('home')
/ / object
this.$router.push({ path: 'home' })
// The named route
this.$router.push({ name: 'user'.params: { userId: '123' }})
// With query parameters, change to /register? plan=private
this.$.push({ path: 'register'.query: { plan: 'private' }})
Copy the code
Move back
// Move forward in the browser record, equivalent to history.forward()
router.go(1)
// Record a step back, equivalent to history.back()
router.go(-1)
// Record 3 steps forward
router.go(3)
// If the history record is not enough, then fail silently
router.go(-100)
router.go(100)
Copy the code
Embedded routines by
Real-world application interfaces are typically composed of multiple layers of nested components. Similarly, the segments of the dynamic path in the URL correspond to the nested layers of components in some structure
/user/1/profile /user/1/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+Copy the code
router.js
{
path: '/user/:id'.name: 'user'.component: User,
props: ({params,query}) = >({
id: params.id,
title:query.title
}),
children: [// If /user/:id/profile is successfully matched,
// The Profile will be rendered in the User's
{
path:"profile".component: Profile
},
// When /user/:id/posts matches,
// Posts will be rendered in User's
{
path: "posts".component: Posts
}
]
}
Copy the code
Add a
to the User component template:
<template>
<div>
<h3>{{$route.params.id}}</h3>
<h3>User page {{id}}</h3>
<router-view></router-view>
</div>
</template>
Copy the code
App.vue
<template>
<div id='app'>
<! -- Nesting reasons -->
<router-link to="/user/1/profile">User/profile</router-link> |
<router-link to="/user/1/posts">User/posts</router-link> |
</div>
</template>
Copy the code
Named view
Sometimes you want to show multiple views at the same time (peer), rather than nested display, such as creating a layout with sidebar and main, this time the named view will come in handy
{
path: '/home'.name: 'home'.// Note that the key is components
components: {
default: Home, // Default name
main: () = >import('@/views/Main.vue'),
sidebar: () = > import('@/views/Sidebar.vue')}},Copy the code
App.vue
<router-view/>
<router-view name='main'/>
<router-view name='sidebar'/>
Copy the code
Navigation guard
Navigation indicates that the route is changing.
Complete navigation parsing process
- Navigation is triggered.
- Call exit guard in a deactivated component.
- Call global
beforeEach
The guards. - Called in a reused component
beforeRouteUpdate
Guard (+ 2.2). - Called in the routing configuration
beforeEnter
. - Resolve the asynchronous routing component.
- Called in an activated component
beforeRouteEnter
. - Call global
beforeResolve
Guard (+ 2.5). - Navigation is confirmed.
- Call global
afterEach
Hook. - Triggers a DOM update.
- Called with the created instance
beforeRouteEnter
Guard passnext
Callback function.
Global guard
You can register a global front guard using router.beforeeach
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) = > {
// ...
})
Copy the code
There is a demand, the user access in browsing the site, will visit many components, when the user to jump to/notes, found that the user is not logged in, should allow the user to log in to view at this time, should allow the user to jump to the login page, login to finish before he can see the contents of my notes, at this time of global guard has played a key role
There are two routes/Notes and /login
router.vue
const router = new VueRouter({
routes:[
{
path: '/notes'.name: 'notes'.component: () = > import('@/views/Notes')}, {path: "/login".name: "login".component: () = > import('@/views/Login')]}})// Global guard
router.beforeEach((to, from, next) = > {
// The user accesses '/notes'
if (to.path === '/notes') {
// Check to see if the user saved the login status information
let user = JSON.parse(localStorage.getItem('user'))
if (user) {
// If yes, release directly
next();
} else {
// If no, the user logs in to the login page
next('/login')}}else{ next(); }})Copy the code
Login.vue
<template> <div> <input type="text" v-model="username"> <input type="password" v-model="pwd"> <button </div> </template> <script> export default {data() {return {username: "", PWD: ""}; }, methods: { handleLogin() { // 1. SetTimeout (() => {let data = {username: this.username}; Localstorage.setitem ("user", json.stringify (data)); $router.push({name: "notes"}); }, 1000); }}}; </script>Copy the code
App.vue
<! -- Global guard demo -->
<router-link to="/notes">My notes</router-link> |
<router-link to="/login">The login</router-link> |
<button @click="handleLogout">exit</button>
Copy the code
export default {
methods: {
handleLogout() {
// Delete the login status information
localStorage.removeItem("user");
// Go to the home page
this.$router.push('/')}}}Copy the code
A guard inside a component
You can define the following route navigation guards directly in the routing component:
beforeRouteEnter
beforeRouteUpdate
(new) 2.2beforeRouteLeave
<template> <div> <h3> User edit page </h3> <textarea name id cols="30" rows="10" v-model="content"></textarea> <button @ click = "saveData" > save < / button > < div class = "wrap" v - for = "(item, index) in the list" : the key = "index" > < p > {{item. The title}} < / p > < / div > </div> </template> <script> export default { data() { return { content: "", list: [], confir: true }; }, methods: { saveData() { this.list.push({ title: this.content }); this.content = ""; } }, beforeRouteLeave(to, from, Next) {// this' if (this.content) {alert(" make sure you save the information before you leave "); next(false); } else { next(); }}}; </script>Copy the code
Implement permission control on routing meta information
Set the meta field for the route to which you want to add permission
{
path: '/blog'.name: 'blog'.component: () = > import('@/views/Blog'),
meta: {
requiresAuth: true}}, {// Route exclusive guards
path: '/notes'.name: 'notes'.component: () = > import('@/views/Notes'),
meta: {
requiresAuth: true}},Copy the code
// Global guard
router.beforeEach((to, from, next) = > {
if (to.matched.some(record= > record.meta.requiresAuth)) {
// Requires permission
if(!localStorage.getItem('user')){
next({
path:'/login'.query: {redirect:to.fullPath
}
})
}else{ next(); }}else{ next(); }})Copy the code
login.vue
// Login operation
handleLogin() {
// 1. Obtain the user name and password
// 2. Interacts with the backend
setTimeout(() = > {
let data = {
username: this.username
};
localStorage.setItem("user".JSON.stringify(data));
// Go to the previous page
this.$router.push({path: this.$route.query.redirect });
}, 1000);
}
Copy the code
Data acquisition
Sometimes, after entering a route, you need to get data from the server. For example, when rendering user information, you need to get the user’s data from the server. We can do this in two ways:
- Fetch after navigation: Complete the navigation and then fetch the data in the following component lifecycle hooks. Displays an indication such as “loading” during data fetch.
- Fetch before navigation: Before navigation is complete, data is obtained from the guard that the route enters, and the navigation is performed after the data is obtained successfully.
Obtain the data after the navigation is complete
When you do this, we immediately navigate and render the component, and then get the data in the component’s created hook. This gives us the opportunity to show a loading state during data acquisition and to show different loading states between different views.
<template> <div class="post"> <div v-if="loading" class="loading">Loading... </div> <div v-if="error" class="error">{{ error }}</div> <div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> </div> </template>Copy the code
export default {
name: "Post".data() {
return {
loading: false.post: null.error: null
};
},
// Obtain data after the component is created,
// The data is already monitored
created() {
// If the route changes, the method is executed again
this.fetchData();
},
watch: {
$route: "fetchData"
},
methods: {
fetchData() {
this.error = this.post = null;
this.loading = true;
this.$http.get('/api/post')
.then((result) = > {
this.loading = false;
this.post = result.data;
}).catch((err) = > {
this.error = err.toString(); }); }}};Copy the code
Vuex
Vuex is a state management pattern developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application, and rules to ensure that the state changes in a predictable manner
Install vuex
vue add vuex
Copy the code
store.js
import Vue from 'vue'
import Vuex from 'vuex'
// Make sure to start with vue.use (Vuex)
Vue.use(Vuex)
export default new Vuex.Store({
state: { //this.$store.state.count
count:0
},
getters: {evenOrOdd:(state) = >{ //this.$store.getters.evenOrOdd
return state.count % 2= = =0 ? 'even number': 'odd'}},mutations: {
increment(state){ //this.$store.commit('increment')
state.count++
},
decrement(state){ //this.$store.commit('decrement')
state.count--
}
},
actions: {
increment({commit}){ //this.$store.dispatch('increment')
// The only way to change the state is to commit mutation
commit('increment');
},
decrement({ commit }) { //this.$store.dispatch('decrement')
commit('decrement');
},
incrementAsync({commit}){ //this.$store.dispatch('incrementAsync')
return new Promise((resolve, reject) = > {
setTimeout(() = > {
commit('increment');
resolve(10);
}, 1000); }}}})Copy the code
We can get the state object from this.$store.state at an appropriate time for the component, and use the this.store.com. MIT method to violate the state change
this.$store.commit('increment');
Copy the code
MapState helper function
When a component needs to obtain multiple states, declaring them all as computed properties can be somewhat repetitive and redundant. To solve this problem, we can use the mapState helper function to help us generate computed properties that allow you to press the key fewer times
// In the separately built version, the helper function is vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// The arrow function makes the code more concise
count: state= > state.count,
// Pass the string argument 'count' as' state => state.count '
countAlias: 'count'.// In order to be able to use 'this' to get local state, you must use regular functions
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
Copy the code
We can also pass an array of strings to mapState when the name of the mapping’s computed property is the same as the name of the state’s child node.
computed: mapState([
// Map this.count to store.state.count
'count'
])
Copy the code
Object expansion operator
The mapState function returns an object. How do we mix it with locally computed properties? Often, we need to use a utility function to combine multiple objects into one so that we can pass the final object to the computed property. But since the advent of the object expansion operator, writing has been greatly simplified
computed:{ ... mapState({"count"})}Copy the code
MapGetters helper function
The mapGetters helper function simply maps getters in a store to locally computed properties:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters([
'evenOrOdd'])}}Copy the code
If you want to give a getter property another name, use the object form:
mapGetters({
// Map 'this.doneevenorodd' to 'this.$store.getters. EvenOrOdd'
doneEvenOrOdd: 'evenOrOdd'
})
Copy the code
Mutation
The only way to change the state in Vuex’s Store is to submit mutation. Mutations in Vuex are very similar to events: each mutation has an event type (type) of a string and a callback function (handler). This callback function is where we actually make the state change, and it takes state as the first argument:
MapMutation
You can use this.store.com MIT (‘ XXX ‘) to submit mutations in the component, or map the methods in the component to store.com MIT calls using the mapMutations assist function, which requires injecting the Store at the root node.
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations('counter'['increment'.'decrement',].}}Copy the code
Action
Action is similar to mutation, except that:
- The Action commits the mutation instead of directly changing the state.
- An Action can contain any asynchronous operation
MapAction helper function
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapActions('counter'['incrementAsync'])}}Copy the code
submission
// Inside the component
// Distribute as a payload
this.$store.dispatch('incrementAsync', {
amount: 10
})
// Distribute as objects
this,.$store.dispatch({
type: 'incrementAsync'.amount: 10
})
Copy the code
Module
By using a single state tree, all the state of the application is concentrated into one large object. When the application becomes very complex, the Store object can become quite bloated.
To solve this problem, Vuex allows us to split the store into modules. Each module has its own state, mutation, action, getter, and even nested submodules — split the same way from top to bottom:
Make a shopping cart case
There are two modules, CART and Products
Create the Store folder
| - store ├ ─ ─ index. The js └ ─ ─ modules ├ ─ ─ cart. Js └ ─ ─ products. JsCopy the code
cart.js
If you want more encapsulation and reuse, you can make your module namespaced: true by adding namespaced: true
When a module is registered, all its getters, actions, and mutations are automatically named according to the registered path of the module.
export default {
// Make the current module more encapsulated and reusable
namespaced: true.state: {... },getters: {... },mutations: {... },actions: {... }},Copy the code
products.js
export default {
// Make the current module more encapsulated and reusable
namespaced: true.state: {... },getters: {... },mutations: {... },actions: {... }},Copy the code
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import cart from './modules/cart';
import products from './modules/products';
export default new Vuex.Store({
modules:{
cart,
products,
}
})
$store.state.cart // Obtain the state of the cart
//this.$store.state.products // Obtain the status of products
Copy the code
Complete Shopping cart Case
The mock data
New vue. Config. Js
const products = [
{ id: 1.title: 'iphone11'.price: 600.inventory: 10 },
{ id: 2.title: 'iphone11 pro'.price: 800.inventory: 5 },
{ id: 3.title: 'iphone11 max'.price: 1600.inventory: 6},]module.exports = {
devServer: {
before(app, server) {
app.get('/api/products'.(req, res) = > {
res.json({
products:products
})
})
}
}
}
Copy the code
cart.js
export default {
// Make the current module more encapsulated and reusable
namespaced: true.state: {
items: []},getters: {
// Get the items in the shopping cart
cartProducts: (state, getters, rootState) = > {
return state.items.map(({ id, quantity }) = > {
const product = rootState.products.products.find(product= > product.id === id)
return {
title: product.title,
price: product.price,
quantity
}
})
},
// The total price of the cart
cartTotalPrice: (state, getters) = > {
return getters.cartProducts.reduce((total, product) = > {
return total + product.price * product.quantity
}, 0)}},mutations: {
pushProductToCart(state, { id }) {
state.items.push({
id,
quantity: 1})},incrementItemQuantity(state, { id }) {
const cartItem = state.items.find(item= >item.id === id); cartItem.quantity++; }},actions: {
// Add items to the cart
addProductToCart({ commit, state }, product) {
// If in stock
if (product.inventory > 0) {
const cartItem = state.items.find(item= > item.id === product.id);
if(! cartItem) { commit('pushProductToCart', { id: product.id });
} else {
commit('incrementItemQuantity', cartItem);
}
/ / submit products module decrementProductInventory method
// Reduce the number of items in inventory by 1
commit('products/decrementProductInventory', { id: product.id }, { root: true})}},}Copy the code
products.js
import Axios from "axios";
export default {
// Make the current module more encapsulated and reusable
namespaced: true.state: {
products: []},getters: {},mutations: {
setProducts(state, products) {
state.products = products;
},
// Methods of reducing commodity inventory
decrementProductInventory(state, { id }) {
const product = state.products.find(product= > product.id === id)
product.inventory--
}
},
actions: {
// How to get all the goods
getAllProducts({ commit }) {
Axios.get('/api/products')
.then(res= > {
console.log(res.data.products);
commit('setProducts',res.data.products)
})
.catch(err= > {
console.log(err); }})}},Copy the code
Products.vue
<template> <div> <h3> Shop </h3> <ul> <li v-for='product in products' :key =' product.id'> {{product.title}} - {{product.price | currency}} <br> <button :disabled='! Product.inventory '@click='addProductToCart(product)'> Add to cart </button> </li> </ul> <hr> </div> </template> <script> import { mapState,mapActions } from "vuex"; export default { name: "ProductList", data() { return {}; }, computed: { products(){ return this.$store.state.products.products } }, methods: { ... mapActions('cart',[ 'addProductToCart' ]) }, created() { this.$store.dispatch("products/getAllProducts"); }}; </script> <style scoped> </style>Copy the code
Cart.vue
<template> <div> <h2> < I > Please add products to your cart.</ I > <ul> <li v-for="product in products" :key="product.id" - > {{product. The title}} {{product. The price | currency}} x {{product. Quantity}} < / li > < / ul > < p > total price: {{total | currency}} < / p > </div> </template> <script> import { mapGetters,mapState } from "vuex"; export default { name: "shoppingcart", computed:{ ... mapGetters('cart',{ products:'cartProducts', total:'cartTotalPrice' }) } }; </script> <style scoped> </style>Copy the code
Under what circumstances should I use Vuex?
Vuex helps manage shared state and comes with more concepts and frameworks. This requires a balance between short – and long-term benefits.
If you don’t plan to develop large, single-page applications, using Vuex can be tedious and redundant. That’s true — if your application is simple enough, you’re better off not using Vuex. A simple store pattern is all you need. However, if you need to build a medium to large single-page application, you are likely to be thinking about how to better manage state outside of components, and Vuex would be a natural choice. To quote Dan Abramov, author of Redux:
The Flux architecture is like glasses: you know when you need it
The plug-in
The log plug-in
Vuex comes with a logging plugin for general debugging:
import createLogger from 'vuex/dist/logger'
const store = new Vuex.Store({
plugins: [createLogger({
collapsed: false.// Automatically expand the record mutation})]})Copy the code
Note that the Logger plug-in generates a snapshot of the status, so use it only in a development environment.