Vue.js框架基础回顾
- 前端路由是实现
单页面应用的基础
单页面应用:只有一张Web页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次,常用于PC端官网、购物等网站
Vue基础结构
e.g.1:基础vue实例,传入el和data选项,vue内部会把data数据填充到el指向的模板上,并把模板渲染到浏览器。
e.g.2:使用了render选项和$mount方法,render方法接收一个参数h(),h函数创建了虚拟DOM,而$mount把虚拟DOM转换为真实DOM,渲染到浏览器。
两者运行有区别
Vue的生命周期
Vue.js语法和概念
语法
- 差值表达式:
{{data}}中成员显示在模板的任何位置,内容中有html字符串,会把内容解析为普通文本,html的内容会被转义,可以使用v-html指令 - 指令:14个
- 计算属性和侦听器:计算属性会被缓存
- Class和Style绑定:Class样式可以重用
- 条件渲染/列表渲染
- 表单输入绑定(双向绑定)
概念
- 组件:可复用的vue实例,一个组件封装了html、css、JavaScript,可以实现页面上的一个功能区域,可以无限次地被重用
- 插槽:在自定义组件中挖坑,在使用组件时填坑,让组件更灵活
- 插件
- 混入mixin:相同的选项合并,代码可以进行重用
- 深入响应式原理
- 不同构件版本的Vue:(不)带编译器
报告:
this.$router
this.$route是当前的路由信息,没有push、back等方法
this.$router可以通过
currentRoute获取到当前路由对象中信息,e.g:path、parm as、query等
- router-link组件的
exact-active-class用来设置地址精确匹配的元素的样式
Vue Router实现原理
基础回顾
Ⅰ、创建路由
-
注册路由插件,制定路由规则
-
创建router对象,导出
-
注册router对象。创建Vue实例的时候,传入router对象,Vue实例中被注入两个属性:
$route:路由规则$router:路由相关的方法 -
创建路由组件的占位
<router-view/> -
创建链接
<router-link/>
Ⅱ、动态路由传参
- 路由懒加载
{
path:'xxx/:id',
//props:true,
//开启props,会把URL中的参数传递给组件
//在组件中通过props来接收URL参数
name:'xxx',
//命名式导航
commponent:() => import('../xxx/xxx.vue')
}
-
通过当前路由规则,获取数据。强依赖路由。
{{$route.params.id}} -
父子组件传值的方式:路由规则中开启props传参。不依赖路由。{{id}}export default { props: ['id'] }
Ⅲ、嵌套路由
layout.vue嵌套到index.vue/ details.vue中输出,使之拥有相同的头和尾
//index.js
//配置路由
import Layout from '@/commponents/Layout.vue'
const routes = [
//嵌套路由:先加载Layout组件,再加载Index/Detail组件
{
path:'/',
component:Layout,
children: [
//首页
{
name: 'index',
path: '',//相对路径'/'
component: Index
},
//详情页
{
name: 'detail',
path: 'detail/:id',
props: true,
component: () => import('@/views/Detail.vue')//'/Detail.vue'
}
]
}
]
Ⅳ、编程式导航
this.$router.push('/')
this.$router.push({name:'Home'})
this.$router.replace('/login')
//不会记录当前历史
this.$router.go(-2)
Hash模式和History模式
- 都是客户端路由的实现方式
Ⅰ、区别
表现形式:
- Hash:
http://xxx.com/#/xxx?id=11很丑 - History:
http://xxx.com/xxx/222
原理:
-
Hash:基于锚点,以及
onhashchange事件调用push方法会先判断当前浏览器是否支持
window.pushState,再调用pushState改变地址栏;否则通过
window.loaction改变地址栏 -
History:基于HTML5中的
HistoryAPI-
history.pushState()IE10以后才支持 -
history.replaceState()
此模式下调用
router.push(url)方法时,push方法内部会直接调用window.history.pushState,把url设置到浏览器的地址栏window.history.pushState不能触发popstate事件,当历史状态被激活的时候才会触发popstate
-
两者都是客户端修改URL,性能相差不大
Ⅱ、History模式
- History模式是基于浏览器的History API实现的
- 需要服务器支持
- 单页应用中,服务端不存在
http://www.xx.com/login会返回找不到页面 - 在服务端应该除了静态资源外都返回单页面应用的
index.html
//index.js
const routes = [
{
path:'*',
name:'404',
compontent: () => import('../xxx/404.vue')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
Ⅲ、node.js服务器配置
node配置的服务器app.js:
//app.js
const path = require('path')//可以合并两个路径
//导入处理hitory模式的模块
const history = require('connect-history-fallback')
//导入express
const express = require('express')
const app = express()
//注册处理history模式的中间件
//app.use(history())
//处理静态资源的中间件,网站根目录../web
app.use(express.static(path.join(__dirname,'../web')))//处理静态资源
//开启服务器,端口是3000
app.listen(3000, () => {
console.log("服务器开启,端口:3000")
})
中间件?
- 服务端不存在
http://www.xx.com/login会返回找不到页面,所以要在服务端配置支持
app.use(history())
node app.js
Ⅳ、nginx服务器配置
(不能有中文)
start nginx
nginx -s reload
nginx -s stop
实现自己的Vue Router
- 前置知识:插件、混入、Vue.observable()、插槽、render函数、运行时和完整版的Vue
Vue.observable():可以使用Vue.observable()把任意对象转换成响应式对象,Vue.observable()返回的对象没有办法在模板中直接使用,但可以在h()函数中使用
Ⅰ、实现原理
Hash模式:
- URL中#后面的内容作为路径地址
- 监听
hashchange事件 - 根据当前路由地址找到对应组件重新渲染
History模式:
- 通过
history.pushState()方法改变地址栏 - 监听
popstate事件 - 根据当前路由地址找到对应组件重新渲染
Ⅱ、分析
类图:
Ⅲ、代码实现
install
//./vuerouter/index.js
let _Vue = null
export default class VueRouter {
static install() {
if(VueRouter.install.installed){
return
}
VueRouter.install.installed = true//当前插件被安装了
_Vue = Vue
//_Vue.prototype.$router = this.$options.router
//混入
_Vue.mixin({
beforeCreate(){
//组件orvue实例
if(this.$options.router) {
_Vue.prototype.$router = this.$options.router//只需要执行一次
this.$options.router.init()
}
}
})
}
}
/**
* 1.判断当前插件是否重复被安装
* 2.install静态方法接收了静态函数,将来要在vue的实例方法中使用这个函数,要把vue构造函数记录到全局变量中
* 3.把创建vue实例时候传入的router对象注入到vue实例上
*/
构造函数
constructor (options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: '/'//存储当前路由地址
})
//三个所需要的属性
}
createRouteMap:把构造函数传过来的选项中的rules(路由规则)转换为键值对的形式存储到routerMap.
routerMap中存储的键的值就是路由中对应的组件。如果路由发生变化,很容易根据地址在routeMap中找到组件,并且渲染到视图上。
createRouteMap() {
// 遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents:
- 实现
router-link组件
//参数vue为了减少和外部的依赖
initComponents (Vue) {
vue.component('router-link',{
props: {
to: String,
},
template: '<a :href = "to"><slot></slot></a>'
})
}
init:包装initComponents和createRouteMap方法让外部使用方便
init() {
this.createRouteMap()
this.initComponents(_Vue)//传入vue的构造函数
}
- 完整版本的Vue
Vue的构建版本
运行时版:不支持
template模板,需要打包的时候提前编译完整版:包含运行时和编译器,体积比运行时版大10K左右,程序运行的时候把模板转换成
render函数完整版性能不如运行时版本
vuecli默认使用运行时版本
Vue-cli中方法,runtimeCompiler:是否使用包含运行时编译器的Vue构建版本。设置为true后可以在Vue组件中使用template选项
创建./vue.config.js
module.exports = {
runtimeCompiler: true,
}
- 运行版本的Vue
关注:render函数怎么写
因为运行版本不支持template选项,需要修改initComponents
initComponents(Vue){
Vue.component('router-link', {
props: {
to: String
}
},
//h创建虚拟DOM
render (h) {
return h('a', {
attrs: {
herf: this.to//history模式
}//设置DOM属性
}, [this.$slots.default])
//获取默认插槽设置到a标签里
}
//template:...
)
}
- 实现
router-view组件,并且使路由转跳都在客户端操作,页面不刷新,但地址栏发生变化
initComponents (Vue) {
Vue.component('router-link', {
props: {
to: String
}
},
render (h) {
return h('a', {
attrs: {
herf: this.to
},
on: {
click: this.clickHandler
},//给a标签注册一个跳转功能
}, [this.$slots.default])
methods:{
clickHander (e) {
//1.改变浏览器的地址栏
history.pushState({}, '', this.to)
//2.把当前路径记录到data.current里
this.$router.data.current = this.to
e.preventDefault()
//事件处理函数,调用默认行为
}
}
}
)
const self = this
Vue.component('route-view', {
render (h) {
//在render内部首先要找到路由地址,再根据当前路由的地址,在routeMap对象中找到路由地址对应的组件,再调用h函数帮这个组件转换成虚拟DOM,然后返回
const component = self.routeMap[self.data.current]//获取路由地址
return h(component)
}
})
}
initEvent:当地址变化的时候,改变页面元素的逻辑
init() {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
initEvent () {
window.addEventListener('popstate', ()=> {
this.data.current = window.location.pathname
})
}
本文首发于我的GitHub博客,其它博客同步更新。