关于vue-router:vuerouter

vue-router1.概念:这里的路由并不是指咱们平时所说的硬件路由器,这里的路由是SPA(single page application单页利用)的门路管理器,即vue-router就是WebApp的链接门路管理系统。2.vue-router是Vue.js官网的路由插件,它和vue.js是深度集成的,适宜用于构建单页面利用。vue的单页面利用是基于路由和组件的,路由用于设定拜访门路,并将门路和组件映射起来。传统的页面利用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面利用中,则是门路之间的切换,也就是组件的切换。路由模块的实质就是建设起url和页面之间的映射关系。3.路由实际上就是能够了解为指向,就是当在页面上点击一个按钮须要跳转到对应的页面,即路由跳转。4.路由的作用依据url锚点门路,在容器中加载不同的模块,实质作用是做页面导航,实现SPA的开发4.1一种非凡的web利用,他将所有的流动局限于web页面中,仅在web页面初始化时加载相应的html,JavaScript和css4.2一旦页面加载实现之后,spa不会因为用户的操作而页面的从新加载或跳转,而是利用javascript动静的跳转html,采纳的是div切换显示和暗藏,从而实现UI和用户的交互5.特点:嵌套路由:容许定义嵌套的门路,每个门路能够映射到一个组件。视图映射:能够定义一个路由组件对应多个门路。动静路由:路由参数或查问参数能够用来动静匹配路由。导航守卫:能够通过全局或组件内的守卫来监听并管制导航。路由懒加载:能够异步加载组件,进步利用性能。路由过渡成果:能够通过 CSS 过渡或 JavaScript 钩子为路由切换增加动画成果。6.cmd装置命令npm install vue-router7.根本应用导入 Vue 和 Vue Router。定义路由组件和路由配置。创立 Vue Router 实例,并将路由配置传递给它。将 Vue Router 实例注入到 Vue 实例中。import Vue from 'vue';import VueRouter from 'vue-router';Vue.use(VueRouter);const Foo = { template: '<div>foo</div>' };const Bar = { template: '<div>bar</div>' };const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar }];const router = new VueRouter({ routes});new Vue({ router, template: '<div><router-link to="/foo">foo</router-link><router-view></router-view></div>'}).$mount('#app');

February 23, 2024 · 1 min · jiezi

关于vue-router:Vue-Router之动态路由

前言个别状况下,路由定义在 createRouter 中,而且创立路由之后不会去批改。但在某些场景可能须要在曾经运行的时候动静的增加路由,比方菜单由接口返回,再比方相似低代码平台中用户能够新增或删除页面。 增加路由增加路由,参数是路由对象 import { useRouter } from 'vue-router';const router = useRouter();router.addRoute({ path: '/about', component: About })增加子路由,第一个参数是父级的 name,第二个参数是子路由对象 router.addRoute('admin', { path: 'settings', component: AdminSettings })注:addRoute 只是增加路由,如果想增加后立刻显示,则须要跳转。注:增加路由会立刻失效。 删除路由增加一个 name 已有的路由,这样会删除原来的路由并增加新的。调用 router.addRoute() 返回的回调,其返回值是个办法,能够间接调用。调用 router.removeRoute(),参数传入 name。判断路由是否已存在传入 name,判断该路由是否存在。 router.hasRoute(name)或 获取所有路由之后进行筛选 router.getRoutes()

September 6, 2023 · 1 min · jiezi

关于vue-router:P33-vuerouter-路由守卫和懒加载

查看to from 全局守卫 每路守卫 组件内的守卫最初,你能够在路由组件内间接定义路由导航守卫(传递给路由配置的)可用的配置 你能够为路由组件增加以下配置: beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave index.js // 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import User from "../views/User.vue";import NotFound from "../views/NotFound.vue";import News from "../views/News.vue";import Parent from "../views/Parent.vue";import Styleone from "../views/Styleone.vue";import Styletwo from "../views/Styletwo.vue";import Page from "../views/Page.vue";import ShopTop from "../views/ShopTop.vue";import ShopMain from "../views/ShopMain.vue";import ShopFooter from "../views/ShopFooter.vue";import Sil from "../views/Sil.vue";import {createRouter, createWebHashHistory, createWebHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', // redirect: {name:"about"} //redirect: '/about' redirect:(to)=>{ console.log(to); return {path:"/about"} } }, { path: '/about', name: 'about', component: About, //每路守卫 beforeEnter:(to,from,next)=>{ console.log(to); console.log(from); if(123 == 123){ next() } } }, { path: '/user/:id', component: User }, { path: '/sil/:id', component: Sil }, { name: "news", //path: '/news/:id(\\d+)',//正则匹配 // path: '/news/:id+',//多个参数 //path: '/news/:id+',//参数可有可无 //path: '/news/:id*',//参数可反复叠加 path: '/news/:id?',//参数不可反复叠加 component: News }, { path: '/:path(.*)', component: NotFound },//应用正则,匹配任意path { path: "/parent", // alias: '/father', //起一个别名 alias: ['/father', '/laofuqin'], //起多个别名 component: Parent, children: [ { path: "styleone", component: Styleone }, { path: "styletwo", component: Styletwo } ] }, { path: "/page", component: Page }, { path: "/shop/:id", components: { default: ShopMain, ShopTop:ShopTop, ShopFooter:ShopFooter, ShopMain:ShopMain }, props:{default: true, ShopMain: true, ShopFooter: false, ShopTop: false} }]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 // history: createWebHashHistory(), history: createWebHistory(), routes, // `routes: routes` 的缩写})//全局守卫// router.beforeEach((to,from,next)=>{// console.log(to);// console.log(from);// next()//通行证// })export default routerNews.vue ...

January 28, 2023 · 3 min · jiezi

关于vue-router:vue路由的使用简单理解

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <div id="app"> <h1>尺寸.com</h1> <p> <!-- 增加超链接 router-link 组件 to属性指定链接--> <router-link to="/home">HOME</router-link> <router-link to="/news">NEWS</router-link> </p> <!-- 路由的进口,路由匹配到组件之后,要渲染到这里 --> <router-view></router-view> </div></body><script src="../js/vue.min.js"></script><script src="../js/axios.min.js"></script><script src="../js/vue-router.min.js"></script><script> // 1、定义路由所需的组件 const home = {template:"<div>首页</div>"}; const news = {template:"<div>新闻</div>"}; // 2、定义路由 每个路由有两局部path(门路),component(组件) const routes = [ {path:"/home",component:home}, {path:"/news",component:news} ]; // 3、创立路由管理器实例 const router = new VueRouter({ routes:routes }); // 4、创立vue实例 将router注入到vue实例中,让整个利用都领有路由的性能 var vm = new Vue({ router }).$mount('#app'); // 代替el</script></html>总结router是vue路由管理器对象,用来治理路由route是路由对象,一个路由就对应了一条拜访门路,一组路由用routes每个路由对象,都有两局部:path门路,component组件router-link是对a标签的封装,通过to属性指定链接router-view 路由拜访到指定的组件,进行页面展现

January 26, 2023 · 1 min · jiezi

关于vue-router:P32-vuerouter-路由组件传参和不同的历史记录

路由组件传参命名视图 ShopMain.vue <template> <h2>shop 次要内容</h2></template><script>export default { name: "ShopMain", props: ['id'], mounted() { console.log(this.id) }}</script><style scoped></style> 不同的历史记录hash 模式 二者的区别是有无#html5模式 #隐没

January 17, 2023 · 1 min · jiezi

关于vue-router:P31-vuerouter-重定向和别名

重定向 拜访 /, 重定向到 /about redirect 返回对象 办法 别名 多个别名 index.js // 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import User from "../views/User.vue";import NotFound from "../views/NotFound.vue";import News from "../views/News.vue";import Parent from "../views/Parent.vue";import Styleone from "../views/Styleone.vue";import Styletwo from "../views/Styletwo.vue";import Page from "../views/Page.vue";import ShopTop from "../views/ShopTop.vue";import ShopMain from "../views/ShopMain.vue";import ShopFooter from "../views/ShopFooter.vue";import {createRouter, createWebHashHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', // redirect: {name:"about"} //redirect: '/about' redirect:(to)=>{ console.log(to); return {path:"/about"} } }, { path: '/about', name: 'about', component: About }, { path: '/user/:id', component: User }, { name: "news", //path: '/news/:id(\\d+)',//正则匹配 // path: '/news/:id+',//多个参数 //path: '/news/:id+',//参数可有可无 //path: '/news/:id*',//参数可反复叠加 path: '/news/:id?',//参数不可反复叠加 component: News }, { path: '/:path(.*)', component: NotFound },//应用正则,匹配任意path { path: "/parent", // alias: '/father', //起一个别名 alias: ['/father', '/laofuqin'], //起多个别名 component: Parent, children: [ { path: "styleone", component: Styleone }, { path: "styletwo", component: Styletwo } ] }, { path: "/page", component: Page }, { path: "/shop", components: { default: ShopMain, ShopTop:ShopTop, ShopFooter:ShopFooter, ShopMain:ShopMain } }]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写})export default router

January 17, 2023 · 1 min · jiezi

关于vue-router:P30-vuerouter-命名路由和命名视图

命名路由App.vue <script setup></script><template> <div> <h1>Hello App!</h1> <!--应用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将出现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">Go to Home</router-link> <p>---</p> <router-link to="/about">Go to About</router-link> <p>---</p> <router-link to="/user/56">Go to User</router-link> <p>---</p><!-- <router-link to="/news/56">Go to User</router-link>--> <router-link :to="{name:'news',params:{id:56}}">Go to News </router-link> <p>---</p> <router-link to="/parent">Go to Parent</router-link> <p>---</p> <router-link to="/page">Go to Page</router-link> <!-- 路由进口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div></template>indev.js // 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import User from "../views/User.vue";import NotFound from "../views/NotFound.vue";import News from "../views/News.vue";import Parent from "../views/Parent.vue";import Styleone from "../views/Styleone.vue";import Styletwo from "../views/Styletwo.vue";import Page from "../views/Page.vue";import {createRouter, createWebHashHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/user/:id', component: User }, { name: "news", //path: '/news/:id(\\d+)',//正则匹配 // path: '/news/:id+',//多个参数 //path: '/news/:id+',//参数可有可无 //path: '/news/:id*',//参数可反复叠加 path: '/news/:id?',//参数不可反复叠加 component: News }, { path: '/:path(.*)', component: NotFound },//应用正则,匹配任意path { path: "/parent", component: Parent, children: [ { path: "styleone", component: Styleone }, { path: "styletwo", component: Styletwo } ] }, { path: "/page", component: Page }]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写})export default routerNew.vue ...

January 16, 2023 · 3 min · jiezi

关于vue-router:P29-vue3-js页面跳转-和替换当前位置

js页面跳转App.vue <script setup></script><template> <div> <h1>Hello App!</h1> <!--应用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将出现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">Go to Home</router-link> <p>---</p> <router-link to="/about">Go to About</router-link> <p>---</p> <router-link to="/user/56">Go to User</router-link> <p>---</p> <router-link to="/news/56">Go to User</router-link> <p>---</p> <router-link to="/parent">Go to Parent</router-link> <p>---</p> <router-link to="/page">Go to Page</router-link> <!-- 路由进口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div></template>Page.vue <template> <div> <h2>PAge web</h2> <button @click="goPage">跳转页面</button> </div></template><script>export default { name: "Page", methods:{ goPage: function (){ console.log("Page-function") console.log(this.$router) //this.$router.push('/') this.$router.push({name:"news",params:{id:56}}) //this.$router.push({path:"/user/56"}) // if(11==11){ // this.$router.push('/parent') // } } }}</script><style scoped></style>index.js ...

January 14, 2023 · 2 min · jiezi

关于vue-router:P28-vue3-嵌套路由

嵌套路由App.vue <script setup></script><template> <div> <h1>Hello App!</h1> <!--应用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将出现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">Go to Home</router-link> <p>---</p> <router-link to="/about">Go to About</router-link> <p>---</p> <router-link to="/user/56">Go to User</router-link> <p>---</p> <router-link to="/news/56">Go to User</router-link> <p>---</p> <router-link to="/parent">Go to Parent</router-link> <!-- 路由进口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div></template>index.js // 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import User from "../views/User.vue";import NotFound from "../views/NotFound.vue";import News from "../views/News.vue";import Parent from "../views/Parent.vue";import Styleone from "../views/Styleone.vue";import Styletwo from "../views/Styletwo.vue";import {createRouter, createWebHashHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/user/:id', component: User }, { //path: '/news/:id(\\d+)',//正则匹配 // path: '/news/:id+',//多个参数 //path: '/news/:id+',//参数可有可无 //path: '/news/:id*',//参数可反复叠加 path: '/news/:id?',//参数不可反复叠加 component: News }, { path: '/:path(.*)', component: NotFound },//应用正则,匹配任意path { path: "/parent", component: Parent, children: [ { path: "styleone", component: Styleone }, { path: "styletwo", component: Styletwo } ] }]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写})export default routerParent.vue ...

January 9, 2023 · 2 min · jiezi

关于vue-router:P27-vue3-路由正则与重复参数

路由正则 新建一个News.vue失常404 路由多层级url

January 3, 2023 · 1 min · jiezi

关于vue-router:P26-vue3-vueroute-带参数的动态路由匹配和404-页面

vue-route 带参数的动静路由匹配App.veu<script setup></script><template> <div> <h1>Hello App!</h1> <!--应用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将出现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">Go to Home</router-link> <p>---</p> <router-link to="/about">Go to About</router-link> <p>---</p> <router-link to="/user/56">Go to User</router-link> <!-- 路由进口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div></template>User.veu <template> <div>User</div></template><script setup> import {useRoute} from 'vue-router' console.log(useRoute().params.id);</script><!--<script>--><!--export default {--><!-- mounted(){--><!-- console.log(this.$router.params.id);--><!-- }--><!--}--><!--</script>--><style scoped></style>main.js import { createApp } from 'vue'import './style.css'import App from './App.vue'import router from "./router/index.js";const app=createApp(App)app.use(router)app.mount('#app')index.js // 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import User from "../views/User.vue";import {createRouter, createWebHashHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/user/:id', component: User },]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写})export default routerHome.vue ...

December 27, 2022 · 2 min · jiezi

关于vue-router:P25-vue3-vuerouter

installnpm install vue-router@4 App.vue<script setup></script><template> <div> <h1>Hello App!</h1> <!--应用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将出现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">Go to Home</router-link> <p>---</p> <router-link to="/about">Go to About</router-link> <!-- 路由进口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div></template>router/index.js// 1. 定义路由组件.// 也能够从其余文件导入import Home from "../views/Home.vue";import About from "../views/About.vue";import {createRouter, createWebHashHistory} from "vue-router";// 2. 定义一些路由// 每个路由都须要映射到一个组件。// 咱们前面再探讨嵌套路由。const routes = [ { path: '/', component: Home }, { path: '/about', component: About },]// 3. 创立路由实例并传递 `routes` 配置// 你能够在这里输出更多的配置,但咱们在这里// 临时放弃简略const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写})export default routerviews/About.vue<template></template><script>export default { name: "About"}</script><style scoped></style>views/Home.vue<template></template><script>export default { name: "Home"}</script><style scoped></style>main.jsimport { createApp } from 'vue'import './style.css'import App from './App.vue'import router from "./router/index.js";const app=createApp(App)app.use(router)app.mount('#app')

December 19, 2022 · 1 min · jiezi

关于vue-router:vuerouter如何实时地址不变动态替换路由参数地址栏参数

动静替换和批改URL的参数,可应用webpack-merge包来实现1.装置 npm install webpack-merge -D2.援用: import merge from 'webpack-merge'3.应用办法: 1️新增一个id this.$router.push({ query:merge(this.$route.query,{'id':'123'})})2️ 批改idthis.$router.push({ query:merge(this.$route.query,{'id':'456'})})3️ 替换所有idthis.$router.push({ query:merge({},{'userId':'xxx123456'})})最初再加上location.reload(),刷新一下以后页面就跳转过来了 特别感谢一下博主:https://www.jianshu.com/p/b9e...

October 11, 2022 · 1 min · jiezi

关于vue-router:element三级菜单无法跳转往上找二级地址的情况解决

路由配置

August 5, 2022 · 1 min · jiezi

关于vue-router:vuerouter源码三理解Vuerouter中的Matcher

前言【vue-router源码】系列文章将带你从0开始理解vue-router的具体实现。该系列文章源码参考vue-router v4.0.15。源码地址:https://github.com/vuejs/router浏览该文章的前提是你最好理解vue-router的根本应用,如果你没有应用过的话,可通过vue-router官网学习下。 该篇文章将带你了解vue-router中matcher的实现。 matcher初识在开始介绍matcher实现之前,咱们先理解下matcher是什么?它的作用是什么?在vue-router中,每一个咱们定义的路由都会被解析成一个对应的matcher(RouteRecordMatcher类型),路由的增删改查都会依附matcher来实现。 createRouterMatcher在createRouter中会通过createRouterMatcher创立一个matcher(RouterMatcher类型)。 export function createRouterMatcher( routes: RouteRecordRaw[], globalOptions: PathParserOptions): RouterMatcher { const matchers: RouteRecordMatcher[] = [] const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>() globalOptions = mergeOptions( { strict: false, end: true, sensitive: false } as PathParserOptions, globalOptions ) function getRecordMatcher(name: RouteRecordName) { // ... } function addRoute( record: RouteRecordRaw, parent?: RouteRecordMatcher, originalRecord?: RouteRecordMatcher ) { // ... } function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) { // ... } function getRoutes() { // ... } function insertMatcher(matcher: RouteRecordMatcher) { // ... } function resolve( location: Readonly<MatcherLocationRaw>, currentLocation: Readonly<MatcherLocation> ): MatcherLocation { // ... } routes.forEach(route => addRoute(route)) return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }}createRouterMatcher接管两个参数:routes、globalOptions。其中routes为咱们定义的路由表,也就是在createRouter时传入的options.routes,而globalOptions就是createRouter中的options。createRouterMatcher中申明了两个变量matchers、matcherMap,用来存储通过路由表解析的matcher(RouteRecordMatcher类型),而后遍历routes,对每个元素调用addRoute办法。最初返回一个对象,该对象有addRoute、resolve、removeRoute、getRoute、getRecordMatcher几个属性,这几个属性都对应着一个函数。接下来咱们看下这几个函数: ...

June 6, 2022 · 14 min · jiezi

关于vue-router:vuerouter源码一routerinstall解析

前言【vue-router源码】系列文章将带你从0开始理解vue-router的具体实现。该系列文章源码参考vue-router v4.0.15。源码地址:https://github.com/vuejs/router浏览该文章的前提是你最好理解vue-router的根本应用,如果你没有应用过的话,可通过vue-router官网学习下。 该篇文章首先介绍router.install的过程。 vue-router的应用在介绍router.install之前,咱们先看下vue3中是如何应用vue-router的。 import { createApp } from 'vue'import { createRouter } from 'vue-router'const router = createRouter({ ... })const app = createApp({})app.use(router).mount('#app')在执行app.use的过程中,会执行router.install,并传入app实例。那么router.install过程中产生了什么呢?接下来咱们一探到底。 router.installrouter.install源码位于createRouter中,文件地位src/router.ts。 install(app: App) { const router = this app.component('RouterLink', RouterLink) app.component('RouterView', RouterView) app.config.globalProperties.$router = router Object.defineProperty(app.config.globalProperties, '$route', { enumerable: true, get: () => unref(currentRoute), }) if ( isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED ) { started = true push(routerHistory.location).catch(err => { if (__DEV__) warn('Unexpected error when starting the router:', err) }) } const reactiveRoute = {} as { [k in keyof RouteLocationNormalizedLoaded]: ComputedRef< RouteLocationNormalizedLoaded[k] > } for (const key in START_LOCATION_NORMALIZED) { reactiveRoute[key] = computed(() => currentRoute.value[key]) } app.provide(routerKey, router) app.provide(routeLocationKey, reactive(reactiveRoute)) app.provide(routerViewLocationKey, currentRoute) const unmountApp = app.unmount installedApps.add(app) app.unmount = function () { installedApps.delete(app) if (installedApps.size < 1) { pendingLocation = START_LOCATION_NORMALIZED removeHistoryListener && removeHistoryListener() removeHistoryListener = null currentRoute.value = START_LOCATION_NORMALIZED started = false ready = false } unmountApp() } if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) { addDevtools(app, router, matcher) }}在install中,首先会注册RouterLink与RouterView两大组件。 ...

June 4, 2022 · 2 min · jiezi

关于vue-router:vuerouter相关配置

本文记录一下我在开发vue我的项目时,应用vue-router遇到过得问题。routesconst routes = [ { path: '/cardLesson', name: 'cardLesson', component: () => import( '../views/myCard/cardLesson'), meta: { auth: false, needPhone: false, keepAlive: false } }, ...]导航守卫router.beforeEach((to, from, next) => { if (to.meta && to.meta.auth) { if (!Vue.prototype.$checkLogin()) { return Vue.prototype.$bus.$emit('auth-login') } if (to.meta.needPhone && !Vue.prototype.$checkPhone()) { return Vue.prototype.$bus.$emit('auth-phone') } return next() } else { next() }})以上的配置,在进入路由前会先检测有没有权限管制,如果须要登录则会触发登录监听事件,弹出登录窗口。如果是从其余我的项目跳转到vue我的项目的路由,却无奈失常弹出登录窗口,因为单页面利用第一次加载,$bus还没有注册。 解决页面加载进度条router.beforeEach((to, from, next) => { NProgress.start(); to.meta.keepAlive = !(to.meta && to.meta.skipCache); next()});router.afterEach(() => { NProgress.done()});scrollBehaviorconst router = new VueRouter({ routes, scrollBehavior(to,from,saveTop){ if(saveTop){ return saveTop; }else{ return {x:0,y:0} } },})

May 15, 2022 · 1 min · jiezi

关于vue-router:进阶篇Vue-Router-核心原理解析

前言此篇为进阶篇,心愿读者有 Vue.js,Vue Router 的应用教训,并对 Vue.js 外围原理有简略理解; 不会大篇幅手撕源码,会贴最外围的源码,对应的官网仓库源码地址会放到超上,能够配合着看; 对应的源码版本是 3.5.3,也就是 Vue.js 2.x 对应的 Vue Router 最新版本; Vue Router 是规范写法,为了简略,上面会简称 router。 本文将用以下问题为线索开展讲 router 的原理: this.$router,this.$route 哪来的router 怎么晓得要渲染哪个组件this.$router.push 调用了什么原生 APIrouter-view 渲染的视图是怎么被更新的router 怎么晓得要切换视图的文末有总结大图 以下是本文应用的简略例子: // main.jsimport Vue from 'vue'import App from './App'import router from './router'new Vue({ el: '#app', // 挂载 Vue Router 实例 router, components: { App }, template: '<App/>'})// router/index.jsimport Vue from 'vue'import Router from 'vue-router'import Home from '@/components/Home'import About from '@/components/About'import Home1 from '@/components/Home1'// 应用 Vue Router 插件Vue.use(Router)// 创立 Vue Router 实例export default new Router({ routes: [ { path: '/', redirect: '/home' }, { path: '/home', name: 'Home', component: Home, children: [ { path: 'home1', name: 'Home1', component: Home1 } ] }, { path: '/about', name: 'About', component: About } ]})// App.vue<template> <div id="app"> <router-link to="/home">Go to Home</router-link> <router-link to="/about">Go to About</router-link> <router-link to="/home/home1">Go to Home1</router-link> <router-view/> </div></template><script>export default { name: 'App'}</script>页面体现举例: ...

April 4, 2022 · 5 min · jiezi

关于vue-router:vuerouter

vue@3.3.4源码解析前端路由次要有2局部组成:1 、url的解决; 2、 组件的加载[toc] install 函数export function install (Vue) { // 防止反复加载 if (install.installed && _Vue === Vue) return install.installed = true _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { // 根路由组件 this._routerRoot = this // 传入路由参数对象 this._router = this.$options.router // 重点 初始化 this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) // $router 和 $route 绑定在Vue的原型对象上,不便全局拜访 Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) // 注册router-view和router-link组件 Vue.component('RouterView', View) Vue.component('RouterLink', Link) const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}同vuex一样,vue-router的注入机会也是在beforeCreated.不过在destroyed的生命周期钩子中多了一步 ...

March 20, 2022 · 10 min · jiezi

关于vue-router:12-款最棒-Vue-开源-UI-库测评-特别针对国内使用场景推荐

本文首发:《12 款最棒 Vue 开源 UI 库测评 - 特地针对国内应用场景举荐》 Vue 3 公布曾经有一段时间了,就在刚刚过来的一年,各大组件库、框架纷纷对 Vue 3 做了优化和反对。整个前端从审慎应用 Vue 3 转向了开始拥抱 Vue 3。特地是年初年末几家大厂陆续开源或新公布了反对 Vue 3 的组件库或框架,十分值得在本文安利一波。本文举荐 12 款实用于中文使用者习惯的开源 Vue 3 UI 库或反对 Vue 3 的开源 UI 库。 我筛选了国内罕用的开源前端 UI 库,选出了 12 款来自国内互联网一线大厂或是商业化较好的企业 / 集体长期保护的收费开源 UI 库分享给大家。 Element Plus - 经典中的经典,全面反对 Vue 3TDesign - 鹅厂优质 UI 组件,配套工具完美,设计工整,文档清晰ArcoDesign - 字节跳动 UI 组件库开源,大厂逻辑,设计文档完满Ant Design Vue - 阿里前端 UI 库,面向企业级中后盾Naive UI - 宝藏 Vue UI 库,Vue UI 新星,从 Vue 3 起步VUX - 挪动端 UI 组件库,对微信敌对反对LuLu UI - 腾讯阅文前端出品 侧重于 C 端用户界面,轻量麻利Vant 3 - 有赞团队开源挪动 UI 组件库,全面反对 Vue 3Vuestic UI - 寰球 Vue 前 15 顶级团队开发,国际化劣势NutUI 3.0 - 京东出品,挪动端敌对,面向电商业务场景View UI - 企业级 to b 中后盾 UI 组件库,面向企业敌对Vuetify 3 - 老牌 Vue UI ,基于谷歌的 Material Design 款式开发如果你正在搭建后盾管理工具,又不想解决前端问题,举荐应用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可疾速搭建属于你本人的后盾管理工具,一周工作量缩减至一天,详见本文文末。 ...

March 7, 2022 · 3 min · jiezi

关于vue-router:Vue-Router-初探一

Vue Router 作用:将组件 (components) 映射到路由 (routes),而后通知 Vue Router 在哪里渲染它们。 router-link应用 router-link 组件来导航,通过传入 to 属性指定链接. <router-link to="/foo">Go to Foo</router-link>官网解释:<router-link> 组件反对用户在具备路由性能的利用中 (点击) 导航。 通过 to 属性指定指标地址,默认渲染成带有正确链接的 标签链接:https://router.vuejs.org/zh/a... router-view通过 router-view 将对应路由匹配到的组件进行渲染。简言之,router-view 能够定制你的路由组件显示的地位 <router-view></router-view>官网解释:<router-view> 组件是一个 functional 组件,渲染门路匹配到的视图组件。<router-view> 渲染的组件还能够内嵌本人的 <router-view>,依据嵌套门路,渲染嵌套组件。链接:https://router.vuejs.org/zh/a... 路由注入:1.定义路由组件(组件具体内容)组件名须要与路由定义的中央一样,不便路由查找对应组件 const Foo = { template: '<div>foo</div>' }const Bar = { template: '<div>bar</div>' }2.定义路由匹配的组件路由数组,个别由 门路 与 组件 组成一个对照表 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar }]3.创立路由实例 const router = new VueRouter({ routes // 还能够传别的配置参数})4.挂载路由到根实例 ...

December 17, 2021 · 1 min · jiezi

关于vue-router:Vue的keepalive-实现滑动定位缓存

Situation首先该我的项目是一个h5我的项目,有一个滑动列表页,当我滑动点击列表项,会进入到详情页,当我退出的时候,我心愿还能定位到我刚刚点进来的地位。 Task咱们的目标是定位滑动地位。在返回的时候应该回到刚刚的滑动地位。所以咱们有两个关键点,第一个是怎么定位滑动地位,第二是返回的时候怎么回到滑动地位。滑动地位的话咱们能够获取到滑动容器的scrollTop值,而后返回的时候设置滑动容器的scrollTop就能够定位。 Action1. 采纳scrollBehavior 函数 应用前端路由,当切换到新路由时,想要页面滚到顶部,或者是放弃原先的滚动地位,就像从新加载页面那样。 vue-router 能做到,而且更好,它让你能够自定义路由切换时页面如何滚动。const router = new VueRouter({ routes: [...], scrollBehavior (to, from, savedPosition) { // return 冀望滚动到哪个的地位 }})scrollBehavior 办法接管 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 后退/后退 按钮触发) 时才可用。所以在定义router的中央,挂载了scrollBehavior函数。 问题:savedPosition打印进去的x,y坐标,不是想要的,不精确。剖析:因为scrollBehavior是挂载在路由身上,它对应的应该是一个整个路由组件,而我的滑动区域是组件外面的一个固定区域,也就是说,我应该把scrollTop挂在滑动区域,然而整个页面构造不容许我这样做。所以尝试第二种办法,keep-alive2. keep-alive Keep-alive 是什么?这可不是http放弃长链接的keep-alive哦keepalive 是 Vue 内置的一个组件,能够使被蕴含的组件保留状态,或防止从新渲染 。也就是所谓的组件缓存然而有个问题是,它不会缓存咱们的滑动地位,咱们须要手动记录地位。 a. 在滑动的时候记录滑动组件的scrollTop (这里能够加一个节流函数) onScroll(e: Event) { const { scrollTop } = e.target as HTMLElement; this.scrollTop = scrollTop; // 记录滑动地位,回来时复原到此地位}keep-alive的申明周期执行 页面第一次进入,钩子的触发程序: created-> mounted-> activated, 退出时触发 deactivated 当再次进入(后退或者后退)时,只触发 activated事件挂载的办法等,只执行一次的放在 mounted 中;组件每次进去执行的办法放在 activated 中; b. 所以咱们在activated申明周期中定位咱们的地位 ...

October 11, 2021 · 1 min · jiezi

关于vue-router:动态路由的使用体验回顾

应用形式应用vue-router的addRoutes办法来进行动静路由增加 应用起因起初是据说动静路由能够进步加载速度,所以想应用体验一下,同时也优化一下自带的路由控制代码。 论断不好用应用动静路由有很多的毛病1、无奈辨别404与无权限页面。应用了动静路由后,无权限页面只会导向404,起因很简略,你页面路由都没加载进来。2、对速度的晋升,假的。模块只有是异步引入,根本就差不了多少 以上就是我应用动静路由的感想了,论断就是不举荐应用把。

September 8, 2021 · 1 min · jiezi

关于vue-router:简单的vuerouter实现

模仿vue-router实现简略的路由性能, 仿照vue-router应用的办法, 在router/index.js中配置路由文件从而逐渐实现目标. 实现繁难的PVueRouter类, 首先实现动态install办法, 保障 Vue.use(Router)这一步失常运行. 并挂载到组件中的$router属性 let Vue // 定义Vue变量,在 install 办法中赋为真正的构造函数应用class PVueRouter { constructor(options){ this.$options = options // 为了不便,在此处将路由实例赋给Vue原型,这样在路由组件内能够通过 this.$router 拜访. 与上面的 mixin 同样作用 Vue.prototype.$router = this } // 静态方法 install static install(_Vue){ Vue = _Vue // 工作1:挂载$router 或者应用 mixin 在根组件混入以后路由实例 Vue.mixin({ beforeCreate() { // 只有根组件领有router选项 if (this.$options.router) { // vm.$router Vue.prototype.$router = this.$options.router; } } }); Vue.component('router-link', RouterLink) Vue.component('router-view', RouterView) // 首先要定义 router-link 及 router-view 组件 }}// import Router from 'pvue-router' // 引入简易版自定义的router类// Vue.use(Router)此时运行会发现报错, 揭示还须要定义 router-link 及 router-view ...

August 22, 2021 · 1 min · jiezi

关于vue-router:一文你带快速认识VueRouter路由

摘要:Vue Router是Vue.js 官网的路由管理器。它和Vue.js的外围深度集成,能够十分不便的用于SPA应用程序的开发。本文分享自华为云社区《Vue-Router路由疾速理解与利用》,原文作者:北极光之夜。 一.速识概念:1. 后端路由:1.依据不同的用户URL申请,返回不同的内容,实质上是URL申请地址与服务器资源之间的对应关系。 2.然而呢,后端渲染存在性能问题。 2. 前端路由:3.所以呈现了Ajax前编渲染 ,前端渲染能进步性能,然而不反对浏览器的后退后退操作。 4.这时又呈现了SPA (Single Page Application)单页面应用程序,整个网站只有一个页面,内容的变动通过Ajax部分更新实现、同时反对浏览器地址栏的后退和后退操作。 5.SPA实现原理之一就是基于URL地址的 hash (hash的变动会导致浏览器记录拜访历史的变动、然而hash的变动不会触发新的URL申请) 。在实现SPA过程中, 其中最外围的技术点就是前端路由。 6.前端路由就是依据不同的用户事件,显示不同的页面内容。实质就是用户事件与事件处理函数之间的对应关系。 3.Vue Router:这是官网应用文档链接。:https://router.vuejs.org/zh/g... Vue Router是Vue.js 官网的路由管理器。它和Vue.js的外围深度集成,能够十分不便的用于SPA应用程序的开发。 它的性能如下: 1.反对HTML5历史模式或hash模式。2.反对嵌套路由。3.反对路由参数。4.反对编程式路由。5.反对命名路由。 二.根本应用:前提:上面将会以一个HTML单页面演示Vue Router的根本应用步骤。在vue我的项目里也是一样的原理。以后单页面根本代码: <!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <div id="app"> </div> <script> const app = new Vue({ el:"#app", data: {} }) </script></body></html>能够看到什么都没有: 上面开始应用的具体步骤: 1.引入相干的文件:单页面必定得先导入vue文件与vue-router文件,这样咱们才可能应用路由。 <script src="https://unpkg.com/vue/dist/vue.js"></script><script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>2.增加路由链接:以下是一个vue提供的标签,默认会被渲染为a标签。其中有一个to属性,这个to属性会被渲染为href属性,默认值被渲染为 # 结尾的hash地址。简略来说就是当用户点击不同时跳转不同内容,而这个标签就是用户要点击的货色,相当于a标签嘛。 <router-link to="/..." >...</router-link>给咱们的单页面上加一个page1和一个page2的链接: <div id="app"> <router-link to="/page1">Page1</router-link> <router-link to="/page2">Page2</router-link> </div>3.增加路由填充位:上面这个标签叫路由填充位,就是说将来通过咱们的路由规定匹配到的组件,将会被渲染到 router-view所在位置。简略来说,就是用户点击路由链接,那得跳转内容吧,咱们晓得的是必定不是整个页面都跳转,只是页面内相干的部分产生内容扭转,这个部分就是router-view所在显示的区域。 <router-view></router-view>给咱们的页面增加: <div id="app"> <router-link to="/page1">Page1</router-link> <router-link to="/page2">Page2</router-link> <router-view></router-view> </div>4.定义路由组件:既然要显示不同的内容,那必定是用一个组件保留一份内容。上面咱们给单页面定义page1,page2这两个组件。 <script> const Page1 = { template: '<h1>我是北极光之夜1号</h1>' } const Page2 = { template: '<h1>我是北极光之夜2号</h1>' } const app = new Vue({ el:"#app", data: {} }) </script>5.配置路由规定井创立路由实例:routes是路由规定数组。每个路由规定都是一个配置对象, 其中至多蕴含path 和component 两个属性,path 示意以后路由规定匹配的hash 地址,component 示意以后路由规定对应要展现的组件。简略来说就是你点击那个链接对应的地址要对应的是哪个内容的组件。path跟router-link标签里的地址要一样,别写错了。 ...

June 10, 2021 · 4 min · jiezi

关于vue-router:vuerouter使用

import {createRouter, createWebHashHistory } from 'vue-router'// 1. 定义路由组件import Home from '../components/demo'import Hello from '../components/HelloWorld'import active from '../components/active'import nest from '../components/nest'import nesta from '../components/nest/a'import nestb from '../components/nest/b'import nestc from '../components/nest/c'// 2. 定义路由,每个路由映射到一个组件let routes = [ // component能够是多个,改为components,设置default { path: '/', name: '/', // 多组件组成 components:{ default: Home, nestc }, meta: { title: 'mainpage' } }, // 重定向 { path: '/demo', redirect: '/', }, // 失常 { path: '/hello', name: 'hello', component: Hello, // 别名 alias:['/ceshi', '/123'], meta: { title: 'hello' } }, // 动静路由,参数$route.params.id获取 { path: '/active/:id', name: 'active', component: active, meta: { title: 'active' }, // 对于蕴含命名视图的路由,你必须别离为每个命名视图增加 `props` 选项: props: { abd: 123, id: 123 }, }, // 嵌套 { path: '/nest', component: nest, children: [ { path: '', component: nestc }, { path: 'a', component: nesta }, { path: 'b', component: nestb } ], meta:{ title: 'nest' } }]// 3. 创立路由实例并传递‘routes’配置,const hash = createWebHashHistory()const router = createRouter({ // 4. 外部提供了 history 模式的实现。为了简略起见,咱们在这里应用 hash 模式。 history: hash, routes})// 5. 监听路由router.beforeEach((to, from, next) => { if (to.meta.title) {//如果设置题目,拦挡后设置题目 document.title = to.meta.title } next()})export default router

May 21, 2021 · 1 min · jiezi

关于vue-router:HTML5-History-模式

vue-router 默认 hash 模式 —— 应用 URL 的 hash 来模仿一个残缺的 URL,于是当 URL 扭转时,页面不会从新加载。 如果不想要很丑的 hash,咱们能够用路由的 history 模式,这种模式充分利用 history.pushState API 来实现 URL 跳转而毋庸从新加载页面。 const router = new VueRouter({ mode: 'history', routes: [...]}) 当你应用 history 模式时,URL 就像失常的 url,例如 http://yoursite.com/user/id,也难看! 不过这种模式要玩好,还须要后盾配置反对。因为咱们的利用是个单页客户端利用,如果后盾没有正确的配置,当用户在浏览器间接拜访 http://oursite.com/user/id 就会返回 404,这就不难看了。 所以呢,你要在服务端减少一个笼罩所有状况的候选资源:如果 URL 匹配不到任何动态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。 后端配置例子留神:下列示例假如你在根目录服务这个利用。如果想部署到一个子目录,你须要应用 Vue CLI 的 publicPath 选项 和相干的 router base property。你还须要把下列示例中的根目录调整成为子目录 (例如用 RewriteBase /name-of-your-subfolder/ 替换掉 RewriteBase /)。 #Apache<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]</IfModule> 除了 mod_rewrite,你也能够应用 FallbackResource。 ...

March 15, 2021 · 2 min · jiezi

关于vue-router:基于vite2vue3xvant3小视频直播聊天实战vue30仿抖音短视频

我的项目介绍Vue3DouYin 基于vite2+vue3.0+vant3+v3popup等技术开发的一款挪动端仿抖音/快手App界面短视频实例我的项目。实现了滑动切换视频、暂停/进度条展现、点赞/评论/聊天、弹幕/送礼物/红包等性能。 技术栈编码+技术:vscode + vite2/vue3.0/vue-router/vuex4UI组件库:vant3 (有赞挪动端vue3组件库)弹层组件:v3popup(挪动端vue3弹框组件)字体图标:阿里iconfont图标导航条+底部栏:自定义顶部navbar/tabbar标签栏组件 我的项目目录构造 vue3自定义手机端弹框组件v3popup一款应用vue3开发的mobile端自定义弹框组件。完满的融入到我的项目中各个弹窗场景。 因为之前写过一篇这方面的分享,大家感兴趣的话能够去看下。vue3.0系列之自定义mobile版弹出层组件|vue3挪动端弹框 vite2我的项目配置/** * Vite2我的项目配置 */import vue from '@vitejs/plugin-vue'import path from 'path'/** * @type {import('vite').UserConfig} */export default { plugins: [vue()], build: { // 根本目录 // base: '/', /** * 输入文件目录 * @default dist(默认) */ // outDir: 'target', }, // 环境配置 server: { // 自定义接口 port: 3000, // 是否主动浏览器关上 open: false, // 是否开启https https: false, // 服务端渲染 ssr: false, // 代理配置 proxy: { // ... } }, // 设置门路别名 alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@views': path.resolve(__dirname, './src/views') }}vue3主入口main.js配置引入一些路由/状态治理,公共组件及款式。 ...

February 2, 2021 · 4 min · jiezi

关于vue-router:vue登录与注册判断页面权限是否需要登录保存状态30天内免登陆

通常vue注册或登录保留用户状态须要用到Vuex和Vue Router Vuex:保留用户状态,是否登录等Vue Router:页面门路与权限 在一个Vue我的项目中,store文件夹个别是Vuex文件夹,咱们能够创立一个index.js文件 最简略的 Store import Vue from 'vue'import Vuex from 'vuex' Vue.use(Vuex)const store = new Vuex.Store({ state () { return { //sessionStorage uid:window.sessionStorage.getItem('uid'),//storage默认存储为字符串,转换成本来数据格式 isLogin: window.sessionStorage.getItem('isLogin'), //localstorge autoLoginIn30Days:window.localStorage.getItem('autoLoginIn30Days'), LocalUid:window.localStorage.getItem('LocalUid'), localIsLogin:window.localStorage.getItem('localIsLogin'), } }, getters: { }, mutations: { //sessionStorage $_setLogin (state, value) { state.isLogin = value sessionStorage.setItem('isLogin', value) }, $_setUID(state,value){ state.uid=value sessionStorage.setItem('uid',value) }, // localstorge $_setLocalLogin (state, value) { state.localIsLogin = value localStorage.setItem('localIsLogin', value) }, $_setLocalUID(state,value){ state.LocalUid=value localStorage.setItem('LocalUid',value) }, AUTO_LOGIN:(state,value)=>{ state.autoLoginIn30Days=value if(state.autoLoginIn30Days){ window.localStorage.setItem('LocalUid',state.LocalUid)// window.localStorage.setItem('localIsLogin',state.localIsLogin) window.localStorage.setItem('autoLoginIn30Days',state.autoLoginIn30Days) }else{ window.localStorage.removeItem('localIsLogin') window.localStorage.removeItem('LocalUid',state.LocalUid) window.localStorage.setItem('autoLoginIn30Days',state.autoLoginIn30Days) } }, LOGOUT:(state)=>{ state.localIsLogin=null state.localUser=null state.autoLoginIn30Days=false window.sessionStorage.removeItem('isLogin') window.sessionStorage.removeItem('user') window.localStorage.removeItem('localIsLogin') window.localStorage.removeItem('localUser') window.localStorage.setItem('autoLoginIn30Days',false) } }}) export default store更改 Vuex 的 store 中的状态的惟一办法是提交 mutation,更改store.state的办法都写在mutations: {}中,这外面咱们写了几个办法:办法1.应用sessionStorage保留以后登录uid和是否已登录isLogin办法2.localStorage保留登录uid和是否已登录isLogin ...

December 17, 2020 · 2 min · jiezi

关于vue-router:vue鉴权的两种方式之路由拦截

vue中鉴权的两种办法罕用的鉴权有两种:一种是路由拦挡,一种是动静路由。 路由拦挡通过vue-router的beforeEach办法进行每一次路由拜访的拦挡,判断拦挡信息中是否有鉴权要求或者权限校验,以此来实现鉴权。如果权限不够,拜访的门路尽管存在但会被拦挡。 动静路由在登录后依据用户信息以及权限动静地增加正确的权限路由,如果权限不够,拜访的门路是不存在的。 比拟路由拦挡实现起来绝对简略,只需在登录的时候保留用户权限信息,而后编写路由的时候将须要鉴权的路由加上权限信息,而后在beforeEach拦挡中进行判断解决是否可进入,并且,即便通过f5刷新页面,只有用户信息权限信息保留下来,就能够实现鉴权。而动静路由,实现起来绝对麻烦,可能还须要要后端配合,不过看起来或者安全性上更高级一些,毕竟你即便晓得有某个权限路由,然而我基本就不渲染,你就相对无奈走进去。动静路由须要登录后记录用户权限菜单列表,这个列表可能是后端给的也可能是前端本人总结。前端本人总结的话就须要依据不同权限用户生成不同的路由列表,而后在登录后进行按需渲染。且这个动静路由加载判断的条件以及实现逻辑会比拟复杂。须要思考f5刷新后动静路由从新加载,因为此时不会再次经验登录操作,所以动静路由加载不会放在登录性能的回调中,但又必须是登陆后渲染,所以就同样放在router的beforeEach这个办法里,只是判断条件须要改为用户权限信息已存在但动静路由为渲染加载的,具体实现请看例子。 例子-路由拦挡// 文件目录|--webapp|----src|------api|------pages|------routers|--------modules|--------index.js|------utils|------App.vue|------main.js|----package.json|----vue.config.js// webapp/src/routers/modules/user.js// 路由能够依据模块辨别,我这边只是细化了,你也能够把所有路由都放一个文件夹,只是可能看起来稍多const userRouter = [ { path: '/user/router1', // 这样写能够实现按需加载,打包细化,webpackChunkName就是打包生成的文件名前缀 component: () => import(/* webpackChunkName: "router1" */ '@/pages/user/router1.vue'), meta: { // 这里轻易放一些自定义的信息,permission就是权限信息,后续会在beforeEach中进行判断,必须amin能力进入 // 有时候咱们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只须要应用 命名 chunk,一个非凡的正文语法来提供 chunk name (须要 Webpack > 2.4)。 // https://router.vuejs.org/zh/guide/advanced/lazy-loading.html title: 'router1', permission: 'admin' } },{ path: '/user/router2', component: () => import(/* webpackChunkName: "router2" */ '@/pages/user/router2.vue'), meta: { title: 'router2' } }]export default userRouter;// webapp/src/router/index.js// 这里把所有细化的模块路由汇总import Vue from 'vue';import Router from 'vue-router';import userRouter from '@/routers/modules/user.js';// vue应用vue-router这个插件Vue.use(Router);const router = new Router({ routes: [ { path: '/', redirect: '/home' }, ...userRouter, { path: '*', redirect: '/404' } ]});export default router;// webapp/src/main.js// 个别router的beforeEach都会放到main.js中,在整个vue实例化时加载。import Vue from 'vue';import router from '@/routers/index.js';// 这里就能够进行vue-router的beforeEach拦挡了,你也能够放其余中央,我比拟喜爱放这router.beforeEach((to, from, next) => { document.title = to.meta.title || ''; // 这里先获取下用户信息,我偷懒用sessionStorage存了 // 外面蕴含了用户权限,用户各种信息等 const user = JSON.parse(sessionStorage.getItem('ms_user')); // 这里必须加上to.path !== 'login'的判断,不然会陷入有限循环, // 因为逻辑是第一次进来,判断用户信息不存在,即!user为true,因为应用的是next('/login')而非next(), // 会再次进入路由跳转,next()办法没有参数是间接进入页面,不会再次进入路由拦挡,有参数则会,因为跳转, // 所以再次进入路由,再次判断,再次进入路由,再次判断,周而复始有限循环 // 所以肯定要加to.path !== 'login'的判断 if (!user && to.path !== '/login') { next('/login'); } else if (to.meta.permission) { user.permission === to.meta.permission ? next() : message.alert('没有权限'); } else { next(); }});new Vue({ router, render: h => h(App)}).$mount('#app');如果心愿交换能够私信或者wx:zilian_taoyaoyao ...

November 9, 2020 · 1 min · jiezi

关于vue-router:vuerouter源码解析

装置增加全局的 beforeCreate、destroyed 生命周期办法;Vue.prototype 上增加$route和$router 属性;注册 router-view 和 router-link 组件;增加新的生命周期 beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate;beforeCreate 周期中,将路由 VueRouter 实例 router 增加根组件(蕴含 VueRouter 实例选项的组件)上,同时增加路由信息\_route 属性到根组件上,设置该属性响应式。子组件\_routerRoot 属性指向该根组件。Vue.prototy 上的$router和$route 属性值也是来自于根组件的 router 和\_route。根组件的 beforeCreate 周期中调用了 VueRouter 实例的 init 办法进行初始化。function install(Vue) { if (install.installed && _Vue === Vue) { return; } install.installed = true; _Vue = Vue; var isDef = function (v) { return v !== undefined; }; var registerInstance = function (vm, callVal) { var i = vm.$options._parentVnode; //组件标签节点 if ( isDef(i) && isDef((i = i.data)) && isDef((i = i.registerRouteInstance)) ) { i(vm, callVal); } }; Vue.mixin({ beforeCreate: function beforeCreate() { if (isDef(this.$options.router)) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this); Vue.util.defineReactive( this, "_route", this._router.history.current ); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } registerInstance(this, this); }, destroyed: function destroyed() { registerInstance(this); }, }); Object.defineProperty(Vue.prototype, "$router", { get: function get() { return this._routerRoot._router; // this._routerRoot 根组件 }, }); Object.defineProperty(Vue.prototype, "$route", { get: function get() { return this._routerRoot._route; // this._routerRoot 根组件 }, }); Vue.component("RouterView", View); Vue.component("RouterLink", Link); var strats = Vue.config.optionMergeStrategies; // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;}router 实例VueRouter 结构器中,依据配置的路由模式生成对应的 History 实例。调用了 createMatcher 办法,依据选项中的路由生成门路、名称和路由配置项的映射,并返回 matcher,提供 match 和 addRoutes 办法。依据路由模式配置,生成对应的 History 实例。var VueRouter = function VueRouter(options) { if (options === void 0) options = {}; this.app = null; this.apps = []; this.options = options; this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; this.matcher = createMatcher(options.routes || [], this); var mode = options.mode || "hash"; this.fallback = mode === "history" && !supportsPushState && options.fallback !== false; if (this.fallback) { mode = "hash"; } if (!inBrowser) { mode = "abstract"; } this.mode = mode; switch (mode) { case "history": this.history = new HTML5History(this, options.base); break; case "hash": this.history = new HashHistory(this, options.base, this.fallback); break; case "abstract": this.history = new AbstractHistory(this, options.base); break; default: if (process.env.NODE_ENV !== "production") { assert(false, "invalid mode: " + mode); } }};RouterView 组件props 的 name 属性匹配对应的路由命名视图。调用 render 函数,渲染以后路由对应的组件。通过 parent.\$route 读取以后的路由信息,而后向上查找,依据 routerView 路由组件标识 routerView,获取以后的路由层级。依据路由层级和命名视图的名称获取对应的组件选项,依据组件选项生成对应的节点(VNode 实例)返回。var View = { name: "RouterView", functional: true, props: { name: { type: String, default: "default", }, }, render: function render(_, ref) { var props = ref.props; var children = ref.children; var parent = ref.parent; var data = ref.data; // used by devtools to display a router-view badge data.routerView = true; // directly use parent context's createElement() function // so that components rendered by router-view can resolve named slots var h = parent.$createElement; var name = props.name; var route = parent.$route; // 读取路由信息 var cache = parent._routerViewCache || (parent._routerViewCache = {}); // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. var depth = 0; var inactive = false; while (parent && parent._routerRoot !== parent) { var vnodeData = parent.$vnode ? parent.$vnode.data : {}; if (vnodeData.routerView) { depth++; } if ( vnodeData.keepAlive && parent._directInactive && parent._inactive ) { inactive = true; } parent = parent.$parent; } data.routerViewDepth = depth; // render previous view if the tree is inactive and kept-alive if (inactive) { var cachedData = cache[name]; var cachedComponent = cachedData && cachedData.component; if (cachedComponent) { // #2301 // pass props if (cachedData.configProps) { fillPropsinData( cachedComponent, data, cachedData.route, cachedData.configProps ); } return h(cachedComponent, data, children); } else { // render previous empty view return h(); } } var matched = route.matched[depth]; // 获取以后层级的组件 var component = matched && matched.components[name]; // 获取该层级下命名视图对应的组件 // render empty node if no matched route or no config component if (!matched || !component) { cache[name] = null; return h(); } // cache component cache[name] = { component: component }; // attach instance registration hook // this will be called in the instance's injected lifecycle hooks data.registerRouteInstance = function (vm, val) { // val could be undefined for unregistration var current = matched.instances[name]; if ((val && current !== vm) || (!val && current === vm)) { matched.instances[name] = val; } }; // also register instance in prepatch hook // in case the same component instance is reused across different routes (data.hook || (data.hook = {})).prepatch = function (_, vnode) { matched.instances[name] = vnode.componentInstance; }; // register instance in init hook // in case kept-alive component be actived when routes changed data.hook.init = function (vnode) { if ( vnode.data.keepAlive && vnode.componentInstance && vnode.componentInstance !== matched.instances[name] ) { matched.instances[name] = vnode.componentInstance; } }; var configProps = matched.props && matched.props[name]; // save route and configProps in cachce if (configProps) { extend(cache[name], { route: route, configProps: configProps, }); fillPropsinData(component, data, route, configProps); } return h(component, data, children); },};route.matched 顺次保留了以后门路对应的组件选项,以上面路由配置为例:/home/tab/list门路对应的 matched 是:[Home, Tab, List]。const router = new Router({ mode: "history", routes: [ { name: "home", path: "/home", component: Home, children: [ { name: "tab", path: "tab", component: Tab, children: [ { name: "list", path: "list", component: List, }, ], }, ], }, ],});实例 init 初始化根组件的 beforeCreate 周期中调用了 VueRouter 实例 init 办法。默认初始门路为“/”,并依据该门路获取对应的路由信息,而后和以后实在的 url 门路比对,更新为以后 url 对应的路由信息,更改\_router 值,因为设置了\_router 属性响应式,且 router-view 读取了该值,当\_router 从新赋值时,就有从新渲染 router-view 组件,加载以后门路对应的组件页面。VueRouter.prototype.init = function init(app /* Vue component instance */) { // 每个组件初始化路由 var this$1 = this; assert( install.installed, "not installed. Make sure to call `Vue.use(VueRouter)` " + "before creating root instance." ); this.apps.push(app); // set up app destroyed handler // https://github.com/vuejs/vue-router/issues/2639 app.$once("hook:destroyed", function () { // clean out app from this.apps array once destroyed var index = this$1.apps.indexOf(app); if (index > -1) { this$1.apps.splice(index, 1); } // 组件销毁时从apps移除 // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } if (!this$1.app) { // clean up event listeners // https://github.com/vuejs/vue-router/issues/2341 this$1.history.teardownListeners(); } }); // main app previously initialized // return as we don't need to set up new history listener if (this.app) { return; } this.app = app; var history = this.history; if (history instanceof HTML5History || history instanceof HashHistory) { var setupListeners = function () { history.setupListeners(); }; history.transitionTo( history.getCurrentLocation(), setupListeners, setupListeners ); } history.listen(function (route) { this$1.apps.forEach(function (app) { app._route = route; // 更新路由 }); });};// 初始路由信息var START = createRoute(null, { path: "/",});路由跳转HTML5History,HashHistory,AbstractHistory 继承了 Histroy,Histroy 实例上的 router 指向 VueRouter 实例。Histroy 提供了 transitionTo 和 confirmTransition 办法,将在路由跳转时调用。/* */var History = function History (router, base) { this.router = router;// VueRouter实例 this.base = normalizeBase(base);// 格式化根底门路 // start with a route object that stands for "nowhere" this.current = START;// "/"对应的路由信息(初始路由信息) this.pending = null; this.ready = false; this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; this.listeners = []; }; History.prototype.transitionTo = function transitionTo ( location,// 以后URL对应路由的门路 onComplete, onAbort ) { var this$1 = this; var route = this.router.match(location, this.current);// 调用VueRouter实例的match办法,返回行将跳转的路由信息,current以后的路由信息 this.confirmTransition( route, function () { var prev = this$1.current; this$1.updateRoute(route);// 更新为跳转后的路由信息 }, ... ); }; History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) { ... }; History.prototype.updateRoute = function updateRoute (route) { this.current = route; this.cb && this.cb(route); }; History.prototype.setupListeners = function setupListeners () { ... }; History.prototype.teardownListeners = function teardownListeners () {// 革除事件监听 ... };var HTML5History = /*@__PURE__*/ (function (History) { //继承了History function HTML5History(router, base) { History.call(this, router, base); this._startLocation = getLocation(this.base); } if (History) HTML5History.__proto__ = History; // 继承History,复用结构器上的办法 HTML5History.prototype = Object.create(History && History.prototype); // 继承History,复用实例上的办法 HTML5History.prototype.constructor = HTML5History;// 继承History,复用结构器 HTML5History.prototype.setupListeners = function setupListeners() { var this$1 = this; if (this.listeners.length > 0) { return; } var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { this.listeners.push(setupScroll()); //增加事件 } var handleRoutingEvent = function () { var current = this$1.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. var location = getLocation(this$1.base); if (this$1.current === START && location === this$1._startLocation) { return; } this$1.transitionTo(location, function (route) { if (supportsScroll) { handleScroll(router, route, current, true); } }); }; window.addEventListener("popstate", handleRoutingEvent); this.listeners.push(function () { window.removeEventListener("popstate", handleRoutingEvent); }); }; HTML5History.prototype.go = function go(n) { // 路由回退或者后退 window.history.go(n); }; HTML5History.prototype.push = function push(location, onComplete, onAbort) { // 路由跳转 var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, function (route) { pushState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,设置页面跳转url,增加一条记录到history中 handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort ); }; HTML5History.prototype.replace = function replace( location, onComplete, onAbort ) { var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, function (route) { replaceState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,批改以后路由,并批改history以后记录 handleScroll(this$1.router, route, fromRoute, false); // 页面滚动 onComplete && onComplete(route); // 跳转胜利回调函数 }, onAbort ); }; /** * 获取路由门路(不蕴含根底门路) */ HTML5History.prototype.getCurrentLocation = function getCurrentLocation() { return getLocation(this.base); }; return HTML5History;})(History);页面跳转路由跳转时先依据以后路由和跳转路由对应的组件,别离提取出须要更新,须要解冻(暗藏或移除),须要激活(创立或显示)的组件。而后顺次调用组件上路由相干的生命周期钩子函数,包含加载异步组件。调用路由生命周期钩子函数后,更新以后的路由数据。最初更新 history 状态,并保留页面的滚动状态。因为对路由数据设置了响应式,更新以后的路由数据会触发页面从新渲染HTML5History.prototype.push = function push(location, onComplete, onAbort) { // 路由跳转 var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, // 跳转选项 function (route) { pushState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,设置页面跳转url,增加一条记录到history中 handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort );};History.prototype.transitionTo = function transitionTo( location, // 跳转选项 onComplete, onAbort) { var this$1 = this; var route = this.router.match(location, this.current); // 调用VueRouter实例的match办法,返回跳转选项匹配的路由信息 this.confirmTransition( route, function () { var prev = this$1.current; this$1.updateRoute(route); // 更新为曾经跳转的路由 onComplete && onComplete(route); this$1.ensureURL(); this$1.router.afterHooks.forEach(function (hook) { hook && hook(route, prev); }); // fire ready cbs once if (!this$1.ready) { this$1.ready = true; this$1.readyCbs.forEach(function (cb) { cb(route); }); } }, function (err) { if (onAbort) { onAbort(err); } if (err && !this$1.ready) { this$1.ready = true; this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); } } );};History.prototype.confirmTransition = function confirmTransition( route, onComplete, onAbort) { var this$1 = this; var current = this.current; var abort = function (err) { // changed after adding errors with // https://github.com/vuejs/vue-router/pull/3047 before that change, // redirect and aborted navigation would produce an err == null if (!isRouterError(err) && isError(err)) { if (this$1.errorCbs.length) { this$1.errorCbs.forEach(function (cb) { cb(err); }); } else { warn(false, "uncaught error during route navigation:"); console.error(err); } } onAbort && onAbort(err); }; if ( isSameRoute(route, current) && // in the case the route map has been dynamically appended to route.matched.length === current.matched.length ) { this.ensureURL(); // 确保以后路由和页面的url统一 return abort(createNavigationDuplicatedError(current, route)); } var ref = resolveQueue(this.current.matched, route.matched); // 比对新旧路由对应的组件嵌套信息,判断出哪些组件须要更新,哪些组件须要激活,哪些组件须要解冻 var updated = ref.updated; // 以后路由和行将跳转路由雷同局部(组件)(更新) var deactivated = ref.deactivated; // 以后路由不同局部(组件)(解冻) var activated = ref.activated; // 行将跳转路由不同局部(组件)(激活) var queue = [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks(updated), // in-config enter guards activated.map(function (m) { return m.beforeEnter; }), // async components resolveAsyncComponents(activated) // 返回函数function(to, from, next) ); this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { return abort(createNavigationCancelledError(current, route)); } try { // 调用钩子办法 hook(route, current, function (to) { if (to === false) { // next(false) -> abort navigation, ensure current URL this$1.ensureURL(true); abort(createNavigationAbortedError(current, route)); } else if (isError(to)) { this$1.ensureURL(true); abort(to); } else if ( typeof to === "string" || (typeof to === "object" && (typeof to.path === "string" || typeof to.name === "string")) ) { // next('/') or next({ path: '/' }) -> redirect abort(createNavigationRedirectedError(current, route)); if (typeof to === "object" && to.replace) { this$1.replace(to); } else { this$1.push(to); } } else { // confirm transition and pass on the value // 调用下一个钩子办法 next(to); } }); } catch (e) { abort(e); } }; runQueue(queue, iterator, function () { // 调用组件leave、update等钩子函数,异步获取组件选项,创立组件结构器,最初调用这里的回调函数 var postEnterCbs = []; var isValid = function () { return this$1.current === route; }; // wait until async components are resolved before // extracting in-component enter guards var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); // 异步加载组件选项中的enter钩子办法 var queue = enterGuards.concat(this$1.router.resolveHooks); runQueue(queue, iterator, function () { if (this$1.pending !== route) { return abort(createNavigationCancelledError(current, route)); } this$1.pending = null; onComplete(route); // 更新路由 if (this$1.router.app) { this$1.router.app.$nextTick(function () { postEnterCbs.forEach(function (cb) { cb(); }); }); } }); });};

November 1, 2020 · 9 min · jiezi

关于vue-router:vuexroutersync如何使用

简略来讲vuex-router-sync插件就是将vue-router的状态同步到vuex中 一、装置npm下载地址:https://www.npmjs.com/package...> npm i vuex-router-sync --save二、应用import { sync } from 'vuex-router-sync'import store from './vuex/store'import router from './router'sync(store, router, {moduleName: 'RouteModule'})const app = new Vue({ router, store,}).$mount('#app');打印store.state即可看到以后路由状态 三、应用场景如果您想在一个组件中显示一条音讯,心愿在简直每一个页面上都显示“Have a nice day, Jack”,除了首页,因为首页要显示"Welcome back, Jack".借助vuex-router-sync,您能够轻松实现 const Top = { template: '<div>{{message}}</div>', computed: { message() { return this.$store.getters.getMessage; } },};const Bar = { template: '<div>{{message}}</div>', computed: { message() { return this.$store.getters.getMessage; } }};const routes = [{ path: '/top', component: Top, name: 'top' }, { path: '/bar', component: Bar, name: 'bar' },];const router = new VueRouter({ routes});const store = new Vuex.Store({ state: { username: 'Jack', phrases: ['Welcome back', 'Have a nice day'], }, getters: { getMessage(state) { return state.route.name === 'top' ? `${state.phrases[0]}, ${state.username}` : `${state.phrases[1]}, ${state.username}`; }, },});// sync store and router by using `vuex-router-sync`sync(store, router);const app = new Vue({ router, store,}).$mount('#app');不然的话,你可能须要在vue-router的钩子函数里监听,或在watch里$route,而后批改store值来实现。 ...

October 31, 2020 · 2 min · jiezi

关于vue-router:浅析-vuerouter-源码和动态路由权限分配

第 72 篇原创好文~ 本文首发于政采云前端团队博客:浅析 vue-router 源码和动静路由权限调配 浅析 vue-router 源码和动静路由权限调配 背景上月立过一个 flag,看完 vue-router 的源码,可到前面逐步发现 vue-router 的源码并不是像很多总结的文章那么容易了解,浏览过你就会发现外面的很多中央都会有多层的函数调用关系,还有大量的 this 指向问题,而且会有很多辅助函数须要去了解。但还是保持啃下来了(当然还没看完,内容是真的多),上面是我在政采云(实习)工作空闲工夫浏览源码的一些感悟和总结,并带剖析了大三期间应用的 vue-element-admin 这个 vuer 无所不知的后盾框架的动静路由权限管制原理。顺便附带本文实际 demo 地址: 基于后盾框架开发的 学生管理系统。 vue-router 源码剖析 首先浏览源码之前最好是将 Vue 和 vue-router 的源码克隆下来,而后第一遍浏览倡议先跟着 官网文档 先走一遍根底用法,而后第二遍开始浏览源码,先理分明各层级目录的作用和抽出一些外围的文件进去,过一遍代码的同时写个小的 demo 边看边打断点调试,看不懂没关系,能够边看边参考一些总结的比拟好的文章,最初将比拟重要的原理过程依据本人的了解整理出来,而后画一画相干的常识脑图加深印象。 前置常识: flow 语法JS 在编译过程中可能看不出一些荫蔽的谬误,但在运行过程中会报各种各样的 bug。flow 的作用就是编译期间进行动态类型查看,尽早发现错误,抛出异样。 Vue、Vue-router 等大型项目往往须要这种工具去做动态类型查看以保障代码的可维护性和可靠性。本文所剖析的 vue-router 源码中就大量的采纳了 flow 去编写函数,所以学习 flow 的语法是有必要的。 首先装置 flow 环境,初始化环境 npm install flow-bin -gflow init在 index.js 中输出这一段报错的代码 /*@flow*/function add(x: string, y: number): number { return x + y}add(2, 11)在控制台输出 flow ,这个时候不出意外就会抛出异样提醒,这就是简略的 flow 应用办法。 ...

October 12, 2020 · 12 min · jiezi

关于vue-router:vuerouter

vue-routervue-router 是vue.js的官网路由管理器.它和vue.js外围深度集成.让构建单页面利用大海捞针 应用步骤(1) 装置 vue add router(2) 应用vue-router插件 import Router from 'vue-router'Vue.use(Router)(3) 创立Router实例 router.js export default new Router({...})(4) 在根组件上增加该实例 main.js import Router from './router'new Vue({ router,}).$mount("#app");(5) 增加路由视图 App.vue <router-view></router-view>(6) 路由导航 <router-link to="/">Home</router-link>this.$router.push('/')vue-router 源码简略实现单页面程序中,url发生变化的时候,不刷新页面,显示对应视图 需要剖析(1) 页面不刷新 hash模式 #/about(2) 页面视图发生变化 router-view相应式数据: url地址发生变化,找到对应的组件 动静从新执行 render工作实现一个插件(1) 实现一个vueRouter类解决路由选项监控url变动, hashchange响应这个变动(2) 实现install办法$router注册两个全局组件 router-link router-view实现一个插件:创立VueRouter类和install办法创立kvue-router.js let Vue; // 援用构造函数, vueRouter中要应用class VueRouter { constructor(options) { this.$options = options; }}// 插件实现install办法, 注册$routerVueRouter.install = function (_vue) { Vue = _Vue; // 工作2 挂载$router // 为什么要应用混入的形式. 次要起因是vue.use代码在前,Router实例创立在后. 而install逻辑又须要应用到该实例 Vue.mixin({ beforeCreated(){ // 只有根组件领有router选项 if (this.$options.router) { Vue.prototype.$router = this.$options.router; } } }) // 工作2 实现两个全局组件router-link router-view Vue.component('router-link',Link); Vue.component('router-view',View);}export default VueRouter实现router-link组件<router-link to="/">点击</router-link> ...

October 9, 2020 · 2 min · jiezi

关于vue-router:vrouter学习笔记

在模块化机制中应用v-router一. 用脚手架构建我的项目(v-cli版本在3.x及以上)vue create myvuejsproject//myvuejsproject为项目名称创立我的项目时须要输出的信息详解:初始化我的项目后的样子二. 在我的项目中应用v-router(用脚手架构建初始化我的项目完后其实我的项目中曾经配好的v-router,但为了纯熟v-router的应用咱们本人手动配置应用v-router)步骤:先在router文件夹中的index.js中导入路由对象,调用Vue.use(VueRouter),再创立路由实例,并传入路由映射而后再main.js中的Vue实例中挂载创立的路由实例导入路由对象,并调用Vue.use(VueRouter)Vue.use(VueRouter)创立路由实例,并且传入路由映射配置// 创立VueRouter对象const routers = [];const router = new VueRouter({ routers})在Vue实例中挂载创立的路由实例import router from './router/index';new Vue({ el: '#app', router,//挂载创立的路由实例 components: { App }, template: '<App/>'})图片来源于codewhy老师的学习视频

October 3, 2020 · 1 min · jiezi

关于vue-router:Part3模块一

一、简答题1、当咱们点击按钮的时候动静给 data 减少的成员是否是响应式数据,如果不是的话,如何把新增成员设置成响应式数据,它的外部原理是什么。let vm = new Vue({ el: '#el' data: { o: 'object', dog: {} }, method: { clickHandler () { // 该 name 属性是否是响应式的 this.dog.name = 'Trump' } }})1.不是响应式的,对于曾经创立的实例,Vue不容许动静增加根级别的响应式属性2.应用Vue.set(vm.dog, 'name', 'dog_name')或this.$set(this.dog, 'name', 'dog_name')3.this.$set在new Vue()时候就被注入到Vue的原型上,set办法外部仍是调用了defineReactive()办法进行响应式解决 2、请简述 Diff 算法的执行过程1.diff的过程就是调用名为patch(el, vnode)/patch(oldVnode, vnode)的函数,比拟新旧节点,一边比拟一边给实在DOM打补丁 2.patch里会调用sameVnode(oldVnode, vonde),依据返回后果: true: 则执行patchVnodefalse: 则用vnode替换oldVnode3.patchVnode(oldNode, vnode, insertedVnodeQueue) 找到对应的实在DOM,成为el判断vnode和oldVnode是否指向同一个对象,如果是,间接return如果它们都有文本节点并且不相等,那么将el的文本节点设置为vnode的文本文本节点如果oldVnode有子节点而vnode没有,则删除el的子节点如果oldVnode没有子节点而vnode有,则将vnode的子节点实在化之后加到el如果两者都有子节点,则执行updateChildren(parentElm, oldCh, newCh)函数比拟子节点(key很重要)二、编程题1、模仿 VueRouter 的 hash 模式的实现,实现思路和 History 模式相似,把 URL 中的 # 前面的内容作为路由的地址,能够通过 hashchange 事件监听路由地址的变动。2、在模仿 Vue.js 响应式源码的根底上实现 v-html 指令,以及 v-on 指令。v-html: ...

September 2, 2020 · 1 min · jiezi

关于vue-router:vuerouter

$route:路由规定,存储以后路由的一些数据$router:VueRouter的实例,这个路由对象提供了路由相干的办法(push,replace,beforeEach,faterEach等等)

August 17, 2020 · 1 min · jiezi

关于vue-router:vuerouter

$route:路由规定,存储以后路由的一些数据$router:VueRouter的实例,这个路由对象提供了路由相干的办法(push,replace,beforeEach,faterEach等等)

August 17, 2020 · 1 min · jiezi

关于vue-router:前端路由解析以及实现学习

1. 什么是前端路由路由的概念来源于服务端,在服务端中路由形容的是URL与处理函数之间的映射关系。 在web前端单页面利用中,路由形容的是 URL 和 UI 之间的映射关系,这种映射关系是单向的,即 URL 变动引起 UI 更新。 2. 如何实现前端路由要实现前端路由,须要解决两个外围: 如何扭转URL 却不引起页面刷新如何检测URL 变动了上面别离应用hash 和 history两种实现形式答复下面的两个外围问题。 2.1通过hash实现hash是URL中#以及前面的那局部,罕用作锚点在页面内进行导航,扭转URL中的hash局部不会引起页面刷新。通过 hashchange监听URL的扭转。通过浏览器后退后退,通过a标签扭转,通过window.location扭转都会触发hashchange事件。2.2通过history实现history 提供了pushState 和 popState办法,这两个办法扭转URL的path局部不会引起页面刷新。history 提供 popState事件,能够监听浏览器的后退后退事件。通过pushState/replaceState或者a标签扭转URL不会触发popState事件,好在咱们能够拦挡pushState、replaceState、a标签的点击事件来检测URL变动,所以监听URL变动能够实现,只是没有hashChange那么不便。3. 原生JS版前端路由实现下面说到基本上分为两种实现形式,别离通过 hash 和 history实现。 3.1 基于hash实现<body> <ul> <!-- 定义路由 --> <li><a href="#/home">home</a></li> <li><a href="#/about">about</a></li> <!-- 渲染路由对应的 UI --> <div id="routeView"></div> </ul></body>// 页面加载,被动触发一次window.addEventListener('DOMContentLoaded', onLoad)window.addEventListener('hashchange', onHashChange)var routerView = nullfunction onload() { routerView = document.querySelector('#routerView')}function onHashChange() { switch(location.hash) { case '#/home': routerView.innerHTML = 'HOME' return case '#/about': routerView.innterHTML = 'About' return default: return }}3.2基于history实现<body> <ul> <li><a href='/home'>home</a></li> <li><a href='/about'>about</a></li> <div id="routeView"></div> </ul></body>window.addEventListener('DOMContentLoaded', onLoad)window.addEvenetListener('popState', onPopState)var routerView = nullfunction onLoad() { routerView = document.querySelector('#routerView') onPopState() // 拦挡a标签 var linkList = document.querySelectorAll('a[href]') linkList.forEach(el => { el.addEventListener('click', function(e){ e.preventDefault() history.pushState(null, '', el.getAttribute('href') onPopState() }) })}function onPopState() { switch(location.pathname) { case '/home': routerView.innterHTML = 'HOME' return case '/about': routerView.innerHTML = 'about' return default: return }}4. React版前端路由实现4.1基于hash实现<BrowserRouter> <ul> <li> <Link to="/home">home</Link> </li> <li> <Link to="/about">about</Link> </li> </ul> <Route path="/home" render={() => <h2>Home</h2>} /> <Route path="/about" render={() => <h2>About</h2>} /> </BrowserRouter>BrowerRouter实现: ...

August 4, 2020 · 3 min · jiezi

vue中使用axios对同一个接口连续请求导致返回数据混乱的问题

业务上出现一个问题:如果连续对同一个接口发出请求,参数不同,有时候先请求的比后请求的返回数据慢,导致数据顺序混乱,或者数据被覆盖的问题,所以需要控制请求的顺序。 解决方法: 1.直接跟后台沟通,将所有参数放到数组里后台统一接收并返回所有数据再由前端进行数据的拆分使用。 2.对于出现返回的数据混乱问题。假设场景: 页面中需要对三个部门请求对应的部门人员,三个部门人员的数据为一个二维数组,连续发送请求,但由于返回数据的顺序不定,导致数组中的数据顺序不是按照部门的顺序。解决方法:使用promise.all + axios。 //获取部门人员的请求getDepartPerson (departData) { let that = this return new Promise(function(resolve,reject) { that.$axios({ method: 'get', url: ..., params: { ... } }).then(res => { const data = res.data.map(item => { return { value: item.userId, label: item.userName } }) resolve(data) }) }) }, //使用promise.all控制返回的数据顺序setPersonData() { const data = [{ departId: 1, departName: '部门1' }, { departId: 2, departName: '部门2' }, { departId: 3, departName: '部门3' }] let promise1 = this.getDepartPerson(data[0]) let promise2 = this.getDepartPerson(data[1]) let promise3 = this.getDepartPerson(data[2]) console.log(promise1,promise2,promise3) let that = this Promise.all([promise1,promise2,promise3]).then(value => { console.log(value) //value返回的数据是按顺序的 }) }, 这里要注意在promise中this不能指向vue的,所以在promise使用前赋值 ...

November 5, 2019 · 2 min · jiezi

Tab切换以及缓存页面处理的几种方式

前言相信tab切换对于大家来说都不算陌生,后台管理系统中多会用到。如果不知道的话,可以看一下浏览器上方的标签页切换,大概效果就是这样。 1.如何切换使用动态组件,相信大家都能看懂(部分代码省略) //通过点击就可以实现两个组件来回切换<button @click="changeView">切换view</button><component :is="currentView"></component>import pageA from "@/views/pageA";import pageB from "@/views/pageB";computed: { currentView(){ return this.viewList[this.index]; }}, methods: { changeView() { this.index=(++this.index)%2 }}注:这个多用于单页下的几个子模块使用,一般切换比较多使用下面的路由使用路由(这个就是配置路由的问题了,不作赘述)2.动态生成tab一般UI框架给我们的tab切换都像是上面的那种,需要自己写入几个tab页之类的配置。但是我们如果想要通过点击左边的目录来生成一个tab页并且可以随时关闭呢(如下图)? 只需要给路由一个点击事件,把你的路由地址保存到一个列表,渲染成另一个平铺的tab目录即可 假设你的布局是这样,左边的目录,上边的tab,有字的是页面 <menu> <menu-item v-for="(item,index) in menuList" :key="index" @click="addToTabList(item.path)"> <router-link :to="item.path">{{item.name}}</router-link> <menu-item></menu><template> <menu class="left"/>//menu代码部分如上 <div class="right"> <tab-list> <tab-item v-for="(item,index) in tabList" :key="index"> <router-link :to="item.path">{{item.name}}</router-link> <icon class="delete" @click="deleteTab"></icon> </tab-item> </tab-list> <page-view> <router-view></router-view>//这里是页面展示 </page-view> </div></template>以上代码并非实际代码,只提供一个大概的思路。至于addToTabList和deleteTab怎么做就是数组方法的简单push和splice操作了。为了效果好看,我们可能还需要一些tab的active样式,这里不作演示。3.缓存组件仅仅是做tab切换,远远是不够的,毕竟大家想要tab页就是要来回切换操作,我们需要保存他在不同tab里操作的进度,比如说填写的表单信息,或者已经查询好的数据列表等。那么我们要怎么缓存组件呢?只需要用到vue中的keep-alive组件 3.1 keep-alive<keep-alive>是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。<keep-alive> 与 <transition>相似,只是一个抽象组件,它不会在DOM树中渲染(真实或者虚拟都不会),也不在父组件链中存在,比如:你永远在 this.$parent 中找不到 keep-alive 。注:不能使用keep-alive来缓存固定组件,会无效//无效<keep-alive> <my-component></my-component></keep-alive>3.2 使用3.2.1 老版本vue 2.1之前的使用<keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><router-view v-if="!$route.meta.keepAlive"></router-view>需要在路由信息里面设置router的元信息meta ...

November 5, 2019 · 1 min · jiezi

elementUiVuei18nVuecli-实现前端国际化

首先安装vue-i18n算了,这个不多讲,看官网:http://kazupon.github.io/vue-i18n/zh/main.js 中引入并注册import i18n from './lang';// 设置国际化Vue.use(ElementUI, { i18n: (key, value) => i18n.t(key, value)});new Vue({ el: '#app', i18n, render: h => h(App)});src目录中建立语言包文件夹 lang/index.js//index.js文件import Vue from 'vue';import VueI18n from 'vue-i18n';import enLocale from 'element-ui/lib/locale/lang/en';import zhLocale from 'element-ui/lib/locale/lang/zh-CN';Vue.use(VueI18n);const messages = { en: { message: { hello: '{msg} world' }, ...enLocale }, zh: { message: { hello: '{msg} 世界' }, ...zhLocale }};const i18n = new VueI18n({ locale: 'zh', // set locale messages // set locale messages});export default i18n;效果初探,随便找个页面试试,此时页面会显示 hello 世界<p>{{ $t('message.hello', { msg: 'hello' }) }}</p><p v-html="$t('message.hello')" />Look,是不是很简单,已经有了雏形,开始精加工一般来讲,要翻译的文字比较多,所以最好为每门语言建立独立文件管理. 新建文件 lang/zh.js、 lang/en.js, 写入一些测试数据//zh.jsexport default { app: { LanguageChage: '语言切换', editPassword: '修改密码', signOut: '登出' }};//en.jsexport default { app: { LanguageChage: 'Language switching', editPassword: 'Change Password', signOut: 'Sign out' }};下面去封装一个组件,用来切换语言,并将语言状态保存到cookie和Vuex中。在封装组件之前,咱们先去封装一个方法,保存语言状态值, 更新之前的 lang/index.js 文件,主要是 getLanguage 方法import Vue from 'vue';import VueI18n from 'vue-i18n';import Cookies from 'js-cookie';import enLocaleElement from 'element-ui/lib/locale/lang/en';import zhLocaleElement from 'element-ui/lib/locale/lang/zh-CN';import zhLocale from './zh';import enLocale from './en';Vue.use(VueI18n);// 语言环境信息const messages = { en: { ...enLocale, ...enLocaleElement }, zh: { ...zhLocale, ...zhLocaleElement }};// 获取语言环境并保留状态export function getLanguage() { const cookieLanguage = Cookies.get('language'); if (cookieLanguage) return cookieLanguage; const browerLanguage = (navigator.language || navigator.browserLanguage).toLowerCase(); const locales = Object.keys(messages); for (const locale of locales) { if (browerLanguage.indexOf(locale) > -1) { return locale; } }}const i18n = new VueI18n({ locale: getLanguage(), messages, fallbackLocale: 'zh'});// 热更新if (module.hot) { module.hot.accept(['./en', './zh'], function() { i18n.setLocaleMessage('en', require('./en').default); i18n.setLocaleMessage('zh', require('./zh').default); });}export default i18n;vuex仓库中增加language字段, 一般大家应该都是用vuex的吧,这里我用vuex的module模式来写, 其实随意,都可以。// app.js 这是我的store文件import { getLanguage } from '@/lang/'; // 引入之前写好的方法// state里增加language字段const state = { language: getLanguage()};const mutations = { SETLANGE: (state, language) => { state.language = language; Cookies.set('language', language); }};const actions = { setLanguage({ commit }, language) { commit('SETLANGE', language); }};export default { namespaced: true, state, mutations, actions};OK, 准备工作已完成,下面封装切换语言的组件, 新建一个 launageSelect.vue 文件<template> <el-dropdown trigger="click" class="international" @command="handleSetLanguage"> <div> 语言切换 </div> <el-dropdown-menu slot="dropdown"> <el-dropdown-item :disabled="language==='zh'" command="zh"> 中文 </el-dropdown-item> <el-dropdown-item :disabled="language==='en'" command="en"> English </el-dropdown-item> </el-dropdown-menu> </el-dropdown></template><script>export default { computed: { language() { return this.$store.getters.language; //这里我用getters处理,代码不贴了,你么你随意 } }, methods: { handleSetLanguage(lang) { // 通过VueI18n的根实例设置当前的语言环境 不懂请看文档 http://kazupon.github.io/vue-i18n/zh/api/#%E9%9D%99%E6%80%81%E5%B1%9E%E6%80%A7 this.$i18n.locale = lang; this.$store.dispatch('app/setLanguage', lang); this.$message({ message: 'Switch Language Success', type: 'success' }); } }};</script>到此为止,基本的国际化框架就已经完成,可以直接去使用了。等等,还没完,接下来才是最重要的。一般vue项目的菜单都是在route中去写的,此时无法直接使用模板语法进行国际化,所有咱们还要封装一个方法,用来处理菜单标题/** *生成字符- 国际化使用 * @param {*} lanuageKey 语言对象键值 * @param {*} title 要转换的值 */export function generateText(lanuageKey, title) { const key = this.$te(`${lanuageKey}.${title}`); if (key) { return this.$t(`${lanuageKey}.${title}`); } return title;}找到渲染菜单的文件,引入刚刚的方法,进行菜单标题格式化 <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}" > <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="generateText('route', onlyOneChild.meta.title)" /> </el-menu-item> </app-link> //引入方法 import { generateText } from '@/utils/'; //methods里调用下 methods: { generateText }此时你的配置应该是这样的// zh.jsexport default { route: { home: '首页', }}//router.js (title中的home要和你的zh.js里的key值对应) { path: '/', component: Layout, redirect: '/home', children: [{ path: 'home', name: 'Home', component: () => import('@/views/home/index'), meta: { title: 'home', icon: 'home' } }] }此时你得菜单项便可以正常渲染了到此便正式结束,其实还蛮简单的,当然i18n有很多个语法,大家自行查阅文档,谢谢!

October 17, 2019 · 2 min · jiezi

vuerouter-history模式下刷新页面404问题

搜索了很多前辈的解决方案一直没有解决问题,配置类似如下` location / { root html; index index.html index.htm; try_files $uri $uri/ /index.html; }`总是会报500。最后研究才发现,前辈得解决案例是nginx下部署一个前端项目,而我这边的情况是一个nginx下有多个前端项目,稍微调整一下路径后,问题迎刃而解。具体方法如下(只需要修改nginx的nginx.conf文件,此修改方式不影响其他前端项目的路由默认,只处理本人得项目):`server { listen 8090; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #解决路由history模式下,刷新页面404问题 location /webName/ { root html; index index.html index.htm; try_files $uri $uri/ /webName/index.html; } ....}`

October 16, 2019 · 1 min · jiezi

Vue-开发必须知道的-36-个技巧近1W字

前言Vue 3.x 的Pre-Alpha 版本。后面应该还会有 Alpha、Beta 等版本,预计至少要等到 2020 年第一季度才有可能发布 3.0 正式版;所以应该趁还没出来加紧打好 Vue2.x 的基础;Vue基本用法很容易上手,但是有很多优化的写法你就不一定知道了,本文从列举了 36 个 vue 开发技巧;后续 Vue 3.x 出来后持续更新. 1.require.context()1.场景:如页面需要导入多个组件,原始写法: import titleCom from '@/components/home/titleCom'import bannerCom from '@/components/home/bannerCom'import cellCom from '@/components/home/cellCom'components:{titleCom,bannerCom,cellCom}2.这样就写了大量重复的代码,利用 require.context 可以写成 const path = require('path')const files = require.context('@/components/home', false, /\.vue$/)const modules = {}files.keys().forEach(key => { const name = path.basename(key, '.vue') modules[name] = files(key).default || files(key)})components:modules这样不管页面引入多少组件,都可以使用这个方法 3.API 方法 实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用require.context(directory,useSubdirectories,regExp)接收三个参数:directory:说明需要检索的目录useSubdirectories:是否检索子目录regExp: 匹配文件的正则表达式,一般是文件名2.watch2.1 常用用法1.场景:表格初始进来需要调查询接口 getList(),然后input 改变会重新查询 ...

October 9, 2019 · 9 min · jiezi

基于iviewadmin实现动态路由

iview-admin是一个基于vue和iview组件库实现的管理后台前端,本文基于iview-admin最新版本,实现基于权限的动态路由加载。本文代码可参见:https://github.com/MayBeWrong... 背景: 动态路由:vue的路由,可通过new Router传入路由数组定义实现,也可以通过router.addRoutes实现。通过router.addRoutes动态传入路由定义的方式,称之为动态路由。路由数据可以全部保存在后台数据库中,也可以将路由配置在前端,后端返回给前端路由权限信息,然后匹配过滤,进行加载。本文就这两种方式分别进行介绍,并且给出实现参考。 目标:基于iview-admin最新代码,实现两种不同的路由动态加载方式: 路由(导航菜单)数据全部存储在后台路由数据配置在前端,后台只存储权限信息注意:本文通过Mock模拟后端接口方式1:路由(导航菜单)数据全部存储在后台 定义路由数据结构体,在文件中:src/mock/data.jsexport const routersData = [{ path: '/pet',//访问路径 name: 'Pet',//路由的名字,这个与i18n有关,需要唯一 meta: { title: '宠物',//标题 hideInMenu: false,//是否在左侧导航菜单隐藏 icon: 'logo-freebsd-devil'//图标 }, component: 'components/main',//组件文件路径,不需要Import children: [{//嵌套路由 path: 'cat', name: 'Cat', meta: { title: '猫咪', hideInMenu: false, icon: 'ios-cloudy-night' }, component: 'view/pet/cat/Cat.vue' }, { path: 'dog', name: 'Dog', meta: { hideInMenu: false, title: '狗娃', icon: 'ios-color-filter' }, component: 'view/pet/dog/Dog.vue' }, { path: 'pig', name: 'Pig', meta: { hideInMenu: false, title: '猪啊', icon: 'ios-contact' }, component: 'view/pet/pig/Pig.vue', children: [ { path: 'female', name: 'Female', meta: { hideInMenu: false, title: '母猪', icon: 'ios-contact' }, component: 'view/pet/pig/Pig.vue', }, { path: 'male', name: 'Male', meta: { hideInMenu: false, title: '公猪', icon: 'ios-contact' }, component: 'view/pet/pig/Pig.vue', } ] }]}] 暴露ajax调用接口:src/mock/index.js,中增加:Mock.mock(/\/sys\/routers/, routersData)实现一个ajax调用:src/api/routers.js中增加: ...

October 1, 2019 · 2 min · jiezi

Vuejs应用性能优化二

在Vue.js应用性能优化一文章中,我们了解了代码拆分是什么,它如何与Webpack一起工作以及如何在Vue应用程序中使用延迟加载来使用它。现在我们将深入研究代码,并学习最有用的Vue.js应用程序代码分割模式。 通过使用以下技术,我们能够将初始bundle的大小减少70%并使其在眨眼间加载。 应用规模增长带来的问题Vue-router是一个库,允许自然地将我们的Web应用程序拆分为单独的页面。每个页面都是与某个特定URL路径关联的路由。 知道这一点,我们有一个简单的应用程序,具有以下结构: 图片说明:所有js代码都被打包到一个文件 — app.js 您可能已经注意到,根据我们访问的路由,我们可能不需要Home.vue或About.vue(依赖lodash)但它们都在相同的app.js包中,无论路由用户是什么,都会被下载访问。浪费下载和解析时间! 如果我们只是多下载了一个路由,那这并不是什么大问题。但你可以想象,随着这个应用程序越来越大,任何新的添加都意味着在首次访问时下载更大的bundle。 当1秒的时间足以让用户心里犯嘀咕,并且(可能)离开我们的网站时,这是不可接受的! 不同延迟,用户的心理反应:0 - 100ms,感觉很快100 - 300ms 可以接受的延迟等待300 - 1000ms 盯着网页,明显感觉到延迟1000+ms 心里开始嘀咕,要不要离开10,000+ms 先去别的地方逛逛吧,稍后见使用vue-router进行基于路由的代码分割为了避免弄巧成拙,我们只需要使用我们在前一篇文章中学习的动态导入语法,为每个路由创建单独的bundle。 像Vue.js中的其他所有东西一样 - 它非常简单。我们只需要在那里动态导入组件,而不是将组件直接导入到路径对象中。仅当解析给定路线时才会下载路线组件。 所以不要像这样静态导入路径组件: 我们需要动态导入它,这将创建一个包含此路由的新bundle作为入口点: 知道了这一点,让我们看看我们的捆绑和路由如何与动态导入一样: 图片说明:home.js,about.js 都被拆分成单独的bundle 通过此设置,webpack将创建三个包: app.js - 我们的主要包含应用程序入口点(main.js)和每个路由所需的库/组件home.js - home页面bundle,只有在输入/路径时才会下载about.js - about页面bundle(依赖 lodash),只有在输入路径为/about时才会下载bundle名称不是webpack生成的真实名称,以便于理解。 Webapck实际上正在生成类似0.js 1.js等,具体取决于您的webpack配置。 这种技术几乎适用于所有应用,并且可以提供非常好的效果。 在许多情况下,基于路由的代码拆分将解决您的所有性能问题,并且可以在几分钟内应用于几乎任何应用程序! Vue生态系统中的代码拆分您可能正在使用Nuxt或vue-cli来创建您的应用程序。如果是这样,重要的是要知道它们都有关于代码拆分的一些自定义行为: 在vue-cli 3中,默认情况下将预取所有延迟加载的块。我们将在稍后学习如何使用预取 (prefetching)。 在Nuxt中,如果我们使用Nuxt路由系统,所有页面路由都是开箱即用的 现在让我们来看看非常流行且常用的反模式,它会减弱基于路由的代码拆分效果。 Vendor bundle 反模式vendor包(第三方类库) 通常用于包含node_modules中所有模块的单独js文件的上下文中。 虽然可以将所有内容放在这里,将所有依赖项保存在一个地方并缓存它们,感觉上可能很好,但这种方法带来了将所有路由打包在一起时遇到的相同问题: 图片说明:黄色模块,都是vendor 你看到了问题吗?即使我们只需要在一个路由中使用lodash(它是其中一个依赖项),但是现在它被捆绑在vendor.js中以及所有其他依赖项中,因此它将始终下载。 将所有依赖项打包在一个文件中听起来很好,但会使您的应用加载时间更长。我们可以做得更好! 如果按照基于路由的代码分割方式,会确保所有依赖的代码被下载。但同时也会重复下载一些相同的依赖。比如两个路由页面中都依赖lodash的情况。 让我们假设Home.vue也需要lodash。 在这种情况下,从/about(About.vue)导航到/(Home.vue)将最终导致两次下载lodash。 它仍然比下载大量的冗余代码更好,但是如果我们已经有了这种依赖,那么重用它就没有意义了,对吧? ...

September 7, 2019 · 1 min · jiezi

8-道高频出现的-Vue-面试题及答案

前言本文讲解 8 道高频出现的 Vue 面试题及答案。 复习前端面试的知识,是为了巩固前端的基础知识,最重要的还是平时的积累!注意:文章的题与题之间用下划线分隔开,答案仅供参考。 前端硬核面试专题的完整版在此:前端硬核面试专题,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 数据结构与算法 + Git 。 Vue对 MVC、MVP 、MVVM 的理解 MVC 模式的意思是,软件可以分成三个部分。 视图(View):用户界面。控制器(Controller):业务逻辑。模型(Model):数据保存。各部分之间的通信方式如下。 View 传送指令到 ControllerController 完成业务逻辑后,要求 Model 改变状态Model 将新的数据发送到 View,用户得到反馈所有通信都是单向的(逆时针)。MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。 各部分之间的通信,都是双向的(顺时针)。View 与 Model 不发生联系,都通过 Presenter 传递。View 非常薄,不部署任何业务逻辑,称为 "被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。 ...

August 18, 2019 · 3 min · jiezi

使用-vuerouter-在-Vue-页面之间传递数据

前言几周前,我写了关于 Vue 路由的使用和在 Vue 页面导航的文章。这是在应用程序中探索的一个基本例子。 通常,在将导航构建到应用程序中时,您会发现需要将数据从一个页面传递到另一个页面。(不通顺)例如,您遵循 master-detail 模式,其中您有一个数据列表,通过更深入地挖掘可以获得关于列表中特定项的更多信息。 我们将学习如何使用路由和 URL参数以及查询参数在 Vue 页面之间传递数据。 如果你还没有读过我之前的教程或者不熟悉 vue-router 库,我建议你温习一下。 利用 URL 参数在不同页面中传递数据假设您有一个 web 应用程序,它显示从某个数据库获得的用户列表。这个列表可能只包含姓名信息,但是数据库中的数据可能包含更多的信息,例如地址、电话等。 在典型的场景中,我们使用主键或其他标识符维护这个用户列表,并用于在请求详细信息时查询数据库时。这样的值可非常合适作为 URL 参数在不同页面传递。 为此,目标页面需要获取到 URL 参数。在前面的教程基础上,我们可以将项目 src/router/index.js 中的文件更改为如下所示 import Vue from 'vue'import Router from 'vue-router'import Page1 from '@/components/page1'import Page2 from '@/components/page2'Vue.use(Router)export default new Router({ routes: [ { path: "/", redirect: { name: "Page1" } }, { path: '/page1', name: 'Page1', component: Page1 }, { path: '/page2/:id', name: 'Page2', component: Page2 } ]})注意,Page2 的路由中路径中包含一个 :id。这个冒号表示我们正在处理一个变量 ...

July 15, 2019 · 2 min · jiezi

Vue-项目功能实现router-传递参数并解决刷新页面参数丢失问题

Vue Router 传参方式:1. this.$router.push({ name: '模块名称', params: { // 各参数 } })router.js:export default new Router({ routes: [ { path: '/paramsPassingByRouter', component: ParamsPassingByRouter, children: [ { path: 'paramsMode', name: 'paramsMode', component: ParamsMode } ] } ]})ParamsPassingByRouter.vue:<!-- html --><button @click="paramsMode(testData)">params传参</button><!-- js --><script>export default { data () { return { testData: { id: '20180101', name: '张三', aka: 'z3', age: '18' } } }, methods: { paramsMode (data) { this.$router.push({ name: 'paramsMode', params: this.testData }) } }}</script>ParamsMode.vue:<!-- html --><div class="params-mode">{{ testData }}</div><!-- js --><script>export default { data () { return { testData: {} } }, created () { this.testData = this.$route.params }}</script>效果:url:http://localhost:8081/#/paramsPassingByRouter/paramsMode 页面显示:{"id":"20180101","name":"张三","aka":"z3","age":"18"} ...

July 8, 2019 · 2 min · jiezi

Vuejsvuerouter实现二级导航切换路由及高亮显示

这里以网易云音乐作为示例,效果图: 我们先一层一层写导航先设计第一层1.设计导航页面样式第一个导航页面为Discover Discover.vue: <!-- --><template> <div> 发现 </div></template><script>export default { name: "discover", data() { return { }; }};</script><style scoped></style>第二个导航页面为Mymusic其余代码一样,注意要把name改为相应路由 name: "mymusic"2.配置路由 index.js: import DisCover from '@/components/DisCover'import MyMusic from '@/components/MyMusic'…… routes: [ { path: '/discover', name: 'discover', component: DisCover }, { path: '/mymusic', name: 'mymusic', component: MyMusic } ]3.使用router-link制作导航我们创建一个新组件Guide.vue,把他插入到app.vue中设计好路由的数据源: guides:[ { id:0, name:'发现音乐', link:'/discover' },{ id:1, name:'我的音乐', link:'/mymusic' }, { id:2, name:'朋友', link:'friend' }, { id:3, name:'商城', link:'mall' }, { id:4, name:'音乐人', link:'musician' }, { id:5, name:'下载客户端', link:'download' } ]Guide.vue: ...

July 8, 2019 · 1 min · jiezi

关于Vue2一些值得推荐的文章-七月初

七月初 vue2 推荐集合查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 七月: 不在山,不在岸,采我之时七月半。!七月-银潢七月秋浪高,黄昏欲渡未成桥。(07.01~至今): 王子乔 [唐] 宋之问 王子乔,爱神仙,七月七日上宾天。白虎摇瑟凤吹笙, 乘骑云气吸日精。吸日精,长不归,遗庙今在而人非。 空望山头草,草露湿人衣。 学习vue源码我们一起写一个Vue的Loading插件吧大白话理解和初步使用vue-routervue使用总结Vue nextTick 变迁史vuex中的四大金刚提前使用Vue 3.0新特性,vue-function-api尝鲜使用vue中的混入mixin优化表单验证插件一张图教你快速玩转vue-cli3学习vue源码—mvvmvue-router 源代码全流程分析「长文」探索Angular,React,Vue的趋势比较深入理解vue响应式原理你不知道的Vue.nextTick源码系列Vue手把手带你撸项目系列之动态面包屑为vue3学点typescript(1), 体验typescript使用 Typescript 加强 Vuex 使用体验前端规范之vue 项目规范大白话理解和初步使用vuexVue 面试知识点总结Vue 项目功能实现:刷新当前页面精读《Vue3.0 Function API》Vue入门学习之技术分享-2(深入理解Vue组件)为vue3学点typescript, 基础类型和入门高级类型vuex了解一下?Vue 面试知识点总结(二)【持续更新中~】【一文学会】vue.js入门到放弃从源码解读Vue生命周期,让面试官对你刮目相看Vue入门学习之技术分享-3(Vue中的动画特效)Vue中jsx不完全应用指南vue打包后vendor.js文件过大解决方案带你了解vue计算属性的实现原理以及vuex的实现原理记录一次vue练习的填坑记录Vue2 weekly 上Why You Should Start Front-End by Learning Vue.js Integrating content management into your Vue.js projects with PrismicVue.js Amsterdam RecordingsiView UI framework 2.4Promoted - Get all products by Creative Tim including Vue premium dashboards 90% offBest resources to learn Vue.js in 2018The Vue.js Conference in Amsterdam will have everything you hope forLaravel Nova Administration Panel with Vue.jsVuePress: What is it and Why it is a great tool to useVue.js Frameworks & Libraries to use in your next projectVueCamp: Vue.js Barcamp BerlinAmendment proposal to Function-based Component API · Issue #63 · vuejs/rfcs Why every Vue developer should be excited by Quasar 1.0 – Razvan StoenescuVue's Darkest Day – Daniel ElkingtonVue2 weekly 中What does the Vue function API feel like - Abdelrahman Awad3 Key Insights from Vue’s new functional API RFC – Kevin BallVue without View - An Introduction to Renderless Components – Jason Yu How to use cookies in VuePress - Dan VegaIn Vue, When Do I Actually Need the :key Attribute and Why? — Marina MostiWhat is VueFront? - VueFrontVue.js functional components: what, why, and when? – Austin GMigrating from Vuetify to Quasar - Stanislav Valasek10 Things You Should Know Before Writing Your Next Vuejs Component - Edithson Abelard GitHub - jamesdruhan/vue-jd-tableHow To Upgrade Your VuePress Site To v1.0 - Florimond MancaUse Fragments to Avoid Obsolete GraphQL Fields in Vue.js Applications – Markus OberlehnerReading Image Sizes and Dimensions with Vue.js – Raymond CamdenFrom JSX to Vue: my favorite templating tips – briwa A beginner-friendly guide to unit testing the Vue.js application – Vladislav BulyukhinVue2 weekly 下tiptap – a renderless rich-text editor for Vue.js VueFrontVuePress 1.x Released! – ULIVZNuxtJS: From Terminal to Browser - Sébastien ChopinTriggering events from Vue Router views - Dan VegaBuild An Intersection Observer Directive In Vue - Alex ReganBuild Decoupled Vue.js Applications with Hooks - Markus OberlehnerHow to Build a Group Chat App with Vue.js - Oscar CastroGitHub - kai-oswald/vue-svg-transitionGitHub - wokes/Laravel-Vue-SPA-template更多推荐查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 ...

July 6, 2019 · 2 min · jiezi

关于前端Vue框架的知识点

最近有时间,整理一下Vue的知识点,很多都是面试常见的 1、Vue的生命周期如果你能理解了这张图,也就对Vue的生命周期有了一个大致的了解。 vue生命周期总共分为8个阶段 创建前/后,载入前/后,更新前/后,销毁前/后。 创建/前后:在beforeCreated阶段,vue实例的挂载元素el还没有。在beforeCreated阶段可以添加loading事件,在created阶段发起后端请求,拿回数据载入前/后:在beforeMount阶段,vue实例的$el和data都初始化,但是挂载之前为虚拟的dom节点,data.message还未替换,页面无重新渲染。在mounted阶段,vue实例挂载完成,data.message成功渲染。更新前/后:当data变化时,会触发beforeUpdate和updated方法。销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。第一次页面加载会触发哪几个钩子?答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。DOM 渲染在 哪个周期中就已经完成?答:DOM 渲染在 mounted 中就已经完成了。2、对MVVM开发模式的理解?MVVM分为Model、View、ViewModel三者。 Model 代表数据模型,数据和业务逻辑都在Model层中定义;View 代表UI视图,负责数据的展示;ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。 3、VUE数据双向绑定原理答:vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息 给订阅者,触发相应的监听回调。 具体步骤: 第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化 第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。 第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新;视图交互变化(input)->数据model变更的双向绑定效果。详细请看:Vue 双向数据绑定原理详细分析 4、v-if 和 v-show 有什么区别?v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。5、父子组件之间的传值和通信?父组件向子组件传值:1)子组件在props中创建一个属性,用来接收父组件传过来的值;2)在父组件中注册子组件;3)在子组件标签中添加子组件props中创建的属性;4)把需要传给子组件的值赋给该属性子组件向父组件传值:1)子组件中需要以某种方式(如点击事件)的方法来触发一个自定义的事件;2)将需要传的值作为$emit的第二个参数,该值将作为实参传给响应事件的方法;3)在父组件中注册子组件并在子组件标签上绑定自定义事件的监听。6、Vue的路由实现:hash模式 和 history模式hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”7、vue路由的钩子函数首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。beforeEach主要有3个参数to,from,next:to:route即将进入的目标路由对象,from:route当前导航正要离开的路由next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。8、vue等单页面应用及其优缺点优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。9、vue中 key 值的作用? ...

July 3, 2019 · 1 min · jiezi

Vue-项目功能实现刷新当前页面

前言前些日子项目中突然接到了一个需求,要求点击当前路由刷新页面,进过实验有如下几种方案可实现需求,并简述不同。 1. this.$router.go(0)此方式是利用了 history 中前进和后退的功能,传入 0 刷新当前页面。 缺点:页面整个刷新,会白屏。 2. location.reload()直接使用刷新当前页面的方法。 缺点:同 this.$router.go(0) 一样,会白屏。 3. 给 router-view 标签添加 v-if通过 $nextTick(),协助实现。先把 <router-view /> 隐藏,隐藏后再让它显示,达到刷新当前页面的功能。是目前最合适的实现方式。 <!-- html --><router-link :to="url" @click.native="refreshView">页面</router-link><router-view v-if="showView"/><!-- js --><script>export default { data () { return { showView: true // 用于点击当前页的router时,刷新当前页 } }, methods: { refreshView (url) { this.showView = false // 通过v-if移除router-view节点 this.$nextTick(() => { this.showView = true // DOM更新后再通过v-if添加router-view节点 }) } }}</script>

July 1, 2019 · 1 min · jiezi

vuecli的npm包

为了方便在项目的中使用的vue框架,自己搭建了一套vue-cli,欢迎大家使用,并提出问题,谢谢 1.安装npm包cnpm(npm) install create-frame-vue -g 2.使用create-frame-vue指令就可以创建项目 使用指令后你可以看到这么的 指令完成之后创建的项目 npm install && npm run serve 即可启动项目。 欢迎大家使用,希望大家提供好的建议或意见,谢谢大家

June 27, 2019 · 1 min · jiezi

Vue项目总结项目nginx部署

项目开发完成,接下来是上线,关于vue项目的部署,我司前端是部署在nginx服务器上,关于nginx的相关文档,请自行查阅;本文只记录部署时碰到的一些问题。打包vue项目打包后,是生成一系列的静态文件,包括项目的请求IP都打入包内,如果后台服务改动,这时你的前端文件,又要重新编译打包,这里采用的是后台管理项目总结提到的前端自行请求一个配置文件,动态修改你的相关配置。 静态文件// config.json{ "api": "test.com"} 请求文件在项目store中请求你的配置文件,写入state中,在调用的时候可以全局访问到你的配置// api.jsGetConfigApi() { return new Promise((resolve, reject) => { axios .get(`/config.json?v=${new Date().getTime()}`) .then(result => { const configApi = { API: result.data['api'], // 统一接口 }; resolve(configApi); }) .catch(error => { reject(error); }); });}nginx部署因为vue-router有hash和history不同的两种模式,使用不同的模式,nginx的配置不同,hash模式下,不需要改动,只需要部署你的前端文件就可以了,所以这里只讨论history模式下.conf文件的修改访问修改nginx配置文件nginx.confserver { listen 80; server_name test.com; location / { root /front; // 前端文件路径 index index.html; // hash模式只配置访问html就可以了 try_files $uri $uri/ /index.html; // history模式下 }}修改完成,重启服务访问test.com部署到子级目录当我们需要把项目部署到子级目录下时,则需要修改项目的BASE_URL,生成一个子级目录下的绝对访问路径。修改对应的.conf配置文件server { listen 80; server_name test.com; location /demo { // 子级目录 alias /front/demo; index index.html; try_files $uri $uri/ /demo/index.html; }}修改完成,重启服务访问test.com/demo缓存处理前端项目的静态文件常常会被浏览器缓存,而项目编译后,js,css,图片等实际上是已经有hash值来去除了缓存,但是项目更新后,仍然会出现缓存问题,这是由于我们的项目整个入口都是在index.html文件上,浏览器实际是缓存了我们的html页面,所以我们要在nginx中告诉浏览器,html文件不被缓存。 location /demo { add_header Cache-Control 'private, no-store, max-age=0'; ... }总结这里只讨论了nginx相关的部署,实际上vue-router文档上是有相关的配置例子的。其他总结文章: webpack常规打包优化方案 组件通信处理方案 后台管理项目总结

June 21, 2019 · 1 min · jiezi

玩转Vue

1小时VueVue Tutorial in 2018 - Learn Vue.js by Example的笔记,深入浅出,通俗易懂。效果如下,在线演示地址:http://www.caishuxiang.cn/demo/190619vueproj/#/ 安装Vue 安装vue有三种方式,本次使用Vue CLI Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了 batteries-included 的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本 步骤: 安装vue cli > npm install -g @vue/cli开始一个新的Vue项目 > vue create vue-proj进入项目,开启服务,访问localhost:8080 yarn serve Vue组件 组件是组成Vue应用的基本单元,可以看下vue-pro的工程目录 这里的App.vue、Skill.vue就是组件,每个vue文件都是组件。 组件的结构template 中放置的是htmlscript中是页面的逻辑style中即样式信息<template> ...</template><script> ...</script><style> ...</style>引入其他的组件 如下所示: <template> <!-- Other HTML removed for brevity --> <HelloWorld msg="Welcome to Your Vue.js App"/> <!-- Other HTML removed for brevity --></template><script>import HelloWorld from './components/HelloWorld.vue'export default { name: 'app', components: { HelloWorld }}</script>Vue class和style绑定scoped style元素上使用了scoped,那么该style下面的写的css或者引用的外部css,只对所在component中的元素起作用.如下: ...

June 19, 2019 · 3 min · jiezi

手摸手带你用vue实现后台管理权限系统及顶栏三级菜单显示

手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示 效果演示地址项目demo展示 重要功能总结权限功能的实现权限路由思路:根据用户登录的roles信息与路由中配置的roles信息进行比较过滤,生成可以访问的路由表,并通过router.addRoutes(store.getters.addRouters)动态添加可访问权限路由表,从而实现左侧和顶栏菜单的展示。 实现步骤: 1.在router/index.js中,给相应的菜单设置默认的roles信息; 如下:给"权限设置"菜单设置的权限为:meta:{roles: ['admin', 'editor']},及不同的角色都可以看到; 给其子菜单"页面权限",设置权限为:meta:{roles: ['admin']},及表示只有"admin"可以看到该菜单; 给其子菜单"按钮权限"设置权限为:meta:{roles: ['editor']},及表示只有"editor"可以看到该菜单。 2.通过router.beforeEach()和router.afterEach()进行路由过滤和权限拦截; 代码如下: // permission judge functionfunction hasPermission(roles, permissionRoles) { if (roles.indexOf('admin') >= 0) return true // admin permission passed directly if (!permissionRoles) return true return roles.some(role => permissionRoles.indexOf(role) >= 0)}const whiteList = ['/login'] // 不重定向白名单router.beforeEach((to, from, next) => { NProgress.start() // 设置浏览器头部标题 const browserHeaderTitle = to.meta.title store.commit('SET_BROWSERHEADERTITLE', { browserHeaderTitle: browserHeaderTitle }) // 点击登录时,拿到了token并存入了vuex; if (getToken()) { /* has token*/ if (store.getters.isLock && to.path !== '/lock') { next({ path: '/lock' }) NProgress.done() } else if (to.path === '/login') { next({ path: '/' }) // 会匹配到path:'',后面的path:'*'还没有生成; NProgress.done() } else { if (store.getters.roles.length === 0) { store.dispatch('GetInfo').then(res => { // 拉取用户信息 const roles = res.roles store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 router.addRoutes(store.getters.addRouters) // 动态添加可访问权限路由表 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch((err) => { store.dispatch('FedLogOut').then(() => { Message.error(err || 'Verification failed, please login again') next({ path: '/' }) }) }) } else { // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ if (hasPermission(store.getters.roles, to.meta.roles)) { next()// } else { next({ path: '/401', replace: true, query: { noGoBack: true }}) } } } } else { if (whiteList.indexOf(to.path) !== -1) { next() } else { // 点击退出时,会定位到这里 next('/login') NProgress.done() } }})router.afterEach(() => { NProgress.done() // 结束Progress setTimeout(() => { const browserHeaderTitle = store.getters.browserHeaderTitle setTitle(browserHeaderTitle) }, 0)})用户点击登录之后的业务逻辑分析: ...

June 19, 2019 · 3 min · jiezi

vue-路由进阶

路由可向路由匹配的组件传递参数,不同情况向组件传递不同的参数,从而实现组件的复用。 路由向组件传递参数和路由匹配的组件可以在组件中使用 $route 获取路由上的参数: 传参方式:、params和query :在路径传递参数{ path: "/argu/:id/book", name: "argu", component: () => import("@/views/ArguPage")}路径中的一部分是参数,必须传递该参数: <!--路径跳转--><router-link to="/argu/123/book">path跳转</router-link><!--路由名跳转--><router-link :to="{name:'argu',params:{id:'test'}}" tag="button">name+params跳转</router-link><!--获取参数--><h1>{{$route.params.id}}</h1><!--params的名字路径中的的参数名一致-->此时 path+ parmas传递参数,params会被忽略。 params+name传递参数路由: { path: "/argu", name: "argu", component: () => import("@/views/ArguPage")}跳转方式是 name+params+(query),通过path跳转,params 会被忽略。 <router-link :to="{name:'argu', params:{name:'hangge'}}"> 跳转到 hello</router-link>// path + params ,params 会被忽略,因为路径中没有定义参数<router-link :to="{path:'/argu', params:{name:'hangge'}}"> 跳转到 hello</router-link>query 参数query 参数参数,表现为查询字符串,和localtion.serach一样的。 不需要先在路径中先定义,可通过path、path+query 或者 name + query 传递参数。 <router-link to="/argu?queryName=value">跳转到 hello</router-link><router-link :to="{path:'/argu',query:{queryName:value}}">跳转到 argu</router-link><router-link :to="{name:'argu',query:{queryName:value}}">跳转到 argu</router-link><h1>{{ $route.query.queryName }}</h1>函数传递 query // 主要是 $router 不是 $routego() { this.$router.push({ name: 'argu', query: { queryName: "你好" } }) }}但是这样使得 $route 和组件耦合在一起,不方便组件的复用,如果能将路由中的参数传递到 组件的props 就好了,恰恰是可以这样设置的。 ...

June 16, 2019 · 3 min · jiezi

vue-路由基础

vue 路由基础vue 使用 vue-router 插件处理路由,路由是开发单页应用必须掌握的知识。 什么是 vue-router?(1)vue-router 是 Vue 官方提供前端路由插件,借助它我们实现可以基于路由和组件的单页面应用。 (2)它与传统的页面区别在于: 传统的页面应用采用的是后端路由,即通过超链接来实现页面切换和跳转的。而在 vue-router 单页面应用中,则是通过路径之间的切换(实际上就是组件的切换)。router-link 和 router-view 组件router-link 是一个a(链接)标签的封装,router-view 是路由视图,渲染 router-link 匹配到的组件,可配合使用<transition> 和 <keep-alive> 使用。 更多详细信息 路由配置动态路由$route 是当前路由,可用 watch在组件中监它的变化,有一个 params 属性,值一个包含动态路由的对象。 watch: { '$route'(to) { console.log(to); //将路由的 params 属性赋值给组件的 data 属性 to.params && to.params.view && (this.effect = to.params.view) },}route 和 router 的区别 路由对象为: { path:'/argu/:name', // 使用 import 动态引入路径对应的组件,起到懒加载的作用 component:()=>import('@/views/ArguPage')}可在该路由的组件中这样获取name的值: $route.params.name //给同一个组件设置传递不同的params,实现组件的复用嵌套路由在路由对象中添加一个 children 属性,值是一个数组,可包含多个子路由。子路由 path 前面不能有 / 。 父级路由对应的组件必须有路由出口,即 router-view。 ...

June 16, 2019 · 2 min · jiezi

Vue实战路由轻松设置vuerouter3

上篇我们说了vue项目的目录设计,本篇我们来学习一下vue路由。 路由的作用: 在web端路由(route)就是URL到函数的映射,vue的router就像一个容器,分配,处理每一个route到URL中。 文档地址: vue路由官方文档https://router.vuejs.org/zh/guide/#html安装: 通过node.js安装;npm install vue-router 但是一般情况下在node中安装vue项目的时候根据提示选择安装vue-router;如何使用路由: 举个项目例子: 1.添加路由链接,打开Nav.vue <router-link to="/goods" class="nav-item">点菜</router-link> <router-link to="/rates" class="nav-item">评价</router-link> <router-link to="/seller" class="nav-item">商家</router-link>to是路由指向的地址。 2.决定将组建渲染在哪,打开App.vue,添加: <router-view/>3.配置路由,打开router文件夹下index.js import Vue from 'vue'import Router from 'vue-router'//1.引入各个组件import Goods from '@/components/Goods/Goods'import Rates from '@/components/Rates/Rates'import Seller from '@/components/Seller/Seller'Vue.use(Router)//2.配置路由 路径->组件export default new Router({ routes: [ { path: '/', redirect:'/goods' }, { path: '/goods', component: Goods }, { path: '/rates', component: Rates }, { path: '/seller', component: Seller } ], linkActiveClass:'active'})redirect路由重定向: 我们在router数组设定redirect为“/goods”。那么只要路径是/。页面会跳转到"/goods"页面。 ...

June 16, 2019 · 1 min · jiezi

VueRouter

当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active 。默认 hash 模式:使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。history 模式:充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面,此模式如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面。动态路由匹配{path: '/user/:id', component: User}user/abc user/123 都将映射到相同的路由this.$route.params.id(template 中 $route.params.id){path: '/user-*'} 匹配以 `/user-` 开头的任意路径嵌套路由User 需要 <router-view></router-view>children 路径不能以 "/" 开头{ path: '/user', component: User, children: [ { // 当 /user/profile 匹配成功, // UserProfile 会被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile }, { // 当 /user/posts 匹配成功 // UserPosts 会被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ]}编程式的导航<router-link :to="..."> 等同于调用 router.push(...)this.$router.push(location, onComplete?, onAbort?)this.$router.push('home')this.$router.push({ path: 'register', query: { plan: 'private' }})this.$router.push({ name: 'user', params: { userId: '123' }})location 如果提供了 path,params 会被忽略,解决办法:{path: `register/${id}`}onComplete 和 onAbort 两个回调用于导航成功完成(在所有的异步钩子被解析之后)或终止(导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)的时候进行相应的调用<router-link :to="..." replace> 等同于调用 router.replace(...),和 router.push() 唯一的不同就是,它不会向 history 添加新记录,而是替换掉当前的 history 记录router.go(n) 这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。命名视图<router-view></router-view><router-view name="a"></router-view><router-view name="b"></router-view>{ path: '/', components: { default: Foo, a: Bar, b: Baz }}重定向和别名{ path: '/a', redirect: '/b' }{ path: '/a', redirect: { name: 'foo' }}{ path: '/a', redirect: to => { // 方法接收 目标路由 作为参数 // return 重定向的 字符串路径/路径对象}}{ path: '/a', component: A, alias: '/b' }导航守卫全局前置守卫router.beforeEach((to, from, next) => { // to: Route: 即将要进入的目标 路由对象 // from: Route: 当前导航正要离开的路由 // next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。 // some code next()})全局后置钩子router.afterEach((to, from) => { // ...})路由独享的守卫{ path: '/foo', component: Foo, beforeEnter: (to, from, next) => {/* */}}组件内的守卫const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 【不能】获取组件实例 `this`,不过,你可以通过传一个回调给 next来访问组件实例 next(vm => { // 通过 `vm` 访问组件实例 }) // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` }}路由元信息{ path: 'bar', component: Bar, meta: { requiresAuth: true, title: 'BAR' }}遍历 $route.matched 来检查路由记录中的 meta 字段。滚动行为scrollBehavior 只在支持 history.pushState 的浏览器中可用。new VueRouter({ routes: [...], scrollBehavior (to, from, savedPosition) { // savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。 // return 期望滚动到哪个的位置 }})路由懒加载const Foo = () => import('./Foo.vue')https://router.vuejs.org/zh/ ...

June 15, 2019 · 2 min · jiezi

20190613Vue-Router-基础一

什么是 Vue Router?Vue Router是Vue.js的官方路由管理器,可以控制Vue单页应用的路由。 如何快速上手?vue-cli脚手架自带Vue-Router依赖新版的vue-cli脚手架中,默认default模式没有router依赖,请选择Manually select features后添加Router依赖后,选择History模式。vue-cli官网 在HTML进行路由设置<el-dropdown-menu slot="dropdown"> <router-link to="/profile/index"> <el-dropdown-item>新建分类</el-dropdown-item> </router-link></el-dropdown-menu>我们可以利用<router-link to="path">标签来控制单页应用的跳转路径 使用单文件组件内进行路由设置<el-dropdown-menu slot="dropdown"> <el-menu-item @click="onCreate">新建分类</el-menu-item></el-dropdown-menu>export default { methods: { onCreate() { this.$router.push({ path: '/categories/create' }) } }}我可以使用this.$router方法进行操作路由 执行了push或<router-link>跳转操作还远远不够,我们还在router.js上进行路由(目录)添加你需要对应标准的地址以及地址包含component组件,不然编译器不知道你要跳转的实质内容是什么~router.js配置// 按需加载你需要的依赖import Vue from 'vue'import Router from 'vue-router'import Main from './views/main.vue'import CategoryEdit from './components/categoryEdit.vue'Vue.use(Router)export default new Router({ mode: 'history', // history模式 base: process.env.BASE_URL, // 环境内部基础地址 routes: [ { path: '/', // 当地址在根目录的时候,跳转到Main的组件,这就是首页 name: 'main', component: Main, children: [ { path: '/categories/create',// 它是Main的子路由,默认在首页 调用CategoryEdit组件 name: 'categoryCreate', component: CategoryEdit }, { path: '/categories/create', // 它是Main的子路由, 当地址转到/categories/create上,则调用CategoryEdit组件 name: 'categoryCreate', component: CategoryEdit } ] } ]})嵌套路由刚刚在上面我们提高了「子路由」,然后我们开始了解下嵌套路由 ...

June 14, 2019 · 2 min · jiezi

Vue-Router-简单例子起步

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:(比较晦涩难懂,随着慢慢熟悉vue,再回来细细品读) 嵌套的路由/视图表模块化的、基于组件的路由配置路由参数、查询、通配符基于 Vue.js 过渡系统的视图过渡效果细粒度的导航控制带有自动激活的 CSS class 的链接HTML5 历史模式或 hash 模式,在 IE9 中自动降级自定义的滚动条行为以下代码直接新建一个html,复制黏贴即可 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue router</title> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script></head><body><div id="app"> <h1>Hello sz!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> <br/><br/>路由匹配到的组件将渲染在这里下面 </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view></div></body><script> // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义 (路由) 组件。 // 可以从其他文件 import 进来 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定义路由 // 每个路由应该映射一个组件。 其中"component" 可以是 // 通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 // 我们晚点再讨论嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 `routes` 配置 // 你还可以传别的配置参数, 不过先这么简单着吧。 const router = new VueRouter({ //routes // (缩写) 相当于 routes: routes routes: routes }) // 4. 创建和挂载根实例。 // 记得要通过 router 配置参数注入路由, // 从而让整个应用都有路由功能 const app = new Vue({ router }).$mount('#app') // 现在,应用已经启动了!// 扩展// 通过 this.$router 访问路由器 this.$route.params.username// 也可以通过 this.$route 访问当前路由 this.$router.push('/')</script> </html>

June 13, 2019 · 1 min · jiezi

前端答疑对象引用vue共享数据源的三种方式

事情发生在上周(2019-06-06)团队技术分享的时候。起因在于一个问题:vue 中多个组件如何使用同一个变量,我们叫这个变量为 baseConfig 吧。说实话我没想到那么多人不理解其中的知识。今天我整理一下发出来。 方案一:VUEX什么是 VuexVuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 状态自管理应用包含以下几个部分: state,驱动应用的数据源;view,以声明方式将 state 映射到视图;actions,响应在 view 上的用户输入导致的状态变化。以下是一个表示“单向数据流”理念的简单示意: Vuex 的应用其实看完了上面的介绍,我们就明白,这是一个非常符合我们需求的工具。那么我们就来看看怎么去应用。 mapState 辅助函数 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。从下面的例子可以看到,我们子组件中的值是通过计算属性来从 store 中获取的。这样在通过 mutations 等方式改变之后,我们的值也会动态更新。当然,你的页面简单的时候,也不需要再去使用 Vuex,考虑一下后面的方案吧。const store = new Vuex.Store({ state: { baseConfig: { server_name: 'lilnong.top' } }})// 创建一个 User 组件const Serv = { template: `<div>{{ server_name }}</div>`, computed: { server_name() { // this.$store return store.state.baseConfig.server_name } }}const app = new Vue({ el: '#app', store, // 这样可以把 store 的实例注入所有的子组件 components: { Serv }, template: ` <div class="app"> <serv></serv> </div> `})方案二:父子组件传参父传参的方式baseConfig 需要定义在最外面,然后给所有的子组件都传递进去,当有改变的时候,子组件也会跟着改变。 ...

June 10, 2019 · 2 min · jiezi

vuepagestackVue页面堆栈管理器

Vue页面堆栈管理器A vue page stack manager Vue页面堆栈管理器 vue-page-stack 示例展示了一般的前进、后退(有activited)和replace的场景,同时还展示了同一个路由可以存在多层的效果(输入必要信息)目前版本还没有经过整体业务的测试,欢迎有同样需求的进行试用 预览 示例源码 需求分析由于重度使用了Vue全家桶在web App、公众号和原生Hybrid开发,所以很自然的会遇到页面跳转与回退这方面的问题。 场景举例: 列表页进入详情页,然后回退某操作页A需要在下一页面B选择,选择后需要退回到A页面(A页面还要知道选择了什么)在任意页面进入到登录页面,登录或者注册成功后返回到原页面,并且要保证继续回退是不会到登陆页面的支持浏览器的back和forward(微信或者小程序很有用)在进入、退出或者某些特殊页面的时候添加一些动画,比如模仿ios的默认动画(进入是页面从右向左平移,退出是页面从左向右平移)尝试过的方法尝试了以下方法,但是都没有达到我的预期 keep-alive一般是使用两个router-view通过route信息和keep-alive控制页面是否缓存,这样存在两个问题: keep-alive对相同的页面只会存储一次,不会有两个版本的相同页面两个router-view之间没有办法使用transition等动画CSS配合嵌套route曾经在查看cube-ui的例子的时候,发现他们的例子好像解决了页面缓存的问题,我借鉴(copy)了他们的处理方式,升级了一下,使用CSS和嵌套route的方式实现了基本的需求。但是也有缺点: 我必须严格按照页面的层级来写我的route很多页面在多个地方需要用到,我必须都得把路由配上(例如商品详情页面,会在很多个地方有入口)功能说明在vue-router上扩展,原有导航逻辑不需改变push或者forward的时候重新渲染页面,Stack中会添加新渲染的页面back或者go(负数)的时候不会重新渲染,从Stack中读取先前的页面,会保留好先前的内容状态,例如表单内容,滚动条滑动的位置等back或者go(负数)的时候会把不用的页面从Stack中移除replace会更新Stack中页面信息回退到之前页面的时候有activited钩子函数触发支持浏览器的后退,前进事件支持响应路由参数的变化,例如从 /user/foo 导航到 /user/bar,组件实例会被复用可以在前进和后退的时候添加不同的动画,也可以在特殊页面添加特殊的动画安装和用法安装npm install vue-page-stack# ORyarn add vue-page-stack使用import Vue from 'vue'import VuePageStack from 'vue-page-stack';// vue-router是必须的Vue.use(VuePageStack, { router }); // App.vue<template> <div id="app"> <vue-page-stack> <router-view ></router-view> </vue-page-stack> </div></template><script>export default { name: 'App', data() { return { }; }, components: {}, created() {}, methods: {}};</script>API注册注册的时候可以指定VuePageStack的名字和keyName,一般不需要 Vue.use(VuePageStack, { router, name: 'VuePageStack', keyName: 'stack-key' });前进和后退想在前进、后退或者特殊路由添加一些动画,可以在router-view的页面通过watch $route,通过stack-key-dir(自定义keyName这里也随之变化)参数判断此时的方向,可以参考实例 ...

June 5, 2019 · 1 min · jiezi

vuerouter工作原理概述和问题分析

问题1. 为什么url变更时,router-view组件就能渲染出在routes中定义的与当前path匹配的组件?root vue实例上定义了一个响应式属性 Vue.util.defineReactive(this, '_route', this._router.history.current)url变更时,会匹配到最新的route,并且会设置 this._routerRoot._route, 触发setterrouter-view组件的render函数中有使用到 parent.$route, 也就是间接的触发了this._routerRoot._route的getter。则this._routerRoot._route的setter触发时即会触发router-view的渲染watcher, 再次渲染, 并且此时拿到的路由组件也是最新的。本质上利用了vue的响应式属性,在route属性变更和router-view视图渲染之间建立关系。 route变更 => render重新渲染 2. router-view render中使用到parent.$route为什么就会被this._routerRoot._route收集watcher在挂载router-view组件时,会生成一个watcher, router-view的render函数中又触发了_route的getter方法,那么此watcher就被收集到_route的Dep中了。 在_route的setter触发时,自然执行了这个watcher, 组件重新render。 在 vue的仓库中vue/src/core/instance/lifecycle.js中mountComponent方法中,能看到挂载组件时是会生成一个watcher的: export function mountComponent( vm: Component, el: ?Element, hydrating?: boolean) { ... let updateComponent updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, noop, { before() { ... } }) ... return vm}3. this.$router, this.$route是怎么挂载每个vue组件上的? Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } })4.替换routes的写法(这样写为什么有用)// 替换现有router的routesrouter.matcher = new VueRouter({ routes: newRoutes}).matcherrouter.matcher是比较核心的一个属性。对外提供两个方法match(负责route匹配), addRoutes(动态添加路由)。 ...

June 4, 2019 · 1 min · jiezi

vuerouter之hash模式和history模式

当前版本: 3.0.3类目录: src/history/base.js hash模式即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。 history模式利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。 HTML5History实现使用window.addEventListener('popstate')监听浏览器滚动行为,然后判断配置是否有scrollBehavior, 有就调用handleScroll方法来处理滚动行为: 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。 handleScroll <!-- 等待页面渲染完才进行滚动的操作 --> router.app.$nextTick(() => { <!-- 初始化数据 --> const position = getScrollPosition() const shouldScroll = behavior.call(router, to, from, isPop ? position : null) if (!shouldScroll) { return } <!-- 判断是否是Promise,官网说支持异步 --> if (typeof shouldScroll.then === 'function') { shouldScroll.then(shouldScroll => { scrollToPosition((shouldScroll: any), position) }).catch(err => { if (process.env.NODE_ENV !== 'production') { assert(false, err.toString()) } }) } else { scrollToPosition(shouldScroll, position) } })scrollToPosition ...

May 30, 2019 · 2 min · jiezi

分享一个vue项目脚手架项目

搭建缘由源于公司每次新启动一个由多人协同开发的项目都由负责人初始化项目之后,每个人再去从私服pull一下项目才开始开发。但是每次初始化工程都是一步步的造轮子,一个个依赖去安装,新建一个个不同功能的文件夹,而每个负责人所初始化的项目目录、以及模块引入方式参差不齐,以至于开发中后期因每个人开发风格的不同导致git提交时总会产生各种各样的“冲突”,也会产生后期代码维护成本增加,所以就有必要考虑一下做一个统一的类似“脚手架”的功能了,用来给团队开发带来便捷的、统一的、易扩展的项目基础。 预实现的功能公共样式统一管理,全局sass的友好引入公共js统一管理解决vue脚手架初始化的部分问题路由形式、接口统一管理store模块化管理定义vue前端项目必用的方法修改好统一的config配置全局混入/指令的封装必要的依赖项node-sass sass sass-resources sass-loader sass-recources-loadervuex vuex-persistedstateaxiosbabel-polyfill项目目录如下 配置公共sass目录assets>scss文件形式 mixin.scss内容详见 mixin公共sass函数 common.scss内容如下 @import './mixin.scss'; // 公共函数@import './icomoon.css'; //字体图标@import './wvue-cli.scss'; //项目公共样式修改utils.js引入commom.css,就不用在main.js 或其他项目中的页面引入了 //57行开始function resolveResouce(name) { return path.resolve(__dirname, '../src/assets/scss/' + name); } function generateSassResourceLoader() { var loaders = [ cssLoader, // 'postcss-loader', 'sass-loader', { loader: 'sass-resources-loader', options: { // it need a absolute path resources: [resolveResouce('common.scss')] } } ]; if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // 注意这里 return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateSassResourceLoader(), scss: generateSassResourceLoader(), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') }接口统一管理js目录下的urlConfig.js ...

May 26, 2019 · 3 min · jiezi

vuecli3-vant-rem-移动端框架方案

描述基于vue-cli3.0+webpack 4+vant ui + sass+ rem适配方案+axios封装,构建手机端模板脚手架 vuecli3.0多环境开发axios封装rem适配方案多环境开发之前写过一个多环境的方案,是基于vue-cli2.0的vue多环境配置方案-传送门最近新的项目采用了vuecli3.0开始了一番折腾 这里参考了 vue-admin-template基本思路不变在src的文件里新建config 根据不同的环境去引用不同的配置文件,不同的是在根目录下有三个设置环境变量的文件这里可以参考vue-admin-template 这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去config下新建三个对应的文件呢?个人比较喜欢这种引入的方式而,比如我需要在文件引入api.common_api import { api } from '@/config'// apiconst { common_api } = api rem适配方案还是那句话,用vw还是用rem,这是个问题? 选用rem的原因是因为vant直接给到了这个适配方案,个人也比较喜欢这个方案 总结因为项目刚刚构建起来,后面还会持续更新,实际使用过程中一定还有很多问题,如果文章中有错误希望能够被指正,一起成长 项目github地址 关于我您可以扫描添加下方的微信并备注 Soul 加交流群,给我提意见,交流学习。 如果对你有帮助送我一颗小星星(づ ̄3 ̄)づ╭❤~ 转载请联系作者!

May 23, 2019 · 1 min · jiezi

IPress-让你更流畅的书写-Spring-Boot-Vue-VueCli3-markdown

导语很久没有更新文章了, 最近参考showdoc.cc 写了一个 笔记(知识管理/博客)工具 IPress. 技术栈Spring Boot 2.x Mysql 8.x Vue 2.x Vue Router 2.x iview ui 3.x 前后端分离 通过 jwtToken 跨域认证演示地址:Ipress 部署在了码云的Pages上 项目源码获取:已上传 github 项目模块划分:IPress│├─ipress-core 用户权限登录模块 (80%)│├─ipress-pocket 业务功能模块(已完成)│├─ipress-run run 入口│└─ipress-ui 用户界面 (已完成)界面首页: pocket: pocket: 文件夹管理: 编辑器:

May 23, 2019 · 1 min · jiezi

vuecli构建的小说阅读器

项目介绍主要页面1、首页home.vue分类展示书籍,幻灯片展示热门推荐2、搜索search.vue,上拉加载更多3、书籍详情book.vue加入书架、立即阅读,展示评论,同类书籍推荐4、书籍内容read.vue,获取目录,存储翻阅的章节位置,5、书架bookrack.vue,获取加入书架的书单 技术栈vue、vue-cli、axios、vue-router、vuex、localStorege 入口页面app.vue分成底部导航 跟 主视图容器 router-view首页tabbar/Home包含: components/sub/item 和 components/sub/search 、components/sub/header结构: banner切换 与 搜索 和 小说分类楼层 小说楼层单独定义了组件 components/sub/item , home循环楼层分类名称,并将楼层分类id传给item组件 :booklistId='{id:item._id}' , item组件用props: ["booklistId"] 接收分类id, 并根据分类id获取对应的数据item.vue mouted: this.getlist(this.booklistId.id);methods: getlist(id) { //每个分类id下对应的数据 子组件接收父组件传过来的id 获取对应的数据 bootd(id).then(res => { var arrdata = res.data.data; arrdata.map(item => { this.booklist.push(item.book); }); }); }小说详情页components/book/Book.vue包含: components/sub/yuedu 、mulu、pinglun、结构: 小说概况与简介,是否加入书架或者继续阅读 ,目录、评论、同类书籍推荐加入书架/立即阅读(yuedu.vue)组件 加入书架,获取书籍信息,并把相关书籍信息存放在书架中、存localbook.vue computed: { ...mapState(['calbook','shuajiabook']) //书籍信息 书架数据[] }, methods:{ addbook(){ this.flag=!this.flag var book= this.calbook; // calbook 是store里存储的书籍信息【 SHEFLBOOK 】 var carbook = JSON.parse(window.localStorage.getItem('book') || '{}') if(!this.flag){ //加入书架 carbook[book._id] = { cover: book.cover, flag:!this.flag, title: book.title, lastChapter:book.lastChapter, id: book._id, author:book.author, chapterIndexCache: 0, bookSource: 0, pageIndexCache: 0, } this.setbook(false) window.localStorage.setItem('book', JSON.stringify(carbook)) }else{ delete carbook[book._id] this.setbook(true) //设置的布尔值 window.localStorage.setItem('book', JSON.stringify(carbook)) } } }立即阅读时进入小说章节 `this.$router.push({name:'read',params:{id:this.booklinks}})`目录组件components/sub/mulu.vue ...

May 13, 2019 · 2 min · jiezi

Vuejs应用性能优化第二部分路由懒加载和-Vendor-bundle-反模式

在前一篇文章中,我们学习了什么是代码分割,它是如何与 Webpack 一起工作的,以及如何在Vue应用程序中使用延迟加载。现在,我们将更深入地研究,并学习用于分割 Vue.js 应用程序最实用的模式。 本系列文章基于对 Vue Storefront 性能优化过程的学习。通过使用下面的技术,我们能够将初始文件的大小减少70%,并在眨眼间使其加载。 Part 1 — Introduction to performance optimization and lazy loading.Part 2 — Lazy loading routes and vendor bundle anti-pattern.Part 3 — Lazy loading Vuex modulesPart 4 — Delivering good waiting experience and lazy loading individual components — soonPart 5 — Lazy loading libs and finding smaller equivalents — soonPart 6 — Performance-friendly usage of UI librariesPart 7 — Making use of Service Worker cache — soonPart 8 — Prefetching应用程序增长的问题Vue-router 是一个库,它允许将我们的web应用程序自然地分割成单独的页面。每个页面都是与某个特定URL路径相关联的路由。 ...

May 13, 2019 · 2 min · jiezi

一张思维导图辅助你深入了解-Vue-VueRouter-Vuex-源码架构

1.前言本文内容讲解的内容:一张思维导图辅助你深入了解 Vue | Vue-Router | Vuex 源码架构。 项目地址:https://github.com/biaochenxuying/vue-family-mindmap 文章的图文结合版 Vue-family.md Vue-family.pdf 2. Vue 全家桶先来张 Vue 全家桶 总图: 3. Vue细分如下 源码目录 源码构建,基于 Rollup  Vue 本质:构造函数 数据驱动 组件化 深入响应式原理 编译 扩展 4. Vue-Router introduction 路由注册 VueRouter 对象 matcher 路径切换 5. Vuex introduction Vuex 初始化 API 插件 6. 已完成与待完成已完成: 思维导图待完成: 继续完善 思维导图添加 流程图因为该项目都是业余时间做的,笔者能力与时间也有限,很多细节还没有完善。 如果你是大神,或者对 vue 源码有更好的见解,欢迎提交 issue ,大家一起交流学习,一起打造一个像样的 讲解 Vue 全家桶源码架构 的开源项目。 7. 总结以上内容是笔者最近学习 Vue 源码时的收获与所做的笔记,本文内容大多是开源项目 Vue.js 技术揭秘 的内容,只不过是以思维导图的形式来展现,内容有省略,还加入了笔者的一点理解。 ...

May 12, 2019 · 1 min · jiezi

打包优化从0到1搭建element后台框架优化篇

前言hello,咱又见了~~嘻嘻。本次主要来说说这个打包优化的问题。一个vue项目从开发到上线必须得经历打包过程,一个项目的打包优化与否都决定了你这个项目的运行速度以及用户体验。本次主要是针对vue.config,js的配置进行优化。项目地址 开发环境与生产环境开发环境与生产环境的配置也是开发中的必不可少的一环。本项目是由vue-cli3开发,vue-cli3深度集成了webpack,如果不熟悉vue-cli3可以先去官网看看相关配置。 开发环境在项目根目录下新建.env.development文件表明是开发环境。 VUE_APP_CURRENTMODE ="development" //当前的环境 VUE_APP_LOGOUT_URL="http://localhost:3000/" //开发环境的地址生产环境在项目根目录下新建.env.production文件表明是生产环境。 VUE_APP_CURRENTMODE ="development" //当前的环境 VUE_APP_LOGOUT_URL="xxx" //生产环境的地址当然你也可以自己创建一个测试环境.env.test,同样可以像上边一样配置。 环境运用那么接下来我们怎么用它呢?这里不得不说一下的是package.json里面的两个命令serve,build,其实对应的是全命令是vue-cli-service serve --mode development,vue-cli-service build --mode production,如果你想要在构建命令中使用开发环境变量,那么可以加入 "dev-build": "vue-cli-service build --mode development"接下来在vue.config.js运用它。 config.plugin('define').tap(args => { args[0]['process.env'].VUE_APP_LOGOUT_URL = JSON.stringify(process.env.VUE_APP_LOGOUT_URL) console.log(args[0]) return args; });这里有必要说下,这段代码是写在chainWebpack配置项下面。这段代码其实运用了两个webpack插件webpack-chain允许配置链式操作,以及webpack.DefinePlugin。 webpack-chain:尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置。了解更多webpack.DefinePlugin:它的作用是定义全局常量,是常量。即在模块用它定义的全局常量,那么你就不能改变它。也就是说我定义了一个process.env.VUE_APP_LOGOUT_URL常量,在src文件夹下面都可以使用。了解更多分包(code splitting)首先思考,我们引入的第三方包与我们的业务代码一起打包会产生什么问题?顾名思义,我们的业务代码变动比较频繁,而我们引入的第三方包基本上不会变动。浏览器会有缓存,没有变动的文件会直接从缓存中读取,这也间接的优化了网站的访问速速。接下来配置vue.config.js, 分割第三方库 //代码分割 config.optimization.minimize(true); config.optimization.splitChunks({ chunks: 'all', cacheGroup:{ //vue2-editor单独打一个包 vueEdior: { name: 'vueEdior', test: /[\\/]node_modules[\\/]vue2-editor[\\/]/, priority: 10 // 优先级要大于 vendors 不然会被打包进 vendors }, //其余的第三方包打进vendor vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 } } })分割共用文件组件是vue项目的重要组成部分。相当一部分组件都可以公用,在不同的文件中引入,因此我们可以将这部分公用的组件直接分割出来。 ...

May 11, 2019 · 2 min · jiezi

Vuejs应用性能优化第一部分介绍性能优化和懒加载

当“移动优先”(mobile-first)的方式逐渐成为一种标准,而且不确定的网络环境因素应该始终是我们考虑的一点,因此保持让应用程序快速加载变得越来越困难。在本系列文章中,我将深入研究Vue性能优化技术,我们在 Vue Storefront 中已经使用了这些技术,您也可以在Vue.js应用程序中使用这些技术,使它们能够立即加载并顺利运行。我的目标是使本系列成为关于Vue应用程序性能的完整指南。 Part 1 — Introduction to performance optimization and lazy loading.Part 2 — Lazy loading routes and vendor bundle anti-pattern.Part 3 — Lazy loading Vuex modulesPart 4 — Delivering good waiting experience and lazy loading individual components — soonPart 5 — Lazy loading libs and finding smaller equivalents — soonPart 6 — Performance-friendly usage of UI librariesPart 7 — Making use of Service Worker cache — soonPart 8 — PrefetchingWebpack 打包工作原理本系列的大部分技巧将集中于使我们的JS包更小。要理解这一点,首先我们需要理解 Webpack 是如何打包(bundling)我们所有的文件的。当打包我们的资源时,Webpack 创建了被成为依赖图(dependency graph)的东西,它是一个基于入口,链接我们所有文件的图。假设我们在webpack配置中指定了一个名为 main.js 的文件作为入口点,它将是依赖关系图的根。现在,我们将在此文件中导入的每个js模块将成为图中的节点,并且在此节点中导入的每个模块都将成为其节点。Webpack 正是使用这个依赖关系图来检测输出的包中应该包含哪些文件。输出包只是一个包含依赖关系图中所有模块的 Javascript 文件(或后面的部分将看到多个)。 ...

May 10, 2019 · 2 min · jiezi

从vuerouter里学习插件写法

前言最近在看vue-router和vuex的源码,都会有自己的install方法,然后通过mixin绑定到生命周期里 vue.usevue.use兼容带install和不带install,这两种注册的方式都可以,只是install会更容易扩展 export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { <!-- 已经use的插件数组 --> const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters <!-- 把传入的参数整理成数组 --> const args = toArray(arguments, 1) <!-- 默认第一个参数是vue对象 --> args.unshift(this) <!-- 判断带不带install方法 --> if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } installedPlugins.push(plugin) return this }}vue-routerexport function install (Vue) { if (install.installed && _Vue === Vue) return // 避免重复加载 install.installed = true // 加载标志 _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } }<!-- 通过mixin添加生命周期 --> Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) <!-- 所有实例中 this.$router 等同于访问 this._routerRoot._router --> Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } })<!-- 所有实例中 this.$route 等同于访问 this._routerRoot._route --> Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) Vue.component('RouterView', View) Vue.component('RouterLink', Link) const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}总结要写一个vue的插件,可以是带install或不带,然后在里面通过mixin,绑定prototype,调用component等方法去注册到vue实例上去 ...

May 7, 2019 · 2 min · jiezi

VueCLI2x全家桶架构支持打包后自动部署到服务器构建案例

今天有时间分享一些平时自己开发上的一些构建配置,我们以Vue-CLI-2.x来构建开发环境。好,我们先来看一下我们要做哪些工作。现附上源码地址,https://github.com/749264345/... 1.Vue,Vuex,vue-router,axios通过CDN引入;优化打包后文件过大的问题2.开发环境区分开发,测试,生产;提高开发效率3.打包区分测试与生产4.实现项目打包后自动部署到远程服务器5.模块化构建vuex状态管理6.打包后自动清除console.log()7.项目默认基于Element-ui,对相关组件二次封装8.模块化构建axios请求响应拦截器一寸光阴一寸金,寸金难买寸光阴~废话不多说,直接撸起袖子就是干~~ 1.Vue,Vuex,vue-router,axios通过CDN引入在过去我们都习惯使用npm或者cnpm安装依赖,因为这种操作非常方便,但是有利必有弊,有些小伙伴在将项目打包后会发现打包后的js文件非常庞大,有些甚至达到了几兆,所以最终导致应用打开时首屏加载非常慢,因此我们可以使用传统的引入方式,也就是CDN引入。Vue,Vuex,vue-router,axios,这些均为静态资源,我们从它们官网下载适合的版本后放入项目根目录【static】文件夹,然后修改几个地方:首先,index.html <!--兼容ie--> <script src="./static/libs/polyfill.min.js"></script> <script src="./static/libs/eventsource.js"></script> <!--vue--> <script src="./static/libs/vue.js"></script> <script src="./static/libs/vue-router.min.js"></script> <script src="./static/libs/element-ui/element-ui.js"></script> <script src="./static/libs/axios.js"></script> <script src="./static/libs/vuex.js"></script>然后修改,build/webpack.base.conf.js module.exports = { externals: { 'vue': 'Vue', 'vuex': 'Vuex', 'vue-router': 'VueRouter', 'axios': 'axios' }, ...}到这里基本配置已经完成,最后一步需要在main.js中删除原先对这些模块的import操作即可。这样后再打包项目,你会发现页面非常丝滑,几乎秒开。 2.开发环境区分开发,测试,生产;提高开发效率在调试接口,或者debug的时候我们经常会切换环境,而且在打包的时候又要改成生产的接口,这样没有效率,所以我们可以做如下配置。在config/dev.env.js文件中, const TARGET = process.env.npm_lifecycle_event;//开发环境if (TARGET === 'dev') { var data = { NODE_ENV: '"dev"', API: '"http://www.dev.com"' }}//测试环境if (TARGET === 'test') { var data = { NODE_ENV: '"test"', API: '"http://www.test.com"' }}//生产环境if (TARGET === 'prod') { var data = { NODE_ENV: '"prod"', API: '"http://www.prod.com"' }}我们从process.env.npm_lifecycle_event中获得当前node执行的环境来区分项目运行的环境,这样我们可以用来区分接口环境,因此我们添加相关指令。在根目录package.json文件中, ...

May 5, 2019 · 3 min · jiezi

vuerouter-前端路由之路由传值

路由传值在前端的路由里面,我们在切换路由的时候,也相当于切换了页面,页面与页面之前有时候需要做到传值 ,这个时候就需要进行路由传值,在VueRouter里面,两个路由之间做跳转的时候,如何进行传值呢? 普通跨页面传值:通过localStorage setItem()getItem()通过search(地址栏? 后面的参数)VueRouter的路由传值VueRouter的路由传值有两种方式 query传值。 类似get传值 传值的路由 this.$router.push({ path: "/login?uname=" + this.userName});传值路由第二种写法 this.$router.push({ path: "/login", query: { uname: this.userName }});接收值的路由 console.log("接收过来的值为:" + this.$route.query.uname); //这里是$route 没有r----params传值 。路径变量传值 params路由传值可以把它理解成express路径变量传值 ,它也可以放在地址栏里面进行传递 传值路由第一种写法(还是会将参数显示在地址栏中) this.$router.push({ path: "/login/" + this.userName});传值路由的第二种写法(不会将参数显示在地址栏中) this.$router.push({ name: "login", params: { uname: this.userName }});接收值的路由 console.log("接收过来的值为:" + this.$route.params.uname);注意:在使用params传递参数的时候,我们需要在router的对象里面,找到当前的这个路由,然后去更改它的path { path: "/login/:uname", //代表当前url跳转的路径 component: login, //代表在当前这个路径下面,我们如何显示组件(显示那一个组件) name: "login" //给当前路由取一个别名 }上面的path后面是/login/:uname,这一个是我们的一个路径变量,前面的login代表路由,而后面:uname代表的是变量---通过第二种方式的params传值 ,引伸出post原理传值 params本身确实是会把参数添加到url地址栏,但是,我们可以让它不显示出来,使用下面的方法,我们就可以把它去掉,不显示,从而内容不经过浏览器地址栏处理,直接做到传值。 它只是把路由对象里面的路径变量给去掉了,直接使用的params传值 注意事项:因为它把path里面的路径变量去掉了,所以不能使用path去传递值了 this.$router.push({ path: "/login/" + this.userName});//现在上面的方法就不可用了,而必须使用下面的方法 this.$router.push({ name: "login", params: { uname: this.userName }});下面的这种写法,变量没有显示在路径里面,只是通过params存储,params传递,不再经过url了,这个时候,我们就可以传递大家数据,同时,也可以保证数据安全这一个就是vue当中变相去处理post传值 ...

April 30, 2019 · 1 min · jiezi

从0到1搭建element后台框架之权限篇

前言首先还是谢谢各位童鞋的大大的赞赞,你们的支持是我前进的动力!上周写了一篇从0到1搭建element后台框架,很多童鞋留言提到权限问题,这一周就给大家补上。GitHub 一、jwt授权认证现在大多数项目都是采用jwt授权认证,也就是我们所熟悉的token登录身份校验机制,jwt的好处多多,由于jwt是由服务端生成,中间人修改密串后,服务端会校验不过,安全有效。一般呆在请求头上的Authorization里面。前端童鞋一般获取token后通过vuex存储起来,随后数据持久化存到session中。 路由跳转验证token首先在路由跳转的时候需要验证vuex是否存储了token,如果没有token的话直接跳到登陆页面获取token。 if (to.path !== '/login' && !store.state.token) { next('/login') NProgress.done() // 结束Progress } else { next(); }请求拦截带上token详细请看项目中的router.js本地存在token之后,我们在每次请求接口的时候都需要带上token来验证token的合法性。 //在请求前拦截 if (store.state.token) { config.headers["Authorization"] = "Bearer " + store.state.token; }如果token不合法,全局错误处理,直接跳到登陆页面 case 401: messages("warning", "用户登陆过期,请重新登陆"); store.commit('COMMIT_TOKEN','') setTimeout(() => { router.replace({ path: "/login", query: { redirect: router.currentRoute.fullPath } }); }, 1000); break;详细代码看项目中的request.js 二、菜单权限本项目中,我主要是通过后端传过来的角色类型来判断导航菜单的显示与隐藏。也就是说首先前端请求接口,后端返回token,以及对应的角色,比如项目中用admin登陆的话,roles=['admin'],用user登陆的话roles=['user']。接下来我这边设计了一份菜单表和一份路由表,路由表主要是为了注册路由,不需要考虑层级关系。而菜单表需要考虑层级关系,里面可以配置主菜单,子菜单,图标等等一系列的东西,当然菜单表最好是通过接口数据从后端传过来。值得注意的是无论是菜单表,还是路由表,里面都有一个meta配置项。里面可以配置我们的角色权限。路由表对应的菜单表角色权限需要一致。没有配置角色权限的菜单默认都开放。 menu.js { icon: "el-icon-question", index: "premission", title: "权限测试", subs: [{ index: "permission", title: "菜单测试", meta: { roles: ['admin'] } }, { index: "permissionBtn", title: "按钮权限", }, ] }router.js { path: '/permission', component: getComponent('permission', 'permission'), meta: { title: '菜单权限', roles: ['admin'] } },根据角色过滤菜单现在我们开始编写菜单逻辑,进入Aside.vue,首先根据角色过滤菜单表menu.js ...

April 26, 2019 · 2 min · jiezi

VueRouter源码分析

感谢funfish, 玩弄心里的鬼, Vue.js 技术揭秘的文章,对我的帮助前言vue-router的源码不算很多, 但是内容也不算少。本文谈不上逐行分析, 但是会尽量详尽的说明主流程和原理。对一些工具函数和边缘条件的处理会略过,因为我也没有逐行去了解它们,请见谅。前置基础知识我们在学习VueRouter源码前,先来复习下hash以及histroy相关的知识。更多细节请参考mdn文档,本节内容节选自mdn文档。hashonhashchange当URL的片段标识符更改时,将触发hashchange事件 (跟在#符号后面的URL部分,包括#符号)。注意 histroy.pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。histroypushStatepushState()需要三个参数: 一个状态对象, 一个标题(目前被忽略), 和一个URL。state, 状态对象state是一个JavaScript对象,popstate事件触发时,该对象会传入回调函数title, 目前所有浏览器忽略url, 新的url记录replaceStatehistory.replaceState()的使用与history.pushState()非常相似,区别在于replaceState()是修改了当前的历史记录项而不是新建一个。onpopstate调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法)。如果当前处于激活状态的历史记录条目是由history.pushState()方法创建, 或者由history.replaceState()方法修改过的, 则popstate事件对象的state属性包含了这个历史记录条目的state对象的一个拷贝。应用初始化通常构建一个Vue应用的时候, 我们会使用Vue.use以插件的形式安装VueRouter。同时会在Vue的实例上挂载router的实例。import Vue from ‘vue’import App from ‘./App.vue’import router from ‘./router’Vue.config.productionTip = falselet a = new Vue({ router, render: h => h(App)}).$mount(’#app’)import Vue from ‘vue’import Router from ‘vue-router’import Home from ‘./views/Home.vue’Vue.use(Router)export default new Router({ mode: ‘history’, base: process.env.BASE_URL, routes: [ { path: ‘/’, name: ‘home’, component: Home }, { path: ‘/about’, name: ‘about’, component: () => import(/* webpackChunkName: “about” / ‘./views/About.vue’) } ]})插件的安装在Vue的文档中指出Vue.js 的插件应该有一个公开方法 install。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象, 我们首先查看源码中install.js的文件。在install文件中, 我们在Vue的实例上初始化了一些私有属性_routerRoot, 指向了Vue的实例_router, 指向了VueRouter的实例在Vue的prototype上初始化了一些getter$router, 当前Router的实例$route, 当前Router的信息并且在全局混入了mixin, 已经全局注册了RouterView, RouterLink组件.import View from ‘./components/view’import Link from ‘./components/link’export let _Vueexport function install (Vue) { if (install.installed && _Vue === Vue) return install.installed = true _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } Vue.mixin({ beforeCreate () { // 判断是否实例是否挂载了router if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) // _router, 劫持的是当前的路由 Vue.util.defineReactive(this, ‘_route’, this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) Object.defineProperty(Vue.prototype, ‘$router’, { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, ‘$route’, { get () { return this._routerRoot._route } }) Vue.component(‘RouterView’, View) Vue.component(‘RouterLink’, Link) const strats = Vue.config.optionMergeStrategies strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}Vue.util.defineReactive, 这是Vue里面观察者劫持数据的方法,劫持_route,当_route触发setter方法的时候,则会通知到依赖的组件。而RouterView, 需要访问parent.$route所以形成了依赖(我们在后面会看到)????我们到Vue中看一下defineReactive的源码, 在defineReactive, 会对_route使用Object.defineProperty劫持setter方法。set时会通知观察者。Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // … }, set: function reactiveSetter (newVal) { // … childOb = !shallow && observe(newVal) dep.notify() }})VueRouter实例export default class VueRouter { constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options this.beforeHooks = [] this.resolveHooks = [] this.afterHooks = [] this.matcher = createMatcher(options.routes || [], this) let mode = options.mode || ‘hash’ // fallback会在不支持history环境的情况下, 回退到hash模式 this.fallback = mode === ‘history’ && !supportsPushState && options.fallback !== false if (this.fallback) { mode = ‘hash’ } if (!inBrowser) { mode = ‘abstract’ } this.mode = mode switch (mode) { case ‘history’: this.history = new HTML5History(this, options.base) break case ‘hash’: this.history = new HashHistory(this, options.base, this.fallback) break case ‘abstract’: this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== ‘production’) { assert(false, invalid mode: ${mode}) } } }}matchermatcher对象中包含了两个属性, addRoutes, match。pathList, pathMap, nameMappathList, pathMap, nameMap分别是路径的列表, 路径和路由对象的映射, 路由名称和路由对象的映射。vue-router目标支持动态路由, pathList, pathMap, nameMap可以在初始化后动态的被修改。它们由createRouteMap方法创建, 我们来看看createRouteMap的源码。export function createRouteMap ( routes, oldPathList, oldPathMap, oldNameMap) { // pathList,pathMap,nameMap支持后续的动态添加 const pathList: Array<string> = oldPathList || [] const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null) const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null) // 遍历路由列表 routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route) }) // 将通配符的路径, push到pathList的末尾 for (let i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === ‘’) { pathList.push(pathList.splice(i, 1)[0]) l– i– } } return { pathList, pathMap, nameMap }}routes为一组路由, 所以我们循环routes, 但是route可能存在children所以我们通过递归的形式创建route。返回一个route的树????function addRouteRecord ( pathList, pathMap, nameMap, route, parent, matchAs) { const { path, name } = route const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {} // normalizePath, 会对path进行格式化 // 会删除末尾的/,如果route是子级,会连接父级和子级的path,形成一个完整的path const normalizedPath = normalizePath( path, parent, pathToRegexpOptions.strict ) if (typeof route.caseSensitive === ‘boolean’) { pathToRegexpOptions.sensitive = route.caseSensitive } // 创建一个完整的路由对象 const record: RouteRecord = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, instances: {}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {}, props: route.props == null ? {} : route.components ? route.props : { default: route.props } } // 如果route存在children, 我们会递归的创建路由对象 // 递归的创建route对象 if (route.children) { route.children.forEach(child => { const childMatchAs = matchAs ? cleanPath(${matchAs}/${child.path}) : undefined addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs) }) } // 这里是对路由别名的处理 if (route.alias !== undefined) { const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] aliases.forEach(alias => { const aliasRoute = { path: alias, children: route.children } addRouteRecord( pathList, pathMap, nameMap, aliasRoute, parent, record.path || ‘/’ // matchAs ) }) } // 填充pathMap,nameMap,pathList if (!pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record } if (name) { if (!nameMap[name]) { nameMap[name] = record } }}addRoutes动态添加更多的路由规则, 并动态的修改pathList,pathMap,nameMapfunction addRoutes (routes) { createRouteMap(routes, pathList, pathMap, nameMap)}matchmatch方法根据参数raw(可以是字符串也可以Location对象), 以及currentRoute(当前的路由对象返回Route对象),在nameMap中查找对应的Route,并返回。如果location包含name, 我通过nameMap找到了对应的Route, 但是此时path中可能包含params, 所以我们会通过fillParams函数将params填充到patch,返回一个真实的路径path。function match ( raw, currentRoute, redirectedFrom) { // 会对raw,currentRoute处理,返回格式化后path, hash, 以及params const location = normalizeLocation(raw, currentRoute, false, router) const { name } = location if (name) { const record = nameMap[name] if (!record) return _createRoute(null, location) // 获取所有必须的params。如果optional为true说明params不是必须的 const paramNames = record.regex.keys .filter(key => !key.optional) .map(key => key.name) if (typeof location.params !== ‘object’) { location.params = {} } if (currentRoute && typeof currentRoute.params === ‘object’) { for (const key in currentRoute.params) { if (!(key in location.params) && paramNames.indexOf(key) > -1) { location.params[key] = currentRoute.params[key] } } } if (record) { // 使用params对path进行填充返回一个真实的路径 location.path = fillParams(record.path, location.params, named route "${name}") // 创建Route对象 return _createRoute(record, location, redirectedFrom) } } else if (location.path) { location.params = {} for (let i = 0; i < pathList.length; i++) { const path = pathList[i] const record = pathMap[path] // 使用pathList中的每一个regex,对path进行匹配 if (matchRoute(record.regex, location.path, location.params)) { return _createRoute(record, location, redirectedFrom) } } } return _createRoute(null, location)}我们接下来继续看看_createRoute中做了什么。function _createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: Location): Route { if (record && record.redirect) { return redirect(record, redirectedFrom || location) } if (record && record.matchAs) { return alias(record, location, record.matchAs) } return createRoute(record, location, redirectedFrom, router)}其中redirect,alias最终都会调用createRoute方法。我们再将视角转向createRoute函数。createRoute函数会返回一个冻结的Router对象。其中matched属性为一个数组,包含当前路由的所有嵌套路径片段的路由记录。数组的顺序为从外向里(树的外层到内层)。export function createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: ?Location, router?: VueRouter): Route { const stringifyQuery = router && router.options.stringifyQuery let query: any = location.query || {} try { query = clone(query) } catch (e) {} const route: Route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || ‘/’, hash: location.hash || ‘’, query, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : [] } if (redirectedFrom) { route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery) } return Object.freeze(route)}initinit中。会挂载cb的回调,这关乎到RouteView的渲染。我们根据当前的url,在Vue根实例的beforeCreate生命周期钩子中完成路由的初始化,完成第一次的路由导航。init (app) { // app为Vue的实例 this.apps.push(app) if (this.app) { return } // 在VueRouter上挂载app属性 this.app = app const history = this.history // 初始化当前的路由,完成第一次导航,在hash模式下会在transitionTo的回调中调用setupListeners // setupListeners里会对hashchange事件进行监听 // transitionTo是进行路由导航的函数,我们将会在下面介绍 if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() } history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } // 挂载了回调的cb, 每次更新路由更好更新_route history.listen(route => { this.apps.forEach((app) => { app._route = route }) })}historyhistory一共有三个模式hash, histroy, abstract, 这三个类都继承至base类base我们首先看下base的构造函数, 其中router是VueRouter的实例, base是路由的基础路径。current是当前的路由默认为"/", ready是路由的状态, readyCbs是ready的回调的集合, readyErrorCbs是raday失败的回调。errorCbs导航出错的回调的集合。export class History { constructor (router: Router, base: ?string) { this.router = router // normalizeBase会对base路径做出格式化的处理,会为base开头自动添加‘/’,删除结尾的‘/’,默认返回’/‘ this.base = normalizeBase(base) // 初始化的当前路由对象 this.current = START this.pending = null this.ready = false this.readyCbs = [] this.readyErrorCbs = [] this.errorCbs = [] }}export const START = createRoute(null, { path: ‘/’})function normalizeBase (base: ?string): string { if (!base) { // inBrowser判断是否为浏览器环境 if (inBrowser) { const baseEl = document.querySelector(‘base’) base = (baseEl && baseEl.getAttribute(‘href’)) || ‘/’ base = base.replace(/^https?://[^/]+/, ‘’) } else { base = ‘/’ } } if (base.charAt(0) !== ‘/’) { base = ‘/’ + base } return base.replace(//$/, ‘’)}base中的listen的方法,会在VueRouter的init方法中使用到,listen会给每一次的路由的更新,添加回调listen (cb: Function) { this.cb = cb} base类中还有一些其他方法比如,transitionTo,confirmTransition,updateRoute它们在base子类中被使用。我们马上在hashrouter中再看看它们的具体实现。HashRouter构造函数在HashHistory的构造函数中。我们会判断当前的fallback是否为true。如果为true,使用checkFallback,添加’#‘,并使用window.location.replace替换文档。如果fallback为false,我们会调用ensureSlash,ensureSlash会为没有“#”的url,添加“#”,并且使用histroy的API或者replace替换文档。所以我们在访问127.0.0.1的时候,会自动替换为127.0.0.1/#/export class HashHistory extends History { constructor (router: Router, base: ?string, fallback: boolean) { super(router, base) // 如果是回退hash的情况,并且判断当前路径是否有/#/。如果没有将会添加’/#/’ if (fallback && checkFallback(this.base)) { return } ensureSlash() }}checkFallback// 检查url是否包含‘/#/’function checkFallback (base) { // 获取hash值 const location = getLocation(base) // 如果location不是以/#,开头。添加/#,使用window.location.replace替换文档 if (!/^/#/.test(location)) { window.location.replace( cleanPath(base + ‘/#’ + location) ) return true }}// 返回hashexport function getLocation (base) { let path = decodeURI(window.location.pathname) if (base && path.indexOf(base) === 0) { path = path.slice(base.length) } return (path || ‘/’) + window.location.search + window.location.hash}// 删除 //, 替换为 /export function cleanPath (path) { return path.replace(////g, ‘/’)}ensureSlashfunction ensureSlash (): boolean { // 判断是否包含#,并获取hash值。如果url没有#,则返回‘’ const path = getHash() // 判断path是否以/开头 if (path.charAt(0) === ‘/’) { return true } // 如果开头不是‘/’, 则添加/ replaceHash(’/’ + path) return false}// 获取“#”后面的hashexport function getHash (): string { const href = window.location.href const index = href.indexOf(’#’) return index === -1 ? ’’ : decodeURI(href.slice(index + 1))}function replaceHash (path) { // supportsPushState判断是否存在history的API // 使用replaceState或者window.location.replace替换文档 // getUrl获取完整的url if (supportsPushState) { replaceState(getUrl(path)) } else { window.location.replace(getUrl(path)) }}// getUrl返回了完整了路径,并且会添加#, 确保存在/#/function getUrl (path) { const href = window.location.href const i = href.indexOf(’#’) const base = i >= 0 ? href.slice(0, i) : href return ${base}#${path}}在replaceHash中,我们调用了replaceState方法,在replaceState方法中,又调用了pushState方法。在pushState中我们会调用saveScrollPosition方法,它会记录当前的滚动的位置信息。然后使用histroyAPI,或者window.location.replace完成文档的更新。export function replaceState (url?: string) { pushState(url, true)}export function pushState (url?: string, replace?: boolean) { // 记录当前的x轴和y轴,以发生导航的时间为key,位置信息记录在positionStore中 saveScrollPosition() const history = window.history try { if (replace) { history.replaceState({ key: _key }, ‘’, url) } else { _key = genKey() history.pushState({ key: _key }, ‘’, url) } } catch (e) { window.locationreplace ? ‘replace’ : ‘assign’ }}push, replace,我们把push,replace放在一起说,因为它们实现的源码都是类似的。在push和replace中,调用transitionTo方法,transitionTo方法在基类base中,我们现在转过头来看看transitionTo的源码(????往下两节,代码不是很难,但是callback嵌套callback, 如蜜传如蜜,看起来还是比较恶心的)push (location, onComplete, onAbort) { const { current: fromRoute } = this this.transitionTo( location, route => { pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort )}replace (location, onComplete, onAbort) { const { current: fromRoute } = this this.transitionTo( location, route => { replaceHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort )}transitionTo, confirmTransition, updateRoutetransitionTo的location参数是我们的目标路径, 可以是string或者RawLocation对象。我们通过router.match方法(我们在在matcher介绍过),router.match会返回我们的目标路由对象。紧接着我们会调用confirmTransition函数。transitionTo (location, onComplete, onAbort) { const route = this.router.match(location, this.current) this.confirmTransition( route, () => { // … }, err => { // … } )}confirmTransition函数中会使用,isSameRoute会检测是否导航到相同的路由,如果导航到相同的路由会停止????导航,并执行终止导航的回调。if ( isSameRoute(route, current) && route.matched.length === current.matched.length) { this.ensureURL() return abort()}接着我们调用resolveQueue方法,resolveQueue接受当前的路由和目标的路由的matched属性作为参数,resolveQueue的工作方式可以如下图所示。我们会逐一比较两个数组的路由,寻找出需要销毁的,需要更新的,需要激活的路由,并返回它们(因为我们需要执行它们不同的路由守卫)function resolveQueue ( current next) { let i // 依次比对当前的路由和目标的路由的matched属性中的每一个路由 const max = Math.max(current.length, next.length) for (i = 0; i < max; i++) { if (current[i] !== next[i]) { break } } return { updated: next.slice(0, i), activated: next.slice(i), deactivated: current.slice(i) }}下一步,我们会逐一提取出,所有要执行的路由守卫,将它们concat到队列queue。queue里存放里所有需要在这次路由更新中执行的路由守卫。第一步,我们使用extractLeaveGuards函数,提取出deactivated中所有需要销毁的组件内的“beforeRouteLeave”的守卫。extractLeaveGuards函数中会调用extractGuards函数,extractGuards函数,会调用flatMapComponents函数,flatMapComponents函数会遍历records(resolveQueue返回deactivated), 在遍历过程中我们将组件,组件的实例,route对象,传入了fn(extractGuards中传入flatMapComponents的回调), 在fn中我们会获取组件中beforeRouteLeave守卫。// 返回每一个组件中导航的集合function extractLeaveGuards (deactivated) { return extractGuards(deactivated, ‘beforeRouteLeave’, bindGuard, true)}function extractGuards ( records, name, bind, reverse?) { const guards = flatMapComponents( records, // def为组件 // instance为组件的实例 (def, instance, match, key) => { // 返回每一个组件中定义的路由守卫 const guard = extractGuard(def, name) if (guard) { // bindGuard函数确保了guard(路由守卫)的this指向的是Component中的实例 return Array.isArray(guard) ? guard.map(guard => bind(guard, instance, match, key)) : bind(guard, instance, match, key) } } ) // 返回导航的集合 return flatten(reverse ? guards.reverse() : guards)}export function flatMapComponents ( matched, fn) { // 遍历matched,并返回matched中每一个route中的每一个Component return flatten(matched.map(m => { // 如果没有设置components则默认是components{ default: YouComponent },可以从addRouteRecord函数中看到 // 将每一个matched中所有的component传入fn中 // m.components[key]为components中的key键对应的组件 // m.instances[key]为组件的实例,这个属性是在routerview组件中beforecreated中被赋值的 return Object.keys(m.components).map(key => fn( m.components[key], m.instances[key], m, key )) }))}// 返回一个新数组export function flatten (arr) { return Array.prototype.concat.apply([], arr)}// 获取组件中的属性function extractGuard (def, key) { if (typeof def !== ‘function’) { def = _Vue.extend(def) } return def.options[key]}// 修正函数的this指向function bindGuard (guard, instance) { if (instance) { return function boundRouteGuard () { return guard.apply(instance, arguments) } }}第二步,获取全局VueRouter对象beforeEach的守卫第三步, 使用extractUpdateHooks函数,提取出update组件中所有的beforeRouteUpdate的守卫。过程同第一步类似。第四步, 获取activated的options配置中beforeEach守卫第五部, 获取所有的异步组件在获取所有的路由守卫后我们定义了一个迭代器iterator。接着我们使用runQueue遍历queue队列。将queue队列中每一个元素传入fn(迭代器iterator)中,在迭代器中会执行路由守卫,并且路由守卫中必须明确的调用next方法才会进入下一个管道,进入下一次迭代。迭代完成后,会执行runQueue的callback。在runQueue的callback中,我们获取激活组件内的beforeRouteEnter的守卫,并且将beforeRouteEnter守卫中next的回调存入postEnterCbs中,在导航被确认后遍历postEnterCbs执行next的回调。在queue队列执行完成后,confirmTransition函数会执行transitionTo传入的onComplete的回调。往下看????// queue为路由守卫的队列// fn为定义的迭代器export function runQueue (queue, fn, cb) { const step = index => { if (index >= queue.length) { cb() } else { if (queue[index]) { // 使用迭代器处理每一个钩子 // fn是迭代器 fn(queue[index], () => { step(index + 1) }) } else { step(index + 1) } } } step(0)}// 迭代器const iterator = (hook, next) => { if (this.pending !== route) { return abort() } try { // 传入路由守卫三个参数,分别分别对应to,from,next hook(route, current, (to: any) => { if (to === false || isError(to)) { // 如果next的参数为false this.ensureURL(true) abort(to) } else if ( // 如果next需要重定向到其他路由 typeof to === ‘string’ || (typeof to === ‘object’ && ( typeof to.path === ‘string’ || typeof to.name === ‘string’ )) ) { abort() if (typeof to === ‘object’ && to.replace) { this.replace(to) } else { this.push(to) } } else { // 进入下个管道 next(to) } }) } catch (e) { abort(e) }}runQueue( queue, iterator, () => { const postEnterCbs = [] const isValid = () => this.current === route // 获取所有激活组件内部的路由守卫beforeRouteEnter,组件内的beforeRouteEnter守卫,是无法获取this实例的 // 因为这时激活的组件还没有创建,但是我们可以通过传一个回调给next来访问组件实例。 // beforeRouteEnter (to, from, next) { // next(vm => { // // 通过 vm 访问组件实例 // }) // } const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid) // 获取全局的beforeResolve的路由守卫 const queue = enterGuards.concat(this.router.resolveHooks) // 再一次遍历queue runQueue(queue, iterator, () => { // 完成过渡 if (this.pending !== route) { return abort() } // 正在过渡的路由设置为null this.pending = null // onComplete(route) // 导航被确认后,我们执行beforeRouteEnter守卫中,next的回调 if (this.router.app) { this.router.app.$nextTick(() => { postEnterCbs.forEach(cb => { cb() }) }) } } )})// 获取组件中的beforeRouteEnter守卫function extractEnterGuards ( activated, cbs, isValid) { return extractGuards(activated, ‘beforeRouteEnter’, (guard, _, match, key) => { // 这里没有修改guard(守卫)中this的指向 return bindEnterGuard(guard, match, key, cbs, isValid) })}// 将beforeRouteEnter守卫中next的回调push到postEnterCbs中function bindEnterGuard ( guard, match, key, cbs, isValid) { // 这里的next参数是迭代器中传入的参数 return function routeEnterGuard (to, from, next) { return guard(to, from, cb => { // 执行迭代器中传入的next,进入下一个管道 next(cb) if (typeof cb === ‘function’) { // 我们将next的回调包装后保存到cbs中,next的回调会在导航被确认的时候执行回调 cbs.push(() => { poll(cb, match.instances, key, isValid) }) } }) }}在confirmTransition的onComplete回调中,我们调用updateRoute方法, 参数是导航的路由。在updateRoute中我们会更新当前的路由(history.current), 并执行cb(更新Vue实例上的_route属性,????这会触发RouterView的重新渲染)updateRoute (route: Route) { const prev = this.current this.current = route this.cb && this.cb(route) // 执行after的钩子 this.router.afterHooks.forEach(hook => { hook && hook(route, prev) })}接着我们执行transitionTo的回调函数onComplete。在回调中会调用replaceHash或者pushHash方法。它们会更新location的hash值。如果兼容historyAPI,会使用history.replaceState或者history.pushState。如果不兼容historyAPI会使用window.location.replace或者window.location.hash。而handleScroll方法则是会更新我们的滚动条的位置我们这里就不在细说了。// replaceHash方法(route) => { replaceHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route)}// push方法route => { pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route)}好了,现在我们就把,replace或者push方法的流程说完了。???????????????????????? 以下是transitionTo,confirmTransition中完整的代码。 ????????????????????????// onComplete 导航成功的回调// onAbort 导航终止的回调transitionTo (location, onComplete, onAbort) { const route = this.router.match(location, this.current) this.confirmTransition(route, () => { this.updateRoute(route) onComplete && onComplete(route) this.ensureURL() if (!this.ready) { this.ready = true this.readyCbs.forEach(cb => { cb(route) }) } }, err => { if (onAbort) { onAbort(err) } if (err && !this.ready) { this.ready = true this.readyErrorCbs.forEach(cb => { cb(err) }) } } )}// onComplete 导航成功的回调// onAbort 导航终止的回调confirmTransition (route: Route, onComplete: Function, onAbort?: Function) { // 当前的路由 const current = this.current const abort = err => { if (isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err) }) } } onAbort && onAbort(err) } // 判断是否导航到相同的路由,如果是我们终止导航 if ( isSameRoute(route, current) && route.matched.length === current.matched.length ) { this.ensureURL() return abort() } // 获取所有需要激活,更新,销毁的路由 const { updated, deactivated, activated } = resolveQueue(this.current.matched, route.matched) // 获取所有需要执行的路由守卫 const queue = [].concat( extractLeaveGuards(deactivated), this.router.beforeHooks, extractUpdateHooks(updated), activated.map(m => m.beforeEnter), resolveAsyncComponents(activated) ) this.pending = route // 定义迭代器 const iterator = (hook: NavigationGuard, next) => { if (this.pending !== route) { return abort() } try { hook(route, current, (to: any) => { if (to === false || isError(to)) { this.ensureURL(true) abort(to) } else if ( typeof to === ‘string’ || (typeof to === ‘object’ && ( typeof to.path === ‘string’ || typeof to.name === ‘string’ )) ) { abort() if (typeof to === ‘object’ && to.replace) { this.replace(to) } else { this.push(to) } } else { next(to) } }) } catch (e) { abort(e) } } // 迭代所有的路由守卫 runQueue( queue, iterator, () => { const postEnterCbs = [] const isValid = () => this.current === route const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid) const queue = enterGuards.concat(this.router.resolveHooks) runQueue(queue, iterator, () => { if (this.pending !== route) { return abort() } this.pending = null onComplete(route) if (this.router.app) { this.router.app.$nextTick(() => { postEnterCbs.forEach(cb => { cb() }) }) } } ) })}go, forward, back在VueRouter上定义的go,forward,back方法都是调用history的属性的go方法// index.jsgo (n) { this.history.go(n)}back () { this.go(-1)}forward () { this.go(1)}而hash上go方法调用的是history.go,它是如何更新RouteView的呢?答案是hash对象在setupListeners方法中添加了对popstate或者hashchange事件的监听。在事件的回调中会触发RoterView的更新// go方法调用history.gogo (n) { window.history.go(n)}setupListeners我们在通过点击后退, 前进按钮或者调用back, forward, go方法的时候。我们没有主动更新_app.route和current。我们该如何触发RouterView的更新呢?通过在window上监听popstate,或者hashchange事件。在事件的回调中,调用transitionTo方法完成对_route和current的更新。或者可以这样说,在使用push,replace方法的时候,hash的更新在_route更新的后面。而使用go, back时,hash的更新在_route更新的前面。setupListeners () { const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { setupScroll() } window.addEventListener(supportsPushState ? ‘popstate’ : ‘hashchange’, () => { const current = this.current if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { if (supportsScroll) { handleScroll(this.router, route, current, true) } if (!supportsPushState) { replaceHash(route.fullPath) } }) })}HistoryRouterHistoryRouter的实现基本于HashRouter一致。差异在于HistoryRouter不会做一些容错处理,不会判断当前环境是否支持historyAPI。默认监听popstate事件,默认使用histroyAPI。感兴趣的同学可以看/history/html5.js中关于HistoryRouter的定义。组件RouterViewRouterView是可以互相嵌套的,RouterView依赖了parent.$route属性,parent.$route即this._routerRoot.route。我们使用Vue.util.defineReactive将_router设置为响应式的。在transitionTo的回调中会更新_route, 这会触发RouteView的渲染。(渲染机制目前不是很了解,目前还没有看过Vue的源码,猛男落泪)。export default { name: ‘RouterView’, functional: true, // RouterView的name, 默认是default props: { name: { type: String, default: ‘default’ } }, render (, { props, children, parent, data }) { data.routerView = true // h为渲染函数 const h = parent.$createElement const name = props.name const route = parent.$route const cache = parent._routerViewCache || (parent._routerViewCache = {}) let depth = 0 let inactive = false // 使用while循环找到Vue的根节点, _routerRoot是Vue的根实例 // depth为当前的RouteView的深度,因为RouteView可以互相嵌套,depth可以帮组我们找到每一级RouteView需要渲染的组件 while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth++ } if (parent.inactive) { inactive = true } parent = parent.$parent } data.routerViewDepth = depth if (inactive) { return h(cache[name], data, children) } const matched = route.matched[depth] if (!matched) { cache[name] = null return h() } // 获取到渲染的组件 const component = cache[name] = matched.components[name] // registerRouteInstance会在beforeCreated中调用,又全局的Vue.mixin实现 // 在matched.instances上注册组件的实例, 这会帮助我们修正confirmTransition中执行路由守卫中内部的this的指向 data.registerRouteInstance = (vm, val) => { const current = matched.instances[name] if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val } } ;(data.hook || (data.hook = {})).prepatch = (, vnode) => { matched.instances[name] = vnode.componentInstance } let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]) if (propsToPass) { propsToPass = data.props = extend({}, propsToPass) const attrs = data.attrs = data.attrs || {} for (const key in propsToPass) { if (!component.props || !(key in component.props)) { attrs[key] = propsToPass[key] delete propsToPass[key] } } } // 渲染组件 return h(component, data, children) }}结语我们把VueRouter源码看完了。总体来说不是很复杂。总的来说就是使用Vue.util.defineReactive将实例的_route属性设置为响应式。而push, replace方法会主动更新属性_route。而go,back,或者点击前进后退的按钮则会在onhashchange或者onpopstate的回调中更新_route,而_route的更新会触发RoterView的重新渲染但是也略过了比如keep-live,滚动行为的处理。我打算接下来,结合VueRouter核心原理实现了一个简易版的VueRouter,当然现在还没有开始。其他从3月中下旬左右一直在学一些库的源码,本身学习源码对工作帮助并不是很大。因为像VueRouter,Preact都有着完善的文档。看源码单纯是个人的兴趣,不过学习了这些库的源码,自己实现一个简易版本,还是挺有成就感的一件事情。Preact源码分析简易的React的实现 ...

April 17, 2019 · 13 min · jiezi

vue、vue-router 知识梳理

vuevue生命周期Vue 实例从创建到销毁的过程,就是生命周期。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会,利用各个钩子来完成我们的业务代码,钩子如下:1.beforeCreate实例初始化之后、创建实例之前被调用,此时数据观察和事件配置都还没好准备好2.created实例创建完成后被调用,此时data有了,dom还没有,挂载属性el还没生成3.beforeMount将编译完成的html挂载到对应的虚拟dom时调用,此时相关的render函数首次被执行,页面还没有内容,表达式{{ }}的值还未被替换4.mounted编译好的html挂载到页面完成后调用,此时页面已经被渲染出数据5.beforeUpdate数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。6.update由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。7.beforeDestroy销毁前调用8.destroyed销毁后调用,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。???? 资料1:标注图+部分举例聊聊Vue生命周期???? 资料2:关于Vue实例的生命周期created和mounted的区别vue指令内置指令1.v-text:更新元素的 textContent2.v-html:更新元素的 innerHTML3.v-show:根据表达式之真假值,切换元素的 display CSS 属性4.v-if 、v-else-if 、v-else:条件判断5.v-for:基于源数据多次渲染元素或模板块6.v-on:(语法糖 @)绑定事件修饰符:.stop - 阻止事件冒泡,调用 event.stopPropagation().prevent - 阻止默认行为,调用 event.preventDefault().capture - 添加事件侦听器时使用 capture 事件捕获模式.self - 元素本身触发时才触发回调.once - 只调用一次该事件7.v-bind(语法糖 :)当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM8.v-model:表单元素实现双向数据绑定修饰符:.trim - 去除两边空格.number - 输入内容转换为number类型.lazy - 当焦点离开文本框时,属性值发生了变化并与文本框内容保持一致???? 资料1:Vue.js入门教程-指令自定义指令通过directive就可以在Vue上注册一个自定义指令(全局注册、局部注册)???? 举例注册一个自定义指令,实现页面加载自动聚焦到元素// 1. 注册一个全局自定义指令 v-focusVue.directive(‘focus’, { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() }})// 2. 注册一个局部自定义指令 v-focusdirectives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } }}// 使用<input v-focus>???? 资料1:【译】vue 自定义指令的魅力???? 资料2:vue 自定义指令filter过滤器过滤器(全局过滤器,局部过滤器)可以通过管道符号|实现文本数据的格式化,可以用在表达式 {{}}和bind中// 全局过滤器Vue.filter(’toTime’, function(value) { //value 表示要过滤的内容})// 批量注册全局过滤器import * as filters from “config/filters"Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]);});// 局部过滤器var app = new Vue({ el: ‘#app’, data: {}, filters: { toTime: function(value){ //value 表示要过滤的内容 } }})// 使用<div :data-time=“date | toTime”>{{ date | toTime }}</div>过滤器可以串联、接收参数,比如:{{ message | filterA | filterB }}{{ message | filterA(‘p1’, ‘p2’) }}computed 计算属性使用方法:<p>我的名字:{{ fullname }}</p>var vm = new Vue({ el: ‘#app’, data: { firstname: ‘jack’, lastname: ‘rose’ }, computed: { fullname() { return this.firstname + ‘.’ + this.lastname } }})注意:computed中的属性不能与data中的属性同名,否则会报错。Q: {{ }} 、computed、methods里面的方法分别在什么情况下使用 ?{{}}常用于简单的运算,当过长或逻辑复杂时会变得难以维护。computed常用语复杂逻辑运算,基于依赖缓存。当计算属性所依赖的数据发生变化时,才会重新取值,当遍历大数组或做大量计算时使用计算属性比较好methods里的方法只要重新渲染就会被调用,函数也会被执行。watch 监测数据监测属性watch可以通过监测数据来响应数据的变化,可在数据变化时需要执行异步或开销较大的操作时使用。它是一个对象,键是需要观察的表达式,值是对应回调函数,回调函数接收两个参数分别是新值和旧值。⚠️ 注意:watcher回调函数不可用箭头函数,箭头函数绑定了父级作用域的上下文,会改变this指向。new Vue({ data: { a: 1, b: { age: 10 } }, watch: { a: function(newVal, oldVal) { //如果a发生改变,这个函数就会运行 }, /** * 监听对象变化 * deep - 深度监测,对象内部属性或方法发生改变,使用它才能监测到该对象的改变 * immediate - 立即执行,初始化的时候 watch 是不会执行的,如果想在初始化的时候就执行,则需开启这个属性 / b: { handler: function (val, oldVal) { //TODO … }, deep: true, immediate: true }, /* * 监听对象具体某个属性的变化 / ‘b.age’: function (val, oldVal) { //TODO … }, }})Q: watch在任何时候都可以监测的到数据变动吗?数组 - 直接用索引设置元素,如 vm.items[0] = {}数组 - 修改数据的长度,如 vm.items.length = 0对象 - 不能检测对象某个(在data中不存在)属性的添加或删除以上三种情况下均数据发生改变时,watch都不能检测出来,解决办法如下:对于新增或删除等操作使用vue提供的方法,如$set()、$delete()等克隆,如Object.assign()、JSON.parse(JSON.stringify(obj))、{…obj}、[…arr]等???? 资料1:Vue watch选项???? 资料2:Vue的computed和watch的细节全面分析???? 资料3:Vue中$watch的监听变化 & vue中正确的使用watch进行监听组件注册全局注册方法:使用Vue.component(tagName, options)可以注册一个全局的组件全局组件在所有的vue实例中都可以使用let myButton = Vue.extend({ template: &lt;button&gt;点击我&lt;/button&gt;, // 组件中的 data 必须是函数 并且函数的返回值必须是对象 data() { return {} }}) Vue.component(‘my-button’, myButton) 语法糖:Vue.component(‘my-button’, { template: &lt;button&gt;点击我&lt;/button&gt;})使用:<div id=“app”> <my-button /> </div>局部注册方法:使用Vue实例 / 组件实例选项中components注册局部注册的组件只可以应用在当前实例中。let myButton = Vue.extend({ template: &lt;button&gt;点击我&lt;/button&gt; })let app = new Vue({ el: ‘#app’, components: { ‘my-button’:myButton }}) 语法糖:let app = new Vue({ el: ‘#app’, components: { ‘my-button’: { template: &lt;button&gt;点击我&lt;/button&gt; } }})使用:<div id=“app”> <my-button /> </div>???? 资料:Vue.js入门教程-组件注册组件通信props:父 -> 子1. 子组件可以通过props声明从父组件接收过来的数据,props值分两种,一种是字符串数组,一种是对象 (需要对值验证可以用对象)。2. 由于html特性不区分大小写,当使用dom模板时,驼峰命名的props名称要转为短横线分隔命名(字符串模板除外)3. 如果传递的数据不是直接写死的,而是来自父级的动态数据,可以使用指令v-bind来动态绑定 props的值,当父组件的数据变化时,也会传递给子组件 ,反之不行4. 业务中经常会遇到两种需要改变props的情况:传递初始值进来,子组件将它作为初始值保存到data中,在自己作用域下可以随意使用和修改prop作为需要被转变的原始值传入,这种情况下可以使用计算属性但是,以上两种情况,针对于引入类型的数据,在子组件中修改值是会影响到父组件的值的,需要注意!例子:基于vue-cli// 父组件 - 传递 msg1 的值到子组件<template> <div class=“parent-box”> 我是父组件 <child :msg1=“msg1”></child> </div></template><script>import child from “./components/child”; export default { name: “app”, components: { child }, data() { return { msg1: “我是msg1 - 父组件传给子组件的第1个值”, }; }};</script>//子组件child - 接收来自父组件的 msg1 的值<template> <div class=“child-box”> 我是子组件 {{msg1}} </div></template><script>export default { name: “child”, props: [“msg1”], //props 接收来自父组件传递过来的值};</script>$emit:子 -> 父当子组件需要像父组件传递数据时,就要用到自定义事件。类似观察者模式,子组件用$emit来触发事件,父组件用v-on(语法糖@)来监听子组件触发的自定义事件。用法: step1:父组件 - 定义一个方法,并通过v-on(语法糖@)绑定给子组件,如:<child :msg1=“msg1” @changeMsg1=“changeMsg1”></child>step2:子组件 - 通过 $emit 触发已绑定的方法,如://参数1 eventName - 方法名//参数2 […arg] - 要返给被触发方法的参数this.$emit(“changeMsg1”, ‘传给父组件的参数’); 例子://父组件 - 传递给子组件一个函数 changeMsg1 <template> <div class=“parent-box”>我是父组件 <child :msg1=“msg1” @changeMsg1=“changeMsg1”></child> </div></template><script>import child from “./components/child”;export default { name: “app”, components: { child }, data() { return { msg1: “我是msg1 - 父组件传给子组件的第1个值”, }; }, methods: { //参数 data 是子组件传递过来的参数 changeMsg1(data) { this.msg1 = data; } }};</script>//子组件child - 给 button 绑定 handleClick 方法,在 handleClick 中通过 $emit 去触发changeMsg1方法,并传参过去<template> <div class=“child-box”> {{msg1}} <button @click=“handleClick”>btn</button> </div></template><script>export default { name: “child”, props: [“msg1”], methods: { handleClick() { this.$emit(“changeMsg1”, ‘传给父组件的参数’); } }};</script>$parent & $children、$refsthis.$parent 可以直接访问该组件父实例或组件(子 -> 父),父组件也可以通过this.$children访问所有子组件(数组)(父 -> 子),而且可以递归向上或向下无线访问,直到根实例或最内层组件。业务中子组件应该应该尽可能避免依赖父组件数据,更不该主动修改父组件数据,这样会使父子组件耦合严重,只看父组件很难理解父组件状态,因为父组件的值可能被随意修改了。这种方式适用于一个页面单纯的拆分成多个组件组合在一起的情况,被拆分的组件只有一个公用且确定了的父组件。例子://父组件 - 通过 $children 改变子组件 tip 值<template> <div class=“parent-box”> 我是父组件 <child :msg=“msg”></child> <button @click=“handleClick”>父btn</button> </div></template><script>import child from “./components/child”;export default { name: “app”, components: { child }, data() { return { msg: “我是父组件 msg 的值” }; }, methods: { handleClick() { this.$children.map(i => { i.tip = “我是父组件,我用 $children 改变了子组件 child 中 tip的值”; }); } }};</script>//子组件 - 通过 $parent 改变父组件 msg 值<template> <div class=“child-box”> 我是子组件 {{tip}} {{msg}} <button @click=“handleClick”>子btn</button> </div></template><script>export default { name: “child”, props: [“msg”], data() { return { tip: “我是子组件的数据,谁能改变我?” }; }, methods: { handleClick() { this.$parent.msg = “我是子组件,我用 $parent 改变了父组件中 msg 的值”; } }};</script>当子组件很多时,很难通过$children遍历出来需要的组件实例,可以用ref来为子组件指定一个索引名称,然后用this.$refs[name]来查找例子://父组件 - 用 ref 给子组件 child 设置索引名,通过 refs 去查找子组件<template> <div class=“parent-box”> 我是父组件 <child ref=“component1” :msg=“msg”></child> <button @click=“handleClick”>父btn</button> </div></template><script>import child from “./components/child”;export default { name: “app”, components: { child }, data() { return { msg: “我是父组件 msg 的值” }; }, methods: { handleClick() { this.$refs.component1.tip = ‘我是父组件,我要通过 $refs 来查找子组件 child 并修改 它的 tip 值’ } }};</script>EventBus:非父子组件通讯如果涉及到爷孙之间、兄弟之间或更深层次组件之间需要通信,可以声明一个vue实例,把公共方法和属性放在这个实例上面,让这个实例充当一条通讯桥梁。范围:适用于简单场景,复杂场景请用 vuex步骤:基于vue-clistep1:创建一个vue实例,挂载到全局,比如://main.js 文件Vue.prototype.$bus = new Vue();//或window.$bus = new Vue();step2:用$on和$emit完成通信// $on 监听事件,处理回调bus.$on(’eventName’, val => { //TODO…})// $emit 触发事件,返回参数bus.$emit(’eventName’, val)例子:让两个同级组件通讯//main.js - 声明一个全局变量来保存 vue 实例import Vue from ‘vue’import App from ‘./App.vue’Vue.config.productionTip = falseVue.prototype.$bus = new Vue(); // $busnew Vue({ render: h => h(App),}).$mount(’#app’)// 根组件 - 包含两个子组件<template> <div class=“parent-box”> 我是父组件 <child></child> <child2></child2> </div></template><script>import child from “./components/child”;import child2 from “./components/child2”;export default { name: “app”, components: { child, child2 }};</script>//child 子组件 - $on 监听事件,如果有新参数进来立即替换 childMsg 值<template> <div class=“child-box”> 我是子组件 child {{childMsg}} </div></template><script>export default { name: “child”, data() { return { childMsg: “我是child的数据” }; }, created() { //$on 监听 this.$bus.$on(‘changeMsg’, data => { this.childMsg = data; }) },};</script>//child2 - $emit 触发事件,并传递参数到 changeMsg 方法中<template> <div class=“child-box”> 我是子组件 child2 <button @click=“handleClick”>child2 btn</button> </div></template><script>export default { name: “child2”, methods: { handleClick() { //$emit 触发 this.$bus.$emit(‘changeMsg’, ‘我是 child2 ,我更改了 child 的 childMsg 值’) } }};</script>v-model:双向数据绑定 - 单个属性v-model可以在 表单控件 或者组件上创建双向绑定。表单控价上的双向绑定,如:<input v-model=“msg”>//相当于:<input :value=“msg” @input=“msg = $event.target.value”>如上,可以看出语法糖v-model的实现过程:将input标签用v-bind绑定value属性,v-on绑定input事件。当输入框的文本发生改变时,自动将属性value的值替换成目标输入值。那么,换到组件中该怎么实现呢?父组件:通过v-model绑定变量<child v-model=“msg”></child>//相当于<child :value=“msg” @input=“msg = arguments[0]"></child>子组件:接收一个value属性在有新的value时触发input事件举个栗子:// 父组件 - 用 v-model 绑定 msg<template> <div class=“parent-box”> 我是父组件 这是我的 msg 值:{{msg}} <child v-model=“msg”></child> </div></template><script>import child from “./components/child”;export default { name: “app”, components: { child }, data() { return { msg: “我是父组件的 msg” }; }};</script>//子组件 - 通过 props 接收 value,用 $emit 触发 input 事件<template> <div class=“child-box”> 我是子组件 child <button @click=“handleClick”>child’s btn</button> </div></template><script>export default { name: “child”, props: [“value”], methods: { handleClick() { this.$emit(“input”, “我是 child,这是我传给父组件的新值”); } }};</script>上例中,如果属性value被占用了,或者input事件冲突了,就会引起报错,所以最好用 model 属性来定制 v-model定制方法:model { prop?: string, event?: string }上例中,child组件重写为://child - 通过 model 定制 v-model<template> <div class=“child-box”> 我是子组件 child <button @click=“handleClick”>child’s btn</button> </div></template><script>export default { name: “child”, model: { prop: ‘msg’, //代替 value event: ‘changeMsg’ //代替input方法 }, props: [“msg”], methods: { handleClick() { this.$emit(“changeMsg”, “我是 child,这是我传给父组件的新值”); } }};</script>.sync:双向数据绑定 - 多个属性2.3版本中重新引入的.sync 修饰符作为语法糖存在,它会被扩展为自动更新父组件属性的v-on监听器,如下:<child :msg.sync=“msg”></child>//相当于<child :msg=“msg” @update:msg=“val => msg = val”></child>当子组件需要更新msg时,触发update方法,将旧值替换成新值,如下:this.$emit(‘update:name’, newVal)一次性想传递多个属性时,可以结合v-bind一起使用,如下://父组件 - 传递一个对象到子组件,会分别为对象的每个属性分配一个 v-on 监听器<template> <div class=“parent-box”> 我是父组件 {{info.msg1}} {{info.msg2}} <child v-bind.sync=“info”></child> </div></template><script>import child from “./components/child”;export default { name: “app”, components: { child }, data() { return { info: { msg1: 1111, msg2: 2222 } }; }};</script>//子组件 - $emit 触发事件<template> <div class=“child-box”> 我是子组件 child <button @click=“handleClick”>child’s btn</button> </div></template><script>export default { name: “child”, methods: { handleClick() { this.$emit(“update:msg1”, “33333”); this.$emit(“update:msg2”, “44444”); } }};</script>$attrs & $listeners1.在多级组件通信中,$attrs & $listeners 分别负责收集父组件中传递过来的属性和事件,其中$attr中收集的属性不包括组件中已通过props接受的属性,$listeners中收集的事件不包括有.native修饰符的事件。属性通过v-bind="$attrs"一级级向下传递, 事件通过v-on="$listeners"一级级向下传递。2.$attrs中包含的属性,默认情况下将会作为普通的HTML属性应用在子组件的根元素上,可以通过在当前组件中设置inheritAttrs: false 去掉(style, class除外),去掉默认行为不影响数据的使用。例如:未设置 inheritAttrs: false时:<div class=“grandson-box” msg3=“333” msg4=“444”></div>设置了 inheritAttrs: false时:<div class=“grandson-box”></div>3.适用范围:简单的多级组件通信例子:// 一级组件 - 通过 v-bind, v-on 传递属性和事件到下级组件<template> <div class=“parent-box”> 我是爷爷 <son v-bind=“msg” @event1=“event1” @event2.native=“event2” @event3=“event3” @event4=“event4”></son> </div></template><script>import son from “./components/son”;export default { name: “app”, components: { son }, data() { return { msg: { msg1: 111, msg2: 222, msg3: 333, msg4: 444 } }; }, methods: { event1() { console.log(1); }, event2() { console.log(2); }, event3(data) { console.log(3, data); //3, 我是孙子组件grandson传递到爷爷那去的参数 }, event4() { console.log(4); } }};</script>// 二级组件 - 通过 v-bind="$attrs” v-on="$listeners” 传递属性和事件到下一级组件中<template> <div class=“son-box”> 我是儿子 {{$attrs}} <grandson v-bind="$attrs" v-on="$listeners"/> </div></template><script>import grandson from “./grandson”;export default { name: “son”, inheritAttrs: false, //组件根部屏蔽掉 $attrs 的属性,但是值仍然存在只是不展示在 html 上了 components: { grandson }, props: [“msg1”, “msg2”], //通过 props 接收的属性 会被过滤,不存在 $attrs 中 created() { console.log("—– son ——-"); //如果上级组件传递过来的事件含有 .native 修饰符,则该事件被过滤, 不存在 $listeners 中 console.log(this.$listeners); //{event1: ƒ, event3: ƒ, event4: ƒ} }};</script>// 三级组件<template> <div class=“grandson-box”> 我是孙子 {{$attrs.msg3}} </div></template><script>export default { name: “grandson”, created() { console.log("—— grandson ——"); console.log(this.$listeners); //{event1: ƒ, event3: ƒ, event4: ƒ} this.$listeners.event3(“我是孙子组件grandson传递到爷爷那去的参数”); }};</script>???? 资料一:vue组件的那点事???? 资料二:vue组件通信全揭秘(共7章)???? 资料三:Vue.js 父子组件通信的十种方式===================== 分割线 ====================vue-routervue-router的使用流程(基于vue-cli):step1: 安装并注册vue-routerstep2: 定义路由组件step3: 配置路由step4: 创建router实例,将配置好的路由传入step5: 创建并挂载根实例,注入路由step1: 安装并注册vue-router安装yarn add vue-router -S注册1.引入vue 及 vue-routerimport Vue from ‘vue’import VueRouter from ‘vue-router'2.注册Vue.use(VueRouter)step2: 定义路由组件基于vue-cli,我们在写好页面组件之后,通常会通过import或require引入到管理路由的文件中,普通引入方式如下:import login from ‘../page/login’;//或者const login = require(’../page/login’).default;注意:用 require 引入组件时需要加 default,否则报错!扩展:exports、module.exports 和 export、export default 到底是咋回事这种普通的引入方式有一个缺点,就是在npm run build的时候,所有引入的文件最终会被打包成一个文件,加载的时候会加载整个文件,文件过大会导致页面加载缓慢,为了解决这个问题,我们通常在项目中将它分成多个小的代码块来加载,通常用以下三种方法:第一种:异步组件当需要这个组件的时候,会异步加载过来,并将结果储存以供下次使用。const login = resolve => require([’../page/login’], resolve)第二种:懒加载组件结合Vue的异步组件和Webpack的代码分割功能,实现路由组件的懒加载。const login = () => import(’../page/login’);注意:如果使用 Babel,需要添加 syntax-dynamic-import 插件如果想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。可以通过命名chunk来实现,如下login 和 index 都被打包到了base文件中:const login = () => import(/ webpackChunkName: “base” / ‘../page/login’);const index = () => import(/ webpackChunkName: “base” / ‘../page/index’);const list = () => import( / webpackChunkName: “list” / ‘../page/list’);第三种:webpack 代码分割webpack打包时,会把其对应的文件拆分到一个单独的chunk中,此chunk会在需要的时候被异步加载。//最后一个参数 ’login’ 可以指定打包出的 chunk 名称const login = r => require.ensure([], () => r(require(’../page/login’)), ’login’);???? 资料:vue项目实现按需加载的3种方式:vue异步组件、es提案的import()、webpack的require.ensure()step3: 配置路由举个例子:const routes = [ { path: ‘/’, redirect: ‘/index’ }, { path: ‘/login’, component: login, name: ’login’, meta: { windowTitle: ‘登录’ } }, { path: ‘/index’, component: index, name: ‘index’, meta: { windowTitle: ‘首页’ } }, { path: ‘/list/:id’, component: list, name: ’list’, meta: { windowTitle: ‘列表’}, props: true}]routes 常用配置:path格式:path: string 概述:当前页面的路径 项目中经常会遇到这种情况,比如有一个 产品列表 list 组件,对于所有id各不相同的产品,都要使用这个组件来渲染,可以通过配置 动态路径参数 来匹配这种路径,例如:path:’/list/:id’。component格式:component?: Component概述:路由视图,通过懒加载等方式引入的组件(见 step2 内容)name格式:name?: string概述:命名路由,可通过 name 名字跳转页面,两边 name 要保持一致,跳转方式如:<router-link :to="{ name: ‘user’, params: { userId: 123 }}">User</router-link>//或router.push({ name: ‘user’, params: { userId: 123 }})props:格式: boolean | Object | Function概述:组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。路径上的参数可通过配置props: true [将组件和路由解耦][27],不必再通过 $route.params 去访问参数例如:{ path: ‘/user/:id’, component: User, props: true },//User - 设置了props:true 之后,route.params 将会被设置为组件属性。const User = { props: [‘id’], template: ‘<div>User {{ id }}</div>’, //template: ‘<div>User {{ $route.params.id }}</div>’ //不需要这样获取值了}children格式:Array<RouteConfig>概述:嵌套路由step4: 创建router实例,将配置好的路由传入常用配置如下:const router = new VueRouter({ //页面配置(见step3) routes, /* * mode - 模式,默认 hash * hash,地址会变成有 # 号的那种,很丑 * history,无 # 号的正常地址 */ mode: ‘history’, strict: process.env.NODE_ENV !== “production”, //滚动位置 scrollBehavior(to, from, savedPosition) { if (savedPosition) { //savedPosition - 在按下 后退/前进 按钮时,就会像浏览器的原生表现那样 return savedPosition; } else { //返回到顶部 return { x: 0, y: 0 }; } }});step5: 创建并挂载根实例,注入路由import router from ‘./router’ //引入路由new Vue({ router, //注册 render: h => h(App),}).$mount(’#app’)完整示例:router.js文件配置//step1 - 安装并注册vue-routerimport Vue from ‘vue’import VueRouter from ‘vue-router’Vue.use(VueRouter)//step2: 定义路由组件const login = r => require.ensure([], () => r(require(’../page/login’)), ’login’); //登录const index = r => require.ensure([], () => r(require(’../page/index’)), ‘index’); //首页const list = r => require.ensure([], () => r(require(’../page/list’)), ’list’); //首页//step3: 配置路由const routes = [ { path: ‘/’, redirect: ‘/index’ }, { path: ‘/login’, component: login, name: ’login’, meta: { windowTitle: ‘登录’ } }, { path: ‘/index’, component: index, name: ‘index’, meta: { windowTitle: ‘首页’ } }, { path: ‘/list/:id’, component: list, name: ’list’, meta: { windowTitle: ‘列表’}, props: true },]//step4: 创建router实例,将配置好的路由传入const router = new VueRouter({ routes, mode: ‘hash’, //模式 strict: process.env.NODE_ENV !== “production”, //滚动位置 scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition; } else { return { x: 0, y: 0 }; } }});//路由守卫router.beforeEach((to, from, next) => { const token = localStorage.token || ‘’; const userArr = localStorage.userArr ? JSON.parse(localStorage.userArr) : null; if (to.meta.windowTitle) document.title = to.meta.windowTitle; if (!token && to.name !== ’login’) { next({name: ’login’, replace: true}); //没有token进入登录页面 } else if (token && userArr && to.name == ’login’) { next({name: ‘index’, replace: true}); //有token进入登录页面跳转首页 } else if (to.name != ’login’ && !userArr) { next({name: ’login’, replace: true}); } else { //TODO 如果没有登录信息 - 保存登录信息 // if (!store.state.userArr) { // store.commit(‘SAVE_USERARR’, userArr); // } next(); }})export default router;main.js文件配置import Vue from ‘vue’import App from ‘./App’import router from ‘./router’ //引入路由Vue.config.productionTip = false//step5: 创建并挂载根实例,注入路由new Vue({ router, render: h => h(App),}).$mount(’#app’)???? 资料1:Vue2.0 探索之路——vue-router入门教程和总结???? 资料2:vue-router 一些容易被忽略的知识点???? 资料3:关于vue-router的beforeEach无限循环的问题???? 资料4:Vue的钩子函数[路由导航守卫、keep-alive、生命周期钩子 ...

April 17, 2019 · 8 min · jiezi

如何实现vue中不跳转不闪动页面刷新?provide /inject 完美解决方案

在vue的项目中刷新功能你会怎么写?我之前一直用this.$router.go(0)这个方法用户体验很不好,因为页面会闪动一下刷新直到我发现了这个方法 provide /inject 不过这个方法貌似有兼容问题,首先要确定一下你的vue版本,此方法适用vue 2.20+原理:此方法使用的是v-if来控制router-view的显示或隐藏,v-if从false变为true时,vue会重新渲染router-view区域,所以当参数变化时,只需让v-if 从true => false => true,就能实现页面刷新首先,找到自己的route-view//App.vue <template> <div id=“app”> <router-view v-if=“isRouterAlive”/> </div> </template> <script> export default { name: ‘App’, provide() { return { reload: this.reload//调用reload方法 } }, data() { return { isRouterAlive: true//一开始router-view为true } }, methods: { reload() { this.isRouterAlive = false //在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM this.$nextTick(() => { this.isRouterAlive = true }) } } } </script> 然后在<route-view>下显示的vue页面中进行如下操作 export default { name: ’newproduct’, inject:[‘reload’],//在export default下面加上这一段 method:{ //调用App.vue下的this.reload()方法,来改变v-if的状态 clickDiv(){//刷新按钮调用的方法 this.reload() } } 就ok了加油!每天进步一点点! ...

April 15, 2019 · 1 min · jiezi

vue单页面在微信下只能分享落地页的解决方案

实际上关键词叫微信pushState只能分享落地页更贴切一点应用场景:vue + vue-routervue-router使用hash模式(history模式没试过)不使用微信的js-sdk(因为我这个项目是可配置域名的商城,比较特殊,不能使用微信sdk)这个方案并不是最优秀的,会对性能造成一定的影响HTML5 history.pushStatevue-router的内部是通过history.pushState和history.replaceState实现的。但是iOS设备的微信浏览器不会去检测它们的变化。但是我们可以通过更新location.href让微信浏览器识别到当前的url。// vue-router/src/util/push-state.jsexport function pushState (url?: string, replace?: boolean) { saveScrollPosition() // try…catch the pushState call to get around Safari // DOM Exception 18 where it limits to 100 pushState calls const history = window.history try { if (replace) { history.replaceState({ key: _key }, ‘’, url) } else { _key = genKey() history.pushState({ key: _key }, ‘’, url) } } catch (e) { window.locationreplace ? ‘replace’ : ‘assign’ }}export function replaceState (url?: string) { pushState(url, true)}解决方法window.location.href = window.location.href,这段代码可以让微信记录当前的url,且不会刷新页面。可以在app.vue中watch $route在每次页面更新的时候执行一次。// app.vuewatch: { $route: { immediate: true, deep: true, handler(to) { // 微信浏览器判断 const WECHAT_BROWSER = navigator.userAgent.toLowerCase().includes(‘micromessenger’) // 解决iOS微信浏览器分享地址只能是落地页的问题,这个操作是不会刷新页面的,query参数改变也会执行 if (WECHAT_BROWSER) { // eslint-disable-next-line window.location.href = window.location.href } }},使用了上述方法可以解决这个问题,但是这会引出一个很奇葩的问题,在真机上进入http://192.168.1.5:8080和http://192.168.1.5:8080/#/这两个页面,其中有一个链接的bug依然存在。原因具体不清楚,经过测试可以在入口文件(main.js)中在页面还没有展示内容前刷新一次页面,即可解决这个问题。// main.js// 微信浏览器判断const WECHAT_BROWSER = navigator.userAgent.toLowerCase().includes(‘micromessenger’)// 在url插入的search参数,可以随意,但是必须要// 例:http://192.168.1.5:8080/?wx=1#/const wxQuery = ‘wx=1’const isRepeatQuery = location.search.includes(wxQuery)if (WECHAT_BROWSER && !isRepeatQuery) { const unit = (location.search && location.search !== ‘?’) ? ‘&’ : ‘?’ location.search += unit + wxQuery // 添加_wx_参数,该操作会刷新页面}上面的代码之所以要在hash前面加一个?wx=1参数,为了方便刷新页面给一个标志位判断是否已刷新。参数的key-value随意。 ...

April 15, 2019 · 1 min · jiezi

vue-router中$route与$router,path与name,params与query的区别梳理

一、$router和$route的区别$router : 是路由操作对象,只写对象$route : 路由信息对象,只读对象栗子://$router操作 路由跳转this.$router.push({ name:‘hello’, params:{ name:‘word’, age:‘11’ }})//$route读取 路由参数接收var name = this.$route.params.name;二、路由跳转方式name 、 path 和传参方式params 、query的区别*path 和 Name路由跳转方式,都可以用query传参栗子://Router.js{path: ‘/hello’,name: ‘HelloWorld’,component: helloPage}跳转方式namethis.$router.push({name: ‘HelloWorld’,query: {id: 12345}})跳转方式paththis.$router.push({path: ‘/hello’,query: {id: 12345}})//获取路由参数信息方式:{{$route.query.id}*path路由跳转方式,params传参会被忽略,只能用name命名的方式跳转注意:params传参如果路由上面不写参数,也是可以传过去的,但不会在url上面显示出你的参数,并且当你跳到别的页面或者刷新页面的时候参数会丢失,要怎么解决?解决:一、传参字符串name小的时候,可以在路由后面加参数名/router1/:name 二、name大的时候用sessionStorage;(欢迎补充)注意:如果路由为动态路由{path: ‘/hello/:id’,name:‘hello’}路由跳转执行this.$router.push({name: ‘hello’,params: obj});obj里面只要有id属性,就会自动带到URL里面

April 9, 2019 · 1 min · jiezi

Vue 路由知识点归纳总结

最近做项目才发现,我确实对 vue-router 太不熟悉了,都只了解个简单用法就开始搞了,本来很简单的问题,都搞不清楚。现在重新看一遍文档,重新梳理一下。vue 路由的原理说实话,路由我一直也就光顾着用,没认真思考过这个问题,还是那次人家面试问了这个,我才反应过来是应该好好的了解一下了。 无刷新跳转页面,是单页应用的一大优势,用户体验好,加载速度快,vue 路由的跳转就是无刷新的,它有两种形式,可以在定义路由的时候通过 mode 字段去配置,如果不配置这个字段,那么默认使用的就是 hash 模式。hash 模式,即通过在链接后添加 # + 路由名字,根据匹配这个字段的变化,触发 hashchange 事件,动态的渲染出页面。就有点类似像 a 链接用作页面上的锚点一样,不会刷新页面。 另外一种方式,是 history 模式,也就是使用的浏览器的 history API,pushState 和 replaceState。通过调用 pushState 操作浏览器的 history 对象,改变当前地址,同时结合window.onpopstate 监听浏览器的返回和前进事件,同样可以实现无刷新的跳转页面。replaceState 与 pushStete 不同的就是,前者是替换一条记录,后者是添加一条记录。 有关于 pushState 的用法,详见MDN 文档。这个 API 与 ajax 合起来构成的 pjax 技术,也是用于实现页面无刷新加载的一种方式,常用于 PC 长列表页面的翻页啥的~history 相对于 hash 模式来说,最大的好处就是没有讨厌的 # 符号,比如同样一个 list 页面,在 hash 模式下,url 链接表现为 http://yoursite.com/#/list,看着就难受。在 history 模式下面,url 链接表现为 http://yoursite.com/list ,看着比较清爽~而且~相信做过微信公众号开发的都懂,这个该死的 # 有多烦人~在下面的应用场景里面我再讲下这个问题~那么问题来了,既然 history 模式样子好看,功能也一样,为啥还是用 hash 模式的人比较多【此处没有真凭实据,我瞎说的】?因为 history 模式,还需要服务端进行配置,否则刷新页面就会产生 404 错误。这里也比较好理解啦,因为我们实际上是使用的 pushState 操作页面的跳转,而不是真的去服务器请求另外一个 list.html 文件,那按照 http://yoursite.com/#/list 这个路径,自然找不到啦~如果是 nginx 的服务器,在 location / 里面加上 try_files $uri $uri/ /index.html; 即可。这行代码表示:如果匹配不到静态资源的路径,就将重定向到 index 页面,这样就不会出错啦~因为需要找后端小哥哥修改服务器配置文件,如果自己没有完全理解,两边又沟通不清楚的情况下,使用 history 模式的难度无疑就大了一些~不过也不是什么大问题~全看个人需要啦哈哈~vue 路由传参的两种方式页面参数无非就两种,query 和 params,params 是以 /params 的形式表现在 url 上,而 query 是以 ?query=query1 这种形式表现在 url 上,此外,使用 params 参数还需要配置到路由定义上,不然不会展示在 url 上,并且刷新就会消失。这个比较简单,需要注意的地方就是:如果传 params 参数,不能使用 path 字段跳转,否则没效果。而 query 参数则没有这个限制,使用 name 和 path 字段都可以。这个虽然简单!但是一定要自己操作一遍才记得住啊。。反正我是早就看到,但是一直记混了~~重新用 demo 写了一遍才记住~而且别人说的也不一定就是对的,还是要自己实验一遍才知道呢。╮(╯▽╰)╭vue 路由的跳转vue 路由的跳转分成两种,一种是声明式,使用<router-link>声明跳转,to属性定义跳转的参数。另一种是编程式,使用 router.go()、router.push()、router.replace()方法进行跳转,go方法就是与浏览器的history api 的方法相同,可以进行返回上一页等操作。push方法和replace方法的区别在于,前者会把当前页面加入 history 记录里面,可以通过history.go(-1)回到这个页面。而replace方法则会在 history 记录里面替换掉当前记录,如果你在跳转后的新页面返回上一页,它不会回到跳转前的页面,会回到上上个页面,如果上上个页面没有记录,则不会跳转。vue 路由守卫vue 路由守卫分为三种,一种是全局的路由守卫,通常在实例化路由之后设置,来做一些通用的路由操作,它所有的路由跳转都会执行的操作;一种是单个路由独享的守卫,在单个路由定义的时候进行设置,所有跳转这个路由都会执行;另外一种就是组件内的守卫,只在组件内生效。全局路由守卫类型:router.beforeEach(to, from, next)router.afterEach(to, from, next)路由独享的守卫:beforeEnter(to, from, next)组件独享的守卫:beforeRouteEnter(to, from, next)beforeRouteUpdate(to, from, next) —— 动态参数路径改变时,组件实例被复用的时候调用。beforeRouteLeave(to, from, next) —— 导航离开组件所在路由时被调用。vue 路由的一些应用场景处理什么时候用 push,什么时候用 replace最开始路由跳转我都是用 push() 比较多,或者用 go() 做返回,很少用到过 replace() ,后来在业务需求下翻文档才发现这个漏掉的 API。简单来说,当你需要从A页面跳转到B页面,再跳转到C页面,然后在C页面返回,能直接返回到A页面。那么在B页面跳转C页面的时候,使用replace()方法进行跳转即可。动态改变页面的 title定义路由的时候,在路由的 meta 字段里面添加一个 title 属性,定义好页面的标题名称。在全局的路由守卫beforeEach()方法里面添加一个判断,获取路由的 meta 字段,动态的改变页面的 title。// router.js{ path: ‘/index’, name: ‘index’, meta: { title: ‘首页’ }}// main.jsrouter.beforeEach(to, from, next){ if(to.meta.title){ document.title = to.meta.title } next() // 这个方法必须调用 不然路由不会跳转}微信开发,单页应用页面 url 导致的传参或者跳转失败的问题微信授权跳转在做微信授权跳转的时候,hash 模式下链接里面带有 # 号可能会导致重定向跳转失败,使用 encodeURIComponent 把页面地址处理之后,再传入。let _url = encodeURIComponent(location.href)location.href = https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appid}&amp;redirect_uri=${_url}&amp;response_type=code&amp;scope=snsapi_base&amp;state=#wechat_redirect获取 wxconfig 配置前端获取 wxconfig 比较简单,主要的操作都在后端,前端只需要传一个 url 参数,由后端去获取 config 的参数,回传给前端。前端拿到参数后,调用 wx.config 方法。let url = location.href.split(’#’)[0]http.get(‘weixin/config’,{ params:{ url: encodeURIComponent(url) }}).then(res=>{ wx.config({ beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题 debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: res.data.appId, // 必填,企业微信的corpID timestamp: res.data.timestamp, // 必填,生成签名的时间戳 nonceStr: res.data.nonceStr, // 必填,生成签名的随机串 signature: res.data.signature,// 必填,签名,见 附录-JS-SDK使用权限签名算法 jsApiList: [‘scanQRCode’] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来 }) // 检测微信 wx.error(function(res){ // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 console.log(‘错误信息====’,res) })})单页面应用加百度统计在单页应用上面,如果直接把百度统计的代码加到 html 不做任何处理的话,是统计不到每个页面的访问量的,所以把添加 js 和 监听跳转页面的代码都写到 main.js 里面去。// 添加百度统计 先判断是生产环境还是开发环境 如果是开发环境 不用添加if (process.env.NODE_ENV !== ‘development’) { let _hmt = _hmt || []; window._hmt = _hmt; // 必须把_hmt挂载到window下,否则找不到 (function() { var hm = document.createElement(“script”); hm.src = “https://hm.baidu.com/hm.js?yourappid"; var s = document.getElementsByTagName(“script”)[0]; s.parentNode.insertBefore(hm, s); })()}router.beforeEach(to, from, next){ // 添加百度统计代码 先判断是生产环境还是开发环境 if (process.env.NODE_ENV !== ‘development’) { // 添加页面统计 if (_hmt) { if (to.path) { _hmt.push([’_trackPageview’, ‘/#’ + to.fullPath]); } } }}参考文档:https://segmentfault.com/a/11…https://www.jianshu.com/p/feb… ...

April 9, 2019 · 2 min · jiezi

vue使用keep-alive保持滚动条位置的实现

前言下班前,20分钟,发一篇。。。简单介绍,使用keep-alive的时候,返回前一页,没有保持滚动条位置。事实上,就算不使用keep-alive,位置也没有被记录。但是,在不适用keep-alive的时候,页面内容会刷新,所以就随他去了……就是这么任性……思路官方有推荐一个scrollBehavior,链接,但是上面标注,只在history.pushState的浏览器生效,不知道是不是只能开启history.pushState才可以使用,看了下实现,挺不友好的,还是自己搞一个吧。。。实现思路是这样的,首先给路由增加一个对象meta:meta: { keepAlive: true, scrollTop: 0,}keepAlive是否需要保持页面,scrollTop记录页面的滚动位置。然后在app.vue增加如下入口:<keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><router-view v-if="!$route.meta.keepAlive"></router-view>这样就启用keep-alive了。然后在全局main.ts增加一个全局路由控制:router.beforeEach((to: Route, from: Route, next: () => void) => { if (from.meta.keepAlive) { const $content = document.querySelector(’#content’); const scrollTop = $content ? $content.scrollTop : 0; from.meta.scrollTop = scrollTop; } next();});很简单,离开的时候判断当前页是否需要保持页面,如果需要,记录页面主容器content的滚动位置,写入路由。然后,每次进入保持好的页面,读取滚动条位置scrollTop,修改主容器的scrollTop,就搞定了:public activated() { const scrollTop = this.$route.meta.scrollTop; const $content = document.querySelector(’#content’); if (scrollTop && $content) { $content.scrollTop = scrollTop; }}看起来很简单哦。遗留问题1、是不是每个页面都可以记录滚动条位置呢?其实不是的,有的页面,内部有js交互,比如tab交互,不同的tab,页面可滚动的高度不一致,如果不保持页面状态而统一记录滚动位置,有可能导致滚动条的位置错位。2、能不能把activated这一步写到全局的main.ts或者state去呢?有想过这点,但是目前来说,没找到实现的方法。首先,如果通过router来控制,做不到,全局路由控制只能在页面加载前监听,取不到载入页的元素。如果写在一个通用的全局函数去控制,比如定义一个state,当页面加载完的时候设置,那需要定义一个mixins来处理,但是对这个mixins不太熟悉,暂时还不知道该怎么做,可能有时间找个方法搞定它。没有啦……

April 8, 2019 · 1 min · jiezi

vue-router使用 看着篇就够了

官网地址:https://router.vuejs.org/zh/先来个自我介绍吧,我就是你们口中的路由,我的作用就是告诉你们怎么到达某地,比如你想去一个地方(前提是这个地方是已经存在的)我会查询我的路线图(路由配置)告诉你怎么过去。明白了吧,我的作用就是给你们导航的,有了我的存在,你们不用在详细的记住每一条路线图,只需要记住要去的目的地名字就行了,至于怎么过去,那是我的事,你就不用操心了,完全按照我的指示就能又快有准的到达目的地。对了,我还有三个助手,他们分别是:1.router :这位是我的指令官,他管理着所有的 route,当你需要指路时,他会召集所有的route,到routes集合,然后一个一个询问谁知道路线,直到找到那个route。如果找到了那个知道路线的route,router就派出他去为你导航,直至把你送到目的地。2.routes:所有的路线route都在这里存放。3.route:看名字也知道我是单数了,能力有限,我只能存放一条路线图。认识了我的三位助手,对我也有个简单的认识了,下面进入实操环节。——–分割线———–一:vue-router 初级应用最终期望:在首页中点击对应的连接进入对应的组件。在 components中新建三个组件 分别是:A、B、CA.<template> <div class=“hello”> <ul> <li> {{name}} </li> </ul> </div></template><script>export default { name: ‘HelloWorld’, data () { return { name: ‘my name is A!’ } }}</script><!– Add “scoped” attribute to limit CSS to this component only –><style scoped>h1, h2 { font-weight: normal;}ul { list-style-type: none; padding: 0;}li { display: inline-block; margin: 0 10px;}a { color: #42b983;}</style>B.<template> <div class=“hello”> <ul> <li> {{name}} </li> </ul> </div></template><script>export default { name: ‘HelloWorld’, data () { return { name: ‘my name is B!’ } }}</script><!– Add “scoped” attribute to limit CSS to this component only –><style scoped>h1, h2 { font-weight: normal;}ul { list-style-type: none; padding: 0;}li { display: inline-block; margin: 0 10px;}a { color: #42b983;}</style>C.<template> <div class=“hello”> <ul> <li> {{name}} </li> </ul> </div></template><script>export default { name: ‘HelloWorld’, data () { return { name: ‘my name is C!’ } }}</script><!– Add “scoped” attribute to limit CSS to this component only –><style scoped>h1, h2 { font-weight: normal;}ul { list-style-type: none; padding: 0;}li { display: inline-block; margin: 0 10px;}a { color: #42b983;}</style>配置路由:/router/index.jsimport Vue from ‘vue’import Router from ‘vue-router’import HelloWorld from ‘@/components/HelloWorld’import A from ‘@/components/A’import B from ‘@/components/B’import C from ‘@/components/C’Vue.use(Router)export default new Router({ routes: [ { path: ‘/’, name: ‘HelloWorld’, component: HelloWorld }, { path: ‘/A’, name: ‘A’, component: A }, { path: ‘/B’, name: ‘B’, component: B }, { path: ‘/C’, name: ‘C’, component: C } ]})在 HelloWord.vue中引入A、B、C三个组件的连接<template> <div class=“hello”> <ul> <li> <a href="/#/A">A</a> </li> <li> <a href="/#/B">B</a> </li> <li> <a href="/#/C">C</a> </li> </ul> </div></template><script>export default { name: ‘HelloWorld’, data () { return { msg: ‘Welcome to Your Vue.js App’ } }}</script><!– Add “scoped” attribute to limit CSS to this component only –><style scoped>h1, h2 { font-weight: normal;}ul { list-style-type: none; padding: 0;}li { display: inline-block; margin: 0 10px;}a { color: #42b983;}</style>运行命令:npm run dev打开网站运行一下:到这里vue-router的初级应用就介绍完了,总结下,有三个部分的知识点1.定义三个组件 A、B、C2.在router/index.js文件中引入三个组件,并做相应的路由配置3.在页面中添加3个a标签,分别跳转到对应的组件这部分内容比较初级,只是对路由做了简单的演示,通过定义好的路由进入对应的组件,在我们平时开发中,涉及到的需求要比这复杂的多,后面的内容会慢慢加深难度,接下来开始讲解vue-router的进阶版:vue-router 中级应用,这部分内容分三个小节:1.怎么动态定义路由2.路由中怎么传递参数3.路由命名有什么用 ...

April 4, 2019 · 2 min · jiezi

两行代码让你创建vue项目

和大家分享一下自己写的cli由于用vue自己的脚手架没那么舒服,所以自己写了个cli(主要是为了新建项目的时候偷懒,不想复制粘贴了);1.vue-cli这个我是基于yeoman开发的npm install -g yonpm install generator-frame -g yo frame大家可以直接去使用,如有什么需要改动,优化的地方,大家可以提出来,共同进步,谢谢

April 2, 2019 · 1 min · jiezi

Vue动态菜单(路由)的实现方案(beforeEach+addRoutes+elementUI)

前端路漫漫,挽起袖子干前言我之前总结过动态菜单的实现方案>动态菜单实现,只不过这篇写的有点稍微复杂,是用后端返回当前登录角色的路由表实现的,也就是前端只要从后端取到路由表进行渲染菜单即可;今天,我再讲解一种方案:路由表写在前端,后端返回用户的角色,前端进行角色对应的菜单渲染在线预览:动态路由github(记的star哈):https://github.com/Mrblackant…开始之前,自己要大概懂写关于vue-router的beforeEach(路由拦截)、addRoutes,elementUI的菜单组件等方法,不然理解可能会有点吃力思路分以下几步:1.前端在本地写好路由表,以及每个路由对应的角色,也就是哪些角色可以看到这个菜单/路由;2.登录的时候,向后端请求得到登录用户的角色(管理者、普通用户);3.利用路由拦截,根据取到的用户角色,跟本地的路由表进行对比,过滤出用户对应的路由,并利用路由进行左侧菜单渲染实现根据上述的3步,我们进行每一步的实现1.前端本地写好路由表我们分成两个路由表,一个是固定的,比如首页展示,每个人都能看到,一个需要根据用户角色动态展示的;这里就利用到了router的meta属性,我们在这里边写上菜单对应的:icon,对应的哪些角色可以看到这个菜单:roles一个完整的路由表如下://代码位置:router/index.js { path: ‘’, component: layout, //整体页面的布局(包含左侧菜单跟主内容区域) children: [{ path: ‘main’, component: main, meta: { title: ‘首页’, //菜单名称 roles: [‘user’, ‘admin’], //当前菜单哪些角色可以看到 icon: ’el-icon-info’ //菜单左侧的icon图标 } }] }2.用户登录,取到用户的角色本来我是写了mock数据,模拟用户登录,请求后端角色的接口,奈何mock挂了,所以我就直接模拟了:取到用户角色,存放进localStorage,然后跳转主页//代码位置:src/components/reLoad.vue // axios.post(’/temp’,this.formModel).then(res=>{}) // 我暂时就不模拟了,直接取 let getUserRole = this.formModel.user === ‘admin’ ? ‘admin’ : ‘user’ localStorage.setItem(‘userRole’, getUserRole) this.$router.push({ path: ‘/main’ })3.路由拦截beforeEach,并过滤出角色对应的路由表经过第2步,我们已经得到了用户的角色,这时候在路由拦截的地方我们就可以取到了,取到之后,结合第1步我们写好的路由,利用数组的filter方法,拿角色跟路由表里meta标签里的roless数据进行对比过滤好了,拿当前路由去渲染左侧菜单,这一步其实可以用vuex去实现,我担心有的小伙伴不理解,就用一个global(全局变量)替代了尤其要注意路由拦截这里,很容易陷入死循环,所以我建议大家先了解一下beforeEach和addRoutes的运行机制//代码位置:src/permission.jsrouter.beforeEach((to, from, next) => { // 取到用户的角色 let GetRole = localStorage.getItem(“userRole”) // 如果登录了 if (GetRole !== ‘unload’) { next() //next()方法后的代码也会执行 // 1.如果路由表 没根据角色进行筛选,就筛选一次 if (!addRouFlag) { addRouFlag = true // 2.根据用户的角色、和需要动态展示的路由,生成符合用户角色的路由 var getRoutes = baseRoleGetRouters(permissionRouter, GetRole.split(",")) // 3.利用global属性,让渲染菜单的组件sideMeuns.vue重新生成左侧菜单 global.antRouter = fixedRouter.concat(getRoutes) // 4.将生成好的路由addRoutes router.addRoutes(fixedRouter.concat(getRoutes)) // 5.push之后,会重新进入到beforeEach的钩子里,直接进入第一个if判断 router.push({ path: to.path }) } } else { // 用户没登录,跳转到登录页面 if (to.path === ‘/’) { next() } else { next(’/’) } }})整体流程走完了,再容易让人蒙的地方1.根据路由进行菜单展示代码位置:/src/components/sideMeuns.vue,先看下elementUI菜单组件,把一些基础的参数先了解一下,这里我把菜单渲染写成了一个组件:用到了递归属性,保证可以生成多级菜单,我建议不熟悉的,大家用组件先模拟着写一个包含跳转功能、icon展示的菜单,然后再看我写的组件2.用户退出系统代码位置:/src/components/layout.vue退出的时候,记得清除掉存在localStorage的用户角色,然后利用 window.location.href = “/“跳转到登录页,为什么要用location.href,这样会把之前addRoutes的路由清除掉,确保下个用户登陆后,会重新渲染正确的菜单如果有些地方不理解,师兄建议把不理解的点先单独拿出来跑跑,或者看看这篇文章的思路来源:手把手…如有不正确的地方,还望小伙伴指正哈 ...

April 1, 2019 · 1 min · jiezi

Vue路由传参的三种基本方式

Vue路由传参的三种基本方式现有如下场景,点击一个button按钮跳转到另外一个路由页面,并向目标路由页面传递参数,便于目标路由页面获取源页面传递的数据信息。源页面中:<button @click=“gotoTargetView”>点击跳转到目标路由user页面</button>方案一,通过调用$router对象的push()方法,向push()方法传递一个路由配置对象,通过params来传递参数需要注意的是使用params必须和name属性一起使用,否则要跳转的目标路由页面无法通过params获取到传递过来的参数。路由配置:{ path: ‘/user’, name: ‘user’, component: User}methods:gotoTargetView(){ this.$router.push({name:“user”, params:{userName:“lhb”}});//注意name不能换成path:"/user"}User.vue组件:this.$route.params.userName;//User.vue组件中就可以通过$route的params获取到方案二:通过调用$router对象的push()方法,向push()方法传递一个路由配置对象,需要通过query来传递参数需要注意的是使用query的时候,可以通过path属性也可以通过name属性来指定目标路由。这种情况下,query传递的参数会显示在url后面?userName=?&,如:http://localhost:8082/about?userName=lhb对应路由配置:{ path: ‘/user’, name: ‘user’, component: User}methods:gotoTargetView(){ this.$router.push({name:“user”,query:{“userName”:“lhb”}}); this.$router.push({path:"/user",query:{“userName”:“lhb”}});}User.vue组件:this.$route.query.userName;//User.vue组件中就可以通过$route的query获取到方案三:通过路由配置,配置动态路由参数上面的路由配置都是严格匹配的,只有要访问的路径与路由配置中的path一模一样,才能跳转到相应的组件上. 但有时现实却不是这样的,当我们去访问网站并登录成功后,它会显示 欢迎你,+ 你的名字。不同的用户登录, 只是显示"你的名字"部分不同,其它部分是一样的。这就表示,它是一个组件,假设是user组件。不同的用户(就是用户的id不同),它都会导航到同一个user 组件中。这样我们在配置路由的时候,就不能写死, 就是路由中的path属性,不能写死,那要怎么设置? 导航到 user 组件,路径中肯定有user, id 不同,那就给路径一个动态部分来匹配不同的id. 在vue-router中,动态部分 以 : 开头,那么路径就变成了 /user/:id, 这条路由就可以这么写:{ path:"/user/:id", component: user }.对应路由配置:{ path: ‘/user/:userName’, name: ‘user’, component: User}methods:gotoTargetView() { this.$router.push({path:/user/${userName}});}User.vue组件:this.$route.params.userName;//User.vue组件中就可以通过$route的params获取到

March 30, 2019 · 1 min · jiezi

Vue技术分类

vue-cli1、vue-cli 工程常用的 npm 命令有哪些?$ npm install vue-cli //1$ vue init webpack vue-project //2$ cd vue-project //3$ npm install //4$ npm run dev 2、请说出vue-cli工程中每个文件夹和文件的用处3、config文件夹 下 index.js 的对于工程 开发环境 和 生产环境 的配置module.exports => dev => proxyTable 开发时请求代理module.exports => port 开发时使用端口module.exports => build => 规定打包后文件的结构以及名称4、详细介绍一些 package.json 里面的配置name: 项目名称,version: 版本号,description: 项目说明,author: 作者信息,dependencies: 开发环境和生产环境都需要的依赖包devDependencies: 生产环境的依赖包vue知识点1、对于Vue是一套渐进式框架的理解 Vue核心功能是一个视图模板引擎,但不是说Vue就不能成为一个框架。可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。这些功能相互独立,可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。这就是“渐进式”,就是Vue的使用方式。2、vue.js的两个核心是什么?数据驱动、组件系统。3、请问 v-if 和 v-show 有什么区别? v-if判断条件是否渲染,是惰性的,初始渲染时条件为假时什么也不做;v-show是 display: block/none;元素始终都会渲染;在项目中如果需要频繁的切换则使用v-show较好,运行条件很少改变,则使用v-if。4、vue常用的修饰符.prevent 提交事件不再重载页面;.stop 阻止单击事件冒泡;.self 当事件发生在该元素本身而不是子元素时触发;.capture 添加事件监听器时使用事件捕获模式;.once 只会触发一次 按键修饰符 :keyup.enter :keyup.tab5、v-on可以监听多个方法吗? 可以。6、vue中 key 值的作用v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,主要是为了高效的更新虚拟DOM。7、vue-cli工程升级vue版本手动修改 package.json 里面vue的版本,同时修改 vue-template-compiler 为相同的版本;后者在devDependencies里面,然后npm install。8、vue事件中如何使用event对象? @click=“EventName($event)“9、$nextTick的使用在修改数据之后立即使用这个方法,获取更新后的 DOM。10、Vue 组件中 data 为什么必须是函数每用一次组件,就会有一个新实例被创建。每个实例可以维护一份被返回对象的独立的拷贝,每个对象都是独立互不影响的。11、v-for 与 v-if 的优先级v-for 具有比 v-if 更高的优先级。v-if 将分别重复运行于每个 v-for 循环中。vue风格指南提示永远不要把 v-if 和 v-for 同时用在同一个元素上。12、vue中子组件调用父组件的方法第一种:this.$parent.xxx;第二种:通过props传递父组件函数名,子组件接受,接受类型为Function;第三种:创建eventBus。13、vue中 keep-alive 组件的作用keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。include - 字符串或正则表达式,只有名称匹配的组件会被缓存;exclude反之亦然。 include=“a,b” :include="/a|b/” :include=”[‘a’, ‘b’]“14、vue中如何编写可复用的组件?1.规范化命名:组件的命名应该跟业务无关,而是依据组件的功能命名。2.数据扁平化:定义组件接口时,尽量不要将整个对象作为一个 prop 传进来。每个 prop 应该是一个简单类型的数据。这样做有下列几点好处: (1) 组件接口清晰。加粗文字 (2) props 校验方便。 (3) 当服务端返回的对象中的 key 名称与组件接口不一样时,不需要重新构造一个对象。 扁平化的 props 能让我们更直观地理解组件的接口。3.可复用组件只实现 UI 相关的功能,即展示、交互、动画,如何获取数据跟它无关,因此不要在组件内部去获取数据。4.可复用组件应尽量减少对外部条件的依赖。5.组件在功能独立的前提下应该尽量简单,越简单的组件可复用性越强。6.组件应具有一定的容错性。15、什么是vue生命周期和生命周期钩子函数?Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。让我们在控制整个Vue实例的过程时更容易形成好的逻辑。16、vue生命周期钩子函数有哪些?beforeCreate(创建前) 在数据观测和初始化事件还未开始created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来; beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上;mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互;beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程;updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用;beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用;destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。17、vue如何监听键盘事件中的按键?监听keyup事件并添加按键修饰符,对一些常用按键vue提供了别名,或者使用keyCode,vue也支持复合按键。18、vue更新数组时触发视图更新的方法Vue.set(arr, key, value) Vue.set(object, key, value)19、vue中对象更改检测的注意事项Vue 不能检测对象属性的添加或删除;不能动态添加根级别的响应式属性。使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。20、解决非工程化项目初始化页面闪动问题vue页面在加载的时候闪烁花括号{}},v-cloak指令和css规则如[v-cloak]{display:none}一起用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。/css样式/[v-clock] { display: none;}21、v-for产生的列表,实现active的切换<ul class=“ul” > <li v-on:click=“currentIndex = index” class=“item” v-bind:class="{clicked: index === currentIndex}” v-for="(items, index) in arr"> <a>{{items}}</a> </li></ul>data() { return{ currentIndex: 0 }}22、v-model语法糖的组件中的使用1:用于表单上数据的双向绑定;2:修饰符: .lazy- 取代input监听change事件 .number- 输入字符串转为数字 .trim- 输入首尾空格过滤 23、十个常用的自定义过滤器// 全局方法 Vue.filter() 注册一个自定义过滤器Vue.filter(“sum”, function(value) { return value + 4;});// 局部new Vue({ el: “.test”, data: { message:12 }, filters: { sum: function (value) { return value + 4; } }})24、vue等单页面应用及其优缺点优点——数据驱动、组件化、轻量简洁高效,通过尽可能简单的API实现响应的数据绑定和组合的视图组件;缺点:不支持低版本的浏览器,不利于SEO优化,可以使用服务器端渲染,首次加载耗时长。25、什么是vue的计算属性?在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。计算属性基于它们的依赖进行缓存的;只在相关依赖发生改变时它们才会重新求值。31、计算属性的缓存和方法调用的区别两种方式的最终结果确实是完全相同的。不同的是计算属性是基于它们的依赖进行缓存的,只在相关依赖发生改变时它们才会重新求值。只要相关依赖还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数如果不希望有缓存,请用方法来替代。 26、vue-cli提供的几种脚手架模板vue-cli的脚手架项目模板有webpack-simple 和 webpack;区别在于webpack-simple 没有包括Eslint 检查等功能。27、vue父组件如何向子组件中传递数据? 通过父组件v-bind传递数据子组件props接收数据28、vue-cli开发环境使用全局常量①少量Vue.prototype.baseUrl = function () { return ‘https://segmentfault.com/';};Vue.prototype.getTitle = { title:’’, isBack: true, isAdd: false,};②配置文件形式在项目的src 目录里面 新建一个 lib目录,lib目录里创建一个 config.js文件。export default { install(Vue,options) { Vue.prototype.baseUrl = function () { return ‘111’; }; Vue.prototype.getTitle = { title:’’, isBack: true, isAdd: false, }; Vue.prototype.showFootTab = { isShow:false, active:0, }}最后导入 import config from ‘./lib/config.js’; Vue.use(config);使用 <template> <div> {{getTitle.title}} </div> </template> this.getTitle29、vue-cli生产环境使用全局常量30、vue弹窗后如何禁止滚动条滚动? /滑动限制/ stop(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=‘hidden’; document.addEventListener(“touchmove”,mo,false);//禁止页面滑动 }, /取消滑动限制/ move(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=’’;//出现滚动条 document.removeEventListener(“touchmove”,mo,false); } // 如果不是Vue,可以直接给html设置overflow:hidden32、vue-cli中自定义指令的使用directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() }![图片描述][1] }}Vue.directive(‘color-swatch’, function (el, binding) { el.style.backgroundColor = binding.value}) vue-router1、vue-router如何响应 路由参数 的变化?当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。同时意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:watch: { ‘$route’ (to, from) { // 对路由变化作出响应… }}2、完整的 vue-router 导航解析流程导航被触发。在失活的组件里调用离开守卫。调用全局的 beforeEach 守卫。在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。在路由配置里调用 beforeEnter。解析异步路由组件。在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫 (2.5+)。导航被确认。调用全局的 afterEach 钩子。触发 DOM 更新。用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。3、vue-router有哪几种导航钩子( 导航守卫 )?全局的, 单个路由独享的, 组件级的。全局守卫:router.beforeEach router.beforeResolve(2.5+) router.afterEachconst router = new VueRouter({ … })router.beforeEach((to, from, next) => { // …})router.afterEach((to, from) => { // 这些钩子不会接受 next 函数也不会改变导航本身: …})路由独享的守卫: beforeEnter 这些守卫与全局前置守卫的方法参数是一样的。const router = new VueRouter({ routes: [ { path: ‘/foo’, component: Foo, beforeEnter: (to, from, next) => { // … } } ]})组件内的守卫beforeRouteEnterbeforeRouteUpdate (2.2 新增)beforeRouteLeaveconst Foo = { template: ..., beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 this // 因为当守卫执行前,组件实例还没被创建 next(vm => { // 通过 vm 访问组件实例 }) }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 this }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 this }}每个守卫方法接收三个参数:to: Route: 即将要进入的目标 路由对象from: Route: 当前导航正要离开的路由next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。4、vue-router的几种实例方法以及参数传递编程式导航 this.$router.push({ name: ’news’, params: { userId: 123 }}); // this.$route.params.userId this.$router.push({ path: ‘/news’, query: { userId: 123 }}); // this.$route.query.userId this.$router.replace();声明式导航 <router-link :to="{ name: ’news’, params: { userId: 1111}}">click to news page</router-link> <router-link :to="{ path: ‘/news’, query: { userId: 1111}}">click to news page</router-link>5、vue-router的动态路由匹配以及使用需要把某种模式匹配到的所有路由,全都映射到同个组件const User = { template: ‘<div>User{{ $route.params.id }}</div>’}const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: ‘/user/:id’, component: User } ]})复用组件时,想对路由参数的变化作出响应的话,使用watch (监测变化) $route 对象 watch: { ‘$route’ (to, from) { // 对路由变化作出响应… } }想匹配任意路径,我们可以使用通配符 (){ // 会匹配所有路径 path: ‘’}, { // 会匹配以 /user- 开头的任意路径 path: ‘/user-*’}6、vue-router如何定义嵌套路由?在router.js使用children数组来定义子路由,并在模板中使用<router-view>定义嵌套路由。如果没有匹配到合适的子路由,可以提供一个 空的 子路由 routes: [ { path: ‘/user/:id’, component: User, children: [ // 当 /user/:id 匹配成功, // UserHome 会被渲染在 User 的 <router-view> 中 { path: ‘’, component: UserHome }, // …其他子路由 ] } ]7、<router-link></router-link>组件及其属性<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签。to: <!– 字符串 –> <router-link to=“home”>Home</router-link> <!– 渲染结果 –> <a href=“home”>Home</a> <!– 使用 v-bind 的 JS 表达式 –> <router-link v-bind:to="‘home’">Home</router-link> <!– 不写 v-bind 也可以,就像绑定别的属性一样 –> <router-link :to="‘home’">Home</router-link> <!– 同上 –> <router-link :to="{ path: ‘home’ }">Home</router-link> <!– 命名的路由 –> <router-link :to="{ name: ‘user’, params: { userId: 123 }}">User</router-link> <!– 带查询参数,下面的结果为 /register?plan=private –> <router-link :to="{ path: ‘register’, query: { plan: ‘private’ }}">Register</router-link>replace: 会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。<router-link :to="{ path: ‘/abc’}" replace></router-link>append: 在当前 (相对) 路径前添加基路径tag: 渲染成某种标签active-class: 设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。8、vue-router实现路由懒加载( 动态加载路由 )component: () => import(‘comp/AlbumlibMore’)9、vue-router路由的两种模式vue-router 默认 hash 模式 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。HTML5 History 模式 充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。 要玩好,还需要后台配置支持; 因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 40410、history路由模式与后台的配合在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面;然后在给出一个 404 页面。vuex1、什么是vuex?专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车2、使用vuex的核心概念state => 基本数据 getters => 从基本数据派生的数据 mutations => 提交更改数据的方法,同步! actions => 像一个装饰器,包裹mutations,使之可以异步。 modules => 模块化Vuex(2019.03.26)3、vuex在vue-cli中的应用4、组件中使用 vuex 的值和修改值的地方?5、在vuex中使用异步修改6、pc端页面刷新时实现vuex缓存http请求1、Promise对象是什么?2、axios、fetch与ajax有什么区别?3、什么是JS的同源策略和跨域问题?4、如何解决跨域问题?5、vue-cli中如何使用JSON数据模拟?6、vue-cli中http请求的统一管理。7、axios有什么特点?UI样式1、.vue组件的scoped属性的作用2、如何让CSS只在当前组件中起作用?3、vue-cli中常用的UI组件库4、如何适配移动端?【 经典 】5、移动端常用媒体查询的使用6、垂直居中对齐7、vue-cli中如何使用背景图片?8、使用表单禁用时移动端样式问题9、多种类型文本超出隐藏问题常用功能1、vue中如何实现tab切换功能?2、vue中如何利用 keep-alive 标签实现某个组件缓存功能?3、vue中实现切换页面时为左滑出效果4、vue中父子组件如何相互调用方法?5、vue中央事件总线的使用混合开发1、vue如何调用 原生app 提供的方法?2、原生app 调用 vue 提供的方法,并将值传递到 .vue 组件中生产环境1、vue打包命令是什么?2、vue打包后会生成哪些文件?3、如何配置 vue 打包生成文件的路径?4、vue如何优化首屏加载速度?MVVM设计模式1、MVC、MVP与MVVM模式2、MVC、MVP与MVVM的区别3、常见的实现MVVM几种方式4、Object.defineProperty()方法5、实现一个自己的MVVM(原理剖析)6、 ES6中类和定义7、JS中的文档碎片8、解构赋值9、Array.from与Array.reduce10、递归的使用11、Obj.keys()与Obj.defineProperty12、发布-订阅模式13、实现MVVM的思路分析源码剖析1、vue内部与运行机制:Vue.js 全局运行机制响应式系统的基本原理什么是 Virtual DOM?如何编译template 模板?diff算法批量异步更新策略及 nextTick 原理?proxy代理?2、vuex工作原理详解Vue.mixinVue.use深入拓展1、vue开发命令 npm run dev 输入后的执行过程2、vue的服务器端渲染3、从零写一个npm安装包4、vue-cli中常用到的加载器5、webpack的特点 ...

March 26, 2019 · 4 min · jiezi

vue-router 起步步骤

1.在main.js中导入vue-router和组件import VueRouter from ‘vue-router’; // 导入vue-router并将它命名为VueRouterimport goods from ‘./components/goods/goods’; // 引入组件import seller from ‘./components/seller/seller’;2.为组件设置URL,通过url可以动态的加载组件const urls = [ { path: ‘/goods’, component: goods }, { path: ‘/rating’, component: rating }, { path: ‘*’, redirect: ‘/goods’ } //无效路径重点向到’/goods’];//定义一个常量来将url和组件绑定起来3.配置vue-router对象并挂载const router = new VueRouter( //新建一个vue-router对象 { routes: urls 将组件 (components) 映射到路由 (routes), }); new Vue({ el: ‘#app’, router, //注册你新建的vue-router对象 render: h => h(App)});4.配置连接的出口,实现动态的加载组件<router-view></router-view> //通过模板中放置元素来确定vue-router渲染组件的位置现在,可以通过url动态加载我们的组件5.将连接入口,挂载到网页上<router-link to="/goods">商品</router-link> //本质上是个a标签,to关联了跳转的url可以通过点击商品和评论完成页面局部的刷新步骤总结1.在main.js中导入vue-router和自定义的组件2.常量定义url和组件的关联3.创建vue-router对象并导入组件关系,并注册4.在模板中定义渲染的出口 <router-view></router-view> 和入口<router-link to=" “>商品</router-link> 官方起步文档:https://router.vuejs.org/zh/g…

March 26, 2019 · 1 min · jiezi

Vue技术点②

1:Vue实现数据双向绑定的原理?答:Object.defineProperty(),采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。<body> <div id=“app”> <input type=“text” id=“txt”> <p id=“show”></p></div></body><script type=“text/javascript”> var obj = {} Object.defineProperty(obj, ’txt’, { get: function () { return obj }, set: function (newValue) { document.getElementById(’txt’).value = newValue document.getElementById(‘show’).innerHTML = newValue } }) document.addEventListener(‘keyup’, function (e) { obj.txt = e.target.value })</script>2:Vue的路由实现:hash模式 和 history模式答:hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”3:vuex是什么?怎么使用?哪种功能场景使用它?答:专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,….. export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车4:vue-router如何响应 路由参数 的变化?答:当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。同时意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象: watch: { ‘$route’ (to, from) { // 对路由变化作出响应… } }5:vue-router有哪几种导航钩子答:全局的, 单个路由独享的, 或者组件级的。全局守卫:router.beforeEach router.beforeResolve(2.5+) router.afterEachconst router = new VueRouter({ … })router.beforeEach((to, from, next) => { // …})router.afterEach((to, from) => { // 这些钩子不会接受 next 函数也不会改变导航本身: …})路由独享的守卫: beforeEnter 这些守卫与全局前置守卫的方法参数是一样的。const router = new VueRouter({ routes: [ { path: ‘/foo’, component: Foo, beforeEnter: (to, from, next) => { // … } } ]})组件内的守卫beforeRouteEnterbeforeRouteUpdate (2.2 新增)beforeRouteLeaveconst Foo = { template: ..., beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 this // 因为当守卫执行前,组件实例还没被创建 next(vm => { // 通过 vm 访问组件实例 }) }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 this }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 this }}每个守卫方法接收三个参数:to: Route: 即将要进入的目标 路由对象from: Route: 当前导航正要离开的路由next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。6:完整的 vue-router 导航解析流程答:导航被触发。在失活的组件里调用离开守卫。调用全局的 beforeEach 守卫。在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。在路由配置里调用 beforeEnter。解析异步路由组件。在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫 (2.5+)。导航被确认。调用全局的 afterEach 钩子。触发 DOM 更新。用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。 ...

March 25, 2019 · 2 min · jiezi

vue-router实现原理

近期面试,遇到关于vue-router实现原理的问题,在查阅了相关资料后,根据自己理解,来记录下。我们知道vue-router是vue的核心插件,而当前vue项目一般都是单页面应用,也就是说vue-router是应用在单页面应用中的。那么,什么是单页面应用呢?在单页面应用出现之前,多页面应用又是什么样子呢?单页面应用与多页面应用单页面即 第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。原理是:JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了。多页面即 每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。原理是:传统的页面应用,是用一些超链接来实现页面切换和跳转的其实刚才单页面应用跳转原理即 vue-router实现原理vue-router实现原理原理核心就是 更新视图但不重新请求页面。vue-router实现单页面路由跳转,提供了三种方式:hash模式、history模式、abstract模式,根据mode参数来决定采用哪一种方式。路由模式vue-router 提供了三种运行模式:● hash: 使用 URL hash 值来作路由。默认模式。● history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。● abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端Hash模式hash即浏览器url中#后面的内容,包含#。hash是URL中的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会加载相应位置的内容,不会重新加载页面。也就是说即#是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中,不包含#。每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。History模式HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面;由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: ‘history’",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。有时,history模式下也会出问题:eg:hash模式下:xxx.com/#/id=5 请求地址为 xxx.com,没有问题。history模式下:xxx.com/id=5 请求地址为 xxx.com/id=5,如果后端没有对应的路由处理,就会返回404错误;为了应对这种情况,需要后台配置支持:在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。abstract模式abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。 (当然,你也可以明确指定在所有情况下都使用 abstract 模式)。具体更加详细的文章,请参考:Vue番外篇 – vue-router浅析原理vue-router的原理 ...

March 20, 2019 · 1 min · jiezi

vue刷新404

问题描述利用vue-route结合webpack编写了一个单页路由项目,运维协助在服务器端配置nginx。部署完成后,访问首页没问题,从首页里打开二级页面没问题,但是所有的二级页面打开后,再次刷新,就会出现404现象!2. 问题原因:刷新页面时访问的资源在服务端找不到,因为vue-router设置的路径不是真实存在的路径。3. 解决方法:在nginx配置里添加vue-route的跳转设置(这里首页是index.html,如果是index.php就在下面对应位置替换),正确配置如下location / { try_files $uri $uri/ @router; index index.html;}location @router { rewrite ^.*$ /index.html last;}

March 19, 2019 · 1 min · jiezi

Vue-router 组件重用解决方案

在官网中有vue-router组件复用的解释和解决方案,但这种方案耦合度太高,得在每个组件里都得写一个watch或beforeRouteUpdate,会产生很多的冗余另一种解决方案使用key的方式,官网中对key的介绍因此,对于不想产生组件服用的组件里,加上<componetent :key="$route.path" />

March 18, 2019 · 1 min · jiezi

VUE全家桶+ElementUi 项目踩坑总结

项目简介vue + axios + vue-router + vuex + ElementUI架构vuevue数据更新,视图不更新只有当实例被创建时 data 中存在的属性才是响应式的,Vue 不能检测对象属性的添加或删除; 可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性Vue.set(vm.userProfile, ‘age’, 27)this.$set(this.transportAddData.serviceOrderList[a].serviceOrderItems[i], ‘deletePoundIds’, [])vue 数据与方法 vue 对象更改检测注意事项Vue 不能检测以下变动的数组:当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:vm.items.length = newLengthvue 数组更新检测持续触发事件的优化持续触发某个事件可以用函数的节流和防抖来做性能优化//防抖function(){ … clearTimeout(timeoutId); timeoutId = setTimeout(function () { console.log(‘content’, content); player(content.msgTypeId, comId) }, 500); … }// 节流var canRun = true;document.getElementById(“throttle”).onscroll = function(){ if(!canRun){ // 判断是否已空闲,如果在执行中,则直接return return; } canRun = false; setTimeout(function(){ console.log(“函数节流”); canRun = true; }, 300);};javaScript的Throttling(节流)和Debouncing(防抖)nextTick在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。get() { this.$http.get(’/api/article’).then(function (res) { this.list = res.data.data.list // ref list 引用了ul元素,我想把第一个li颜色变为红色 document.querySelectorAll(’li’)[0].style.color = ‘red’ //这里会报错-,因为还没有 li this.$nextTick( ()=> { document.querySelectorAll(’li’)[0].style.color = ‘red’ }) })},Vue.nextTick 的原理和用途音频文件自动播放报错谷歌浏览器(Chrome 66)音频自动播放报错DOMException: play() failed because the user didn’t interact with the document first.解决方案:AudioContext// Chromerequest.get(’/baseConfig/messageAudio/play’, { params: { “comId”: Cookies.get(‘comId’), “msgTypeId”: id }, responseType: ‘arraybuffer’ // 这里需要设置xhr response的格式为arraybuffer }) .then(req => { … let context = new (window.AudioContext || window.webkitAudioContext)(); context.decodeAudioData(req.data, decode => play(context, decode)); function play (context, decodeBuffer) { sourceadio = context.createBufferSource(); sourceadio.buffer = decodeBuffer; sourceadio.connect(context.destination); // 从0s开始播放 sourceadio.start(0); } … })Chrome 66禁止声音自动播放之后 [AudioContext](https://developer.mozilla.org… [AudioContext.decodeAudioData()](https://developer.mozilla.org...vuex使用vuex修改state的方法和区别可以直接使用 this.$store.state.变量 = xxx;this.$store.dispatch(actionType, payload) 或者 this.$store.commit(commitType, payload)相同点:能够修改state里的变量,并且是响应式的(能触发视图更新) 不同点: 若将vue创建 store 的时候传入 strict: true, 开启严格模式,那么任何修改state的操作,只要不经过 mutation的函数,vue就会报如下错throw error : [vuex] Do not mutate vuex store state outside mutation handlers。使用commit提交到mutation修改state的优点:vuex能够记录每一次state的变化记录,保存状态快照,实现时间漫游/回滚之类的操作。dispatch 和 commit的区别在于: 使用dispatch是异步操作, commit是同步操作, 所以 一般情况下,推荐直接使用commit,即 this.$store.commit(commitType, payload),以防异步操作会带来的延迟问题。vuex strict vuex Mutation vuex actionsvuex到底是什么const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})==vuex中的state本质上是没有template的隐藏的vue组件。==vuex工作原理详解axios兼容问题Edge 41.16299.15.0 post请求会自动转为getmicrosoft-edge/platform/issues vue 使用axios 在EDGE浏览器上面post请求变成了get请求取消请求场景:每次请求在拦截器中添加token,后台判断token是否过期;当进入一个页面时触发多次请求,且正好token已经过期。这个时候需要第一次请求完成之后知道token已经过期则弹出提示、页面跳转到登录、停止之后的请求;否则会因为多次请求和axios响应拦截而多次弹框提示。解决方案 axios自带cancelToken 取消方法,然后在路由跳转后停止之前的请求// 请求拦截中 添加 cancelTokenaxios.interceptors.request.use(config => { config.cancelToken = store.source.token return config}, err => { return Promise.reject(err)}) // 路由跳转中进行判断router.beforeEach((to, from, next) => { const CancelToken = axios.CancelToken store.source.cancel && store.source.cancel() store.source = CancelToken.source() next()})//sort文件/state: { source: { token: null, cancel: null } }axios api 路由变化时使用axios取消所有请求 vue项目中 axios请求拦截器与取消pending请求功能存在问题: 如果 token过期提示弹框为二次确认弹框,再次确认之后才会进行页面跳转,那么在点击确认之前,页面中之前的请求还是会继续进行; 解决方案:给弹窗添加全局状态,根据状态判断是否需要弹出弹框预检请求CORS跨域 CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,==IE浏览器不能低于IE10。== 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。简单请求请求方法是以下三种方法之一: HEAD GET POSTHTTP的头信息不超出以下几种字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain简单请求不会触发 CORS 预检请求。非简单请求当请求满足下述任一条件时,即为非简单请求:使用了下面任一 HTTP 方法: PUT DELETE CONNECT OPTIONS TRACE PATCH人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:AcceptAccept-LanguageContent-LanguageContent-Type (but note the additional requirements below)DPRDownlinkSave-DataViewport-WidthWidthContent-Type 的值不属于下列之一: application/x-www-form-urlencoded multipart/form-data text/plain请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。请求中使用了ReadableStream对象。HTTP访问控制(CORS)预检请求非简单请求,会在正式通信之前,增加一次HTTP OPTIONS方法发起的查询请求,称为"预检"请求(preflight)。以获知服务器是否允许该实际请求。“预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。 ==该方法不会对服务器资源产生影响==优化方案Access-Control-Max-Age: <delta-seconds> //单位是秒。表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息) 可以被缓存多久Vue Routerpush、replace的区别push会向history添加新的记录,replace只是替换掉原来的记录,不会添加新的记录;这就导致了用replace方法跳转的页面是无法回到之前的页面的;(类似window.history)vue Router 编程式的导航路由懒加载为了提升页面加载速度,实现按需加载,也就是当路由访问时才加载对应组件,我们可以结合vue的异步组件和webpack的代码分割功能来实现路由的懒加载。{ path: ‘/iov/login’, name: ‘登录’, component: resolve => require([’@/views/login/login’], resolve),},{ path:’/iov/organization’, name:‘组织管理’, component:() => import(’@/views/enterprise/organization’)}vue Router 路由懒加载 vue 异步组件 vue + vue-router 懒加载 import / resolve+requireelementUI表单弹窗中 elementform 清除验证残留提示给表单添加不同的 ref (REFNAME),如果有相同的ref 会导致残留验证提示清除失败 this.dialogTranspor = true //弹窗打开后 dom 没有生成,所有要用 this.$nextTick this.$nextTick(function () { this.$refs.REFNAME.resetFields(); })页码数无法正常显示场景:列表页在跳到详情或其他页面后再返回列表页,无法正常显示对应页数(页码数放在state中),但请求的数据时正常的; 解决方案:页码数、总页数必须要在同一个对象中,否则当前页码数不能正常显示data() { return { //完成查询条件 searchComplate: { “comId”: Cookies.get(‘comId’), “transportOrderCode”: null, “orderCode”: null, “customerId”: null, “abnormal”: 2, “startTime”: null, “endTime”: null, “pageNum”: 1, “pageSize”: 20, total: 0, currentRow: ‘’, dataArea: [’’, ‘’], activeName: ‘’, expands: [] }, }}动态多级表单验证<li v-for="(item,index) in transportAddData.serviceOrderList”> <template v-for="(subItem,subIndex) in item.serviceOrderItems"> <tr > <td> <el-form-item :prop="‘serviceOrderList.’+index+’.serviceOrderItems.’ + subIndex + ‘.addressName’" :rules="{required: true, message: ‘卸货地不能为空’, trigger: ‘blur’}"> <el-input v-model=“subItem.addressName” placeholder=“地址”></el-input> </el-form-item> </td> <td> <el-form-item :prop="‘serviceOrderList.’+index+’.serviceOrderItems.’ + subIndex + ‘.planTonnage’" :rules="[{required: true, message: ‘必填项’, trigger: ‘blur’},{pattern: /^((([1-9]+(\d+)?)(.\d+)?)|(0.\d+))$/, message: ‘必须为正数且不为0’}]"> <el-input v-model=“subItem.planTonnage” placeholder=“预卸吨数”></el-input> </el-form-item> </td> … </tr> </template></li> ...

March 7, 2019 · 3 min · jiezi

vue-cli3+typescript初体验——router篇

前言vue基于类的写法,和基于对象的写法并不一致。使用vue-cli3创建的项目,src目录下的文件结构并没有多大区别,store、router、app、view、components、aeests该有的还是有的。但是,多了一个东西:vue-property-decorator,vue-property-decorator是vue-class-component的超集。import { Component, Prop, Vue, Watch, Emit } from ‘vue-property-decorator’;最主要的区别就是这里,组件的定义,参数的接受,方法的定义,等等。但是本文主要讲的是router的监听。路由监听用vue2的vue-cli创建项目,在src下有App.vue,main.js,其中如果要做路由权限控制,可以通过在mian.js添加以下代码来控制:import router from ‘./router’router.beforeEach((to, from, next) => { /如果需要登录,当前没有登录,直接跳转到登录页/ if (to.meta.Auth && !store.state.loginStatus) { return next({ name: ‘Login’, query: {path: to.name}}) } next() })这个功能,在新版本的vue3中依然可以使用,因为使用了typescript,所以应该是main.ts文件。但是如果要在组件内部使用路由监听,就遇到问题了,路由钩子beforeRouteEnter,beforeRouteLeave,beforeRouteUpdate不生效。官网推荐在mian.ts中注册解决:import Component from ‘vue-class-component’Component.registerHooks([ ‘beforeRouteEnter’,//进入路由之前 ‘beforeRouteLeave’,//离开路由之前 ‘beforeRouteUpdate’])但是在vue-cli3中试过,不生效。可能是vue-property-decorator和vue-class-component有区别的原因,或者项目配置问题。组件中实现路由监听,只能通过@Watch(’$route’)来实现。但是@Watch不是路由守卫,如果离开当前组件,就不能继续监听路由变化,所以需要在当前的router-virew容器组件中监听。<template> <div id=“app”> <div id=“nav”> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/> </div></template><script lang=“ts”>import { Component, Vue, Watch } from ‘vue-property-decorator’;@Componentexport default class App extends Vue { @Watch(’$route’,{ immediate: true }) private changeRouter(route: Route){ console.log(route) }}</script>其中{ immediate: true }是关键,必须加这个参数才嫩实现对$route的监听。结语vue-cli3+typescript的规范还不成熟,各种文档还不够齐全,尤其是中文文档。很多demo都是基于vue2改造的,导致使用vue-cli3的时候出bug。尤其是vue-router和vuex的使用。但也正是这些问题,让我们有更大的兴趣学习使用这个新的技术规范。 ...

March 1, 2019 · 1 min · jiezi

vue-router 源码阅读 - 文件结构与注册机制

前端路由是我们前端开发日常开发中经常碰到的概念,在下在日常使用中知其然也好奇着所以然,因此对 vue-router 的源码进行了一些阅读,也汲取了社区的一些文章优秀的思想,于本文记录总结作为自己思考的输出,本人水平有限,欢迎留言讨论~目标 vue-rouer 版本:3.0.2vue-router源码注释:vue-router-analysis声明:文章中源码的语法都使用 Flow,并且源码根据需要都有删节(为了不被迷糊 @_@),如果要看完整版的请进入上面的 github地址 ~ 本文是系列文章,链接见底部 0. 前备知识FlowES6语法设计模式 - 外观模式HTML5 History Api如果你对这些还没有了解的话,可以看一下本文末尾的推介阅读。1. 文件结构首先我们来看看文件结构:.├── build // 打包相关配置├── scripts // 构建相关├── dist // 构建后文件目录├── docs // 项目文档├── docs-gitbook // gitbook配置├── examples // 示例代码,调试的时候使用├── flow // Flow 声明├── src // 源码目录│ ├── components // 公共组件│ ├── history // 路由类实现│ ├── util // 相关工具库│ ├── create-matcher.js // 根据传入的配置对象创建路由映射表│ ├── create-route-map.js // 根据routes配置对象创建路由映射表 │ ├── index.js // 主入口│ └── install.js // VueRouter装载入口├── test // 测试文件└── types // TypeScript 声明我们主要关注的就是 src 中的内容。2. 入口文件2.1 rollup 出口与入口按照惯例,首先从 package.json 看起,这里有两个命令值得注意一下:{ “scripts”: { “dev:dist”: “rollup -wm -c build/rollup.dev.config.js”, “build”: “node build/build.js” }}dev:dist 用配置文件 rollup.dev.config.js 生成 dist 目录下方便开发调试相关生成文件,对应于下面的配置环境 development;build 是用 node 运行 build/build.js 生成正式的文件,包括 es6、commonjs、IIFE 方式的导出文件和压缩之后的导出文件;这两种方式都是使用 build/configs.js 这个配置文件来生成的,其中有一段语义化比较不错的代码挺有意思,跟 Vue 的配置生成文件比较类似:// vue-router/build/configs.jsmodule.exports = [{ // 打包出口 file: resolve(‘dist/vue-router.js’), format: ‘umd’, env: ‘development’ },{ file: resolve(‘dist/vue-router.min.js’), format: ‘umd’, env: ‘production’ },{ file: resolve(‘dist/vue-router.common.js’), format: ‘cjs’ },{ file: resolve(‘dist/vue-router.esm.js’), format: ’es’ }].map(genConfig)function genConfig (opts) { const config = { input: { input: resolve(‘src/index.js’), // 打包入口 plugins: […] }, output: { file: opts.file, format: opts.format, banner, name: ‘VueRouter’ } } return config}可以清晰的看到 rollup 打包的出口和入口,入口是 src/index.js 文件,而出口就是上面那部分的配置,env 是开发/生产环境标记,format 为编译输出的方式:es: ES Modules,使用ES6的模板语法输出cjs: CommonJs Module,遵循CommonJs Module规范的文件输出umd: 支持外链规范的文件输出,此文件可以直接使用script标签,其实也就是 IIFE 的方式那么正式输出是使用 build 方式,我们可以从 src/index.js 看起// src/index.jsimport { install } from ‘./install’export default class VueRouter { … }VueRouter.install = install首先这个文件导出了一个类 VueRouter,这个就是我们在 Vue 项目中引入 vue-router 的时候 Vue.use(VueRouter) 所用到的,而 Vue.use 的主要作用就是找注册插件上的 install 方法并执行,往下看最后一行,从一个 install.js 文件中导出的 install 被赋给了 VueRouter.install,这就是 Vue.use 中执行所用到的 install 方法。2.2 Vue.use可以简单看一下 Vue 中 Vue.use 这个方法是如何实现的:// vue/src/core/global-api/use.jsexport function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { // … 省略一些判重操作 const args = toArray(arguments, 1) args.unshift(this) // 注意这个this,是vue对象 if (typeof plugin.install === ‘function’) { plugin.install.apply(plugin, args) } return this }}上面可以看到 Vue.use 这个方法就是执行待注册插件上的 install 方法,并将这个插件实例保存起来。值得注意的是 install 方法执行时的第一个参数是通过 unshift 推入的 this,因此 install 执行时可以拿到 Vue 对象。对应上一小节,这里的 plugin.install 就是 VueRouter.install。3. 路由注册3.1 install接之前,看一下 install.js 里面是如何进行路由插件的注册:// vue-router/src/install.js/* vue-router 的注册过程 Vue.use(VueRouter) /export function install(Vue) { _Vue = Vue // 这样拿到 Vue 不会因为 import 带来的打包体积增加 const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode // 至少存在一个 VueComponent 时, _parentVnode 属性才存在 // registerRouteInstance 在 src/components/view.js if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } // new Vue 时或者创建新组件时,在 beforeCreate 钩子中调用 Vue.mixin({ beforeCreate() { if (isDef(this.$options.router)) { // 组件是否存在$options.router,该对象只在根组件上有 this._routerRoot = this // 这里的this是根vue实例 this._router = this.$options.router // VueRouter实例 this._router.init(this) Vue.util.defineReactive(this, ‘_route’, this._router.history.current) } else { // 组件实例才会进入,通过$parent一级级获取_routerRoot this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed() { registerInstance(this) } }) // 所有实例中 this.$router 等同于访问 this._routerRoot._router Object.defineProperty(Vue.prototype, ‘$router’, { get() { return this._routerRoot._router } }) // 所有实例中 this.$route 等同于访问 this._routerRoot._route Object.defineProperty(Vue.prototype, ‘$route’, { get() { return this._routerRoot._route } }) Vue.component(‘RouterView’, View) // 注册公共组件 router-view Vue.component(‘RouterLink’, Link) // 注册公共组件 router-link const strats = Vue.config.optionMergeStrategies strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}install 方法主要分为几个部分:通过 Vue.mixin 在 beforeCreate、 destroyed 的时候将一些路由方法挂载到每个 vue 实例中给每个 vue 实例中挂载路由对象以保证在 methods 等地方可以通过 this.$router、this.$route 访问到相关信息注册公共组件 router-view、router-link注册路由的生命周期函数Vue.mixin 将定义的两个钩子在组件 extend 的时候合并到该组件的 options 中,从而注册到每个组件实例。看看 beforeCreate,一开始访问了一个 this.$options.router 这个是 Vue 项目里面 app.js 中的 new Vue({ router }) 这里传入的这个 router,当然也只有在 new Vue 这时才会传入 router,也就是说 this.$options.router 只有根实例上才有。这个传入 router 到底是什么呢,我们看看它的使用方式就知道了:const router = new VueRouter({ mode: ‘hash’, routes: [{ path: ‘/’, component: Home }, { path: ‘/foo’, component: Foo }, { path: ‘/bar’, component: Bar }]})new Vue({ router, template: &lt;div id="app"&gt;&lt;/div&gt;}).$mount(’#app’)可以看到这个 this.$options.router 也就是 Vue 实例中的 this._route 其实就是 VueRouter 的实例。剩下的一顿眼花缭乱的操作,是为了在每个 Vue 组件实例中都可以通过 _routerRoot 访问根 Vue 实例,其上的 _route、_router 被赋到 Vue 的原型上,这样每个 Vue 的实例中都可以通过 this.$route、this.$router 访问到挂载在根实例 _routerRoot 上的 _route、_router,后面用 Vue 上的响应式化方法 defineReactive 来将 _route 响应式化,另外在根组件上用 this._router.init() 进行了初始化操作。随便找个 Vue 组件,打印一下其上的 _routerRoot:可以看到这是 Vue 的根组件。3.2 VueRouter在之前我们已经看过 src/index.js 了,这里来详细看一下 VueRouter 这个类// vue-router/src/index.jsexport default class VueRouter { constructor(options: RouterOptions = {}) { } / install 方法会调用 init 来初始化 / init(app: any / Vue组件实例 /) { } / createMatcher 方法返回的 match 方法 / match(raw: RawLocation, current?: Route, redirectedFrom?: Location) { } / 当前路由对象 / get currentRoute() { } / 注册 beforeHooks 事件 / beforeEach(fn: Function): Function { } / 注册 resolveHooks 事件 / beforeResolve(fn: Function): Function { } / 注册 afterHooks 事件 / afterEach(fn: Function): Function { } / onReady 事件 / onReady(cb: Function, errorCb?: Function) { } / onError 事件 / onError(errorCb: Function) { } / 调用 transitionTo 跳转路由 / push(location: RawLocation, onComplete?: Function, onAbort?: Function) { } / 调用 transitionTo 跳转路由 / replace(location: RawLocation, onComplete?: Function, onAbort?: Function) { } / 跳转到指定历史记录 / go(n: number) { } / 后退 / back() { } / 前进 / forward() { } / 获取路由匹配的组件 / getMatchedComponents(to?: RawLocation | Route) { } / 根据路由对象返回浏览器路径等信息 / resolve(to: RawLocation, current?: Route, append?: boolean) { } / 动态添加路由 / addRoutes(routes: Array<RouteConfig>) { }}VueRouter 类中除了一坨实例方法之外,主要关注的是它的构造函数和初始化方法 init。首先看看构造函数,其中的 mode 代表路由创建的模式,由用户配置与应用场景决定,主要有三种 History、Hash、Abstract,前两种我们已经很熟悉了,Abstract 代表非浏览器环境,比如 Node、weex 等;this.history 主要是路由的具体实例。实现如下:// vue-router/src/index.jsexport default class VueRouter { constructor(options: RouterOptions = {}) { let mode = options.mode || ‘hash’ // 路由匹配方式,默认为hash this.fallback = mode === ‘history’ && !supportsPushState && options.fallback !== false if (this.fallback) { mode = ‘hash’ } // 如果不支持history则退化为hash if (!inBrowser) { mode = ‘abstract’ } // 非浏览器环境强制abstract,比如node中 this.mode = mode switch (mode) { // 外观模式 case ‘history’: // history 方式 this.history = new HTML5History(this, options.base) break case ‘hash’: // hash 方式 this.history = new HashHistory(this, options.base, this.fallback) break case ‘abstract’: // abstract 方式 this.history = new AbstractHistory(this, options.base) break default: … } }}init 初始化方法是在 install 时的 Vue.mixin 所注册的 beforeCreate 钩子中调用的,可以翻上去看看;调用方式是 this._router.init(this),因为是在 Vue.mixin 里调用,所以这个 this 是当前的 Vue 实例。另外初始化方法需要负责从任一个路径跳转到项目中时的路由初始化,以 Hash 模式为例,此时还没有对相关事件进行绑定,因此在第一次执行的时候就要进行事件绑定与 popstate、hashchange 事件触发,然后手动触发一次路由跳转。实现如下:// vue-router/src/index.jsexport default class VueRouter { / install 方法会调用 init 来初始化 / init(app: any / Vue组件实例 */) { const history = this.history if (history instanceof HTML5History) { // 调用 history 实例的 transitionTo 方法 history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() // 设置 popstate/hashchange 事件监听 } history.transitionTo( // 调用 history 实例的 transitionTo 方法 history.getCurrentLocation(), // 浏览器 window 地址的 hash 值 setupHashListener, // 成功回调 setupHashListener // 失败回调 ) } }}除此之外,VueRouter 还有很多实例方法,用来实现各种功能的,剩下的将在系列文章分享 本文是系列文章,随后会更新后面的部分,共同进步vue-router 源码阅读 - 文件结构与注册机制网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出推介阅读:H5 History Api - MDNECMAScript 6 入门 - 阮一峰JS 静态类型检查工具 Flow - SegmentFault 思否JS 外观模式 - SegmentFault 思否前端路由跳转基本原理 - 掘金参考:Vue.js 技术揭秘 ...

February 24, 2019 · 5 min · jiezi

简述vue-router实现原理

router源码解读阅读请关注下代码注释打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif)1. router是什么首先,你会从源码里面引入Router,然后再传入参数实例化一个路由对象// router/index.jsimport Router from ‘vue-router’new Router({…})…源码基础类:// 源码index.jsexport default class VueRouter { … constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options this.beforeHooks = [] this.resolveHooks = [] this.afterHooks = [] this.matcher = createMatcher(options.routes || [], this) let mode = options.mode || ‘hash’ // 不选择模式会默认使用hash模式 this.fallback = mode === ‘history’ && !supportsPushState && options.fallback !== false if (this.fallback) { mode = ‘hash’ } if (!inBrowser) { // 非浏览器环境默认nodejs环境 mode = ‘abstract’ } this.mode = mode switch (mode) { // 根据参数选择三种模式的一种 case ‘history’: this.history = new HTML5History(this, options.base) // 根据HTML5版History的方法和属性实现的模式 break case ‘hash’: this.history = new HashHistory(this, options.base, this.fallback) // 利用url中的hash特性实现 break case ‘abstract’: this.history = new AbstractHistory(this, options.base) // 这种模式原理暂不清楚 break default: if (process.env.NODE_ENV !== ‘production’) { assert(false, invalid mode: ${mode}) } } } … // 一些api方法,你应该很熟悉,$router.push(…) push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.push(location, onComplete, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.replace(location, onComplete, onAbort) } go (n: number) { this.history.go(n) } back () { this.go(-1) } forward () { this.go(1) } …}我们创建的路由都是VueRouter类的实例化,用来管理我们的【key-components-view】,一个key(代码中的path)对应一个组件,view也就是<router-view>在template里面占个坑,用来根据key展示对应的组件,实例上的func让我们可以控制路由,也就是官网的api说简单点,路由就是一个‘轮播图’,emmmmmm,说轮播好像也不过分哈,写个循环切换key的func就是‘轮播了’,而key就是轮播的index,手动滑稽。那么,vue-router是如何实现不发送请求就更新视图的呢,让我们来看看vue如何使用路由的实例化后的路由输出:区分下route和router2. router工作原理如果你要使用到router,你会在实例化Vue的参数options中加入router// main.jsimprot xxx from xxximport router from xxxnew Vue({ el: ‘#app’, router: router, components: { App }, template: ‘<App/>’})那,Vue是如何使用这个参数呢,vue-router是作为插件加入使用的,通过mixin(混合)来影响每一个Vue实例化,在beforeCreate 钩子的时候就会完成router的初始化,从参数获取router -> 调用init初始化 -> 加入响应式(defineReactive方法在vue源码用的很多,也是底层实现响应式的核心方法)// 源码install.jsVue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router // 获取options里面的router配置 this._router.init(this) // 初始化,这个init是VueRouter类里面的方法,实例化后会继承这个方法,方法代码见下方 Vue.util.defineReactive(this, ‘_route’, this._router.history.current) // 这个是把_route加入数据监控,所以你可以watch到_route } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } })初始化会做些什么:-判断主程序状态(已经初始化了的vue实例不会再重新初始化路由,也就是你不能手动去再init一次)-把实例加入内置数组-判断history的类型,做一些类似优化的事,比如hash模式的setupListeners方法,就是延迟监听hashChange事件,等到vue完成挂载再监听,太细节不用深入-listen定义一个callback,listen是定义在最底层History类上的,作用就是定义一个callback,listen会在需要的时候被调用,在路由发生变化的时候会执行这个callback// 源码index.jsexport default class VueRouter {…init (app: any /* Vue component instance */) { process.env.NODE_ENV !== ‘production’ && assert( install.installed, not installed. Make sure to call \Vue.use(VueRouter)` + before creating root instance.` ) this.apps.push(app) // 这个apps存储了让所有的Vue实例化(根组件),后面遍历的时候,会把当前标记route挂到所有根组件的,也就是 vm._route 也是 vm._router.history.current // main app already initialized. if (this.app) { return } this.app = app const history = this.history if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() } history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } history.listen(route => { // 注意这个listen会在后面用到 this.apps.forEach((app) => { app._route = route // 根组件全部获取当前route }) }) }…}关于route的变化过程会在下面具体模式中说明,这里先跳过,接下来先说vue拿到router后,怎么使用router来渲染组件的3. vue如何使用router的在安装vue-router插件的时候export function install (Vue) { … Vue.component(‘RouterView’, View) // <router-link> & <router-view> 你应该很熟悉,本质就是vue组件,看源码之前我的猜测也是组件 Vue.component(‘RouterLink’, Link) …}router-link你不一定会使用,但是router-view你肯定会使用,它就是作为’窗口’的存在,来渲染你需要展示的组件。那,从这个组件开始说,一个前提条件是:vnode是通过render来创建的,也就是说改变_route的值会执行render函数,Router-View这个组件定义了自己的render,省略了大部分代码,这两行够了,你最终通过<router-view>看到的视图就是这么来的// vue源码render.jsexport function renderMixin (Vue: Class<Component>) {…vnode = render.call(vm.renderProxy, vm.$createElement)…}// router源码 view.jsrender (, { props, children, parent, data }) {…const h = parent.$createElement…return h(component, data, children)}第一种:hashHistory模式流程$router.push() –> HashHistory.push() –> History.transitionTo() –> History.updateRoute() –> {app._route = route} –> vm.render()1. 关于hashurl中#号后面的参数,别名哈希值,关于hash的一些特性1.改变hash并不会引起页面重载2.HTTP请求不包括#,所以使用hash不会影响到其他功能3.改变#会改变浏览器的访问历史4.window.location.hash可以读取哈希值5.JavaScript可以通过onhashchange监听到hash值的变化,这就意味着可以知道用户在浏览器手动改变了hash值因为这些特性才有的hashHistory更多关于hash知识见 URL的井号 - 阮一峰的网络日志2. hashHistory源码首先,这三种模式都是通过继承一个基础类History来的export class HashHistory extends History {…}那,三种模式肯定有相同的属性,相同的方法,肯定不会去创建三次所以从一个基类继承,然后各自的部分属性or方法会有差异,至于History这个类,我是不会去细看的,反正我也看不懂,哈哈哈哈router上的实例属性、方法可以在VueRouter、HashHistory/HTML5History/AbstractHistory、History上找到,这里说下HashHistory的几个func的实现、// router源码hash.jsexport class HTML5History extends History {…go (n: number) { window.history.go(n) }push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { // History类上的func pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) }function pushHash (path) { if (supportsPushState) { // 是否浏览器环境且环境支持pushstat方法,这个func下面会说 pushState(getUrl(path)) // 支持的话往window.history添加一条数据 } else { window.location.hash = path // 不支持的话直接修改location的hash }} replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { replaceHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) }// 其实replace和push只有两个区别1.window.location.hash = pathwindow.location.replace(getUrl(path))2.if (replace) { // replace调这个func会传一个true history.replaceState({ key: _key }, ‘’, url)} else { _key = genKey() history.pushState({ key: _key }, ‘’, url)}…}还有一点就是,在初始化hash模式路由的时候,会执行一个func,监听hashchange事件setupListeners () { window.addEventListener(supportsPushState ? ‘popstate’ : ‘hashchange’, () => { const current = this.current if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { if (supportsScroll) { handleScroll(this.router, route, current, true) } if (!supportsPushState) { replaceHash(route.fullPath) } }) })}第二种:HTML5History模式HTML5–History 科普主要是新增的两个api1.History.pushState()[优点写的清清楚楚]HTML5History的push、replace跟hash模式的差不多,就不上代码了一个标记是否支持HTML5的flag,这么写的,有需要的可以刨回去用export const supportsPushState = inBrowser && (function () { const ua = window.navigator.userAgent if ( (ua.indexOf(‘Android 2.’) !== -1 || ua.indexOf(‘Android 4.0’) !== -1) && ua.indexOf(‘Mobile Safari’) !== -1 && ua.indexOf(‘Chrome’) === -1 && ua.indexOf(‘Windows Phone’) === -1 ) { return false } return window.history && ‘pushState’ in window.history})()还有一个就是scrollBehavior,用来记录路由跳转的时候滚动条的位置,这个只能在HTML5模式下使用,即支持pushState方法的时候,部分博客说只有在HTML5History下才能使用,这个等我明天验证一下,我个人觉得支持HTML5就可以了2.History.replaceState()说的也很直观,就是不创新新纪录而覆盖一条记录,just do it结束语别问第三种情况(我是谁、我在哪、谁打我)我兜子好沃,早知道不做前端了在学习router源码的时候阅读了熵与单子的代码本的文章,看完这篇文章配合源码基本都可以很好掌握vue-router的大概,感谢作者,另外说明下本文由本人学习结束后加上自己的理解一字一字敲出来的,可能有些相似之处,侵删请联系我,写文章的目的是看看自己能否表述清楚,对知识点的掌握情况,讲的不对的地方,请各位大佬指正~~感谢潘童鞋的指导(^▽^)当然,我也稀罕你的小❤❤,点个赞再走咯以上图片均来自MDN网页截图、vue官网截图、百度首页截图,不存在版权问题 /滑稽【注】:内容有不当或者错误处请指正转载请注明出处谢谢合作! ...

February 21, 2019 · 3 min · jiezi