VUE基础小记
浏览器渲染流程
- 构建DOM树(文档对象模型)
- 构建CSSOM树(
构建完整
才能使用) - 在上两步若遇到js阻塞树的构建,则优先
加载
js文件 - DOM树与CSSOM树结合,构建渲染树(Render Tree)
- 布局Layout(为Render树上的节点确定显示屏上的精确坐标)
- 绘制Painting(调用节点的paint方法,绘制)
- 页面的重绘,重排(回流)
回流:回流是布局或者几何属性需要改变
重绘:由于节点的几何属性发生改变或者由于样式发生改变但不会影响布局
问题
- FOUC:Flash of Unstyled Content,无样式内容闪烁。CSS与图片可以并发加载,但js在加载时会禁用并发,阻止其他内容加载。原因:在CSS文件放在页面底部时,或先执行脚本文件
- 白屏:浏览器长时间不渲染页面。原因:CSS样式被置于底部(最后加载),js放在页面顶部导致白屏,阻碍DOM解析
- 浏览器性能问题:改变样式内容时,需要通过遍历整个DOM树,寻找需要修改的节点。dom树与js的模块是分开的,跨模块通讯成本增大;dom操作造成重绘与回流,使性能开销巨大。(用虚拟dom解决)
虚拟DOM
用JS对象模拟DOM树,更新DOM节点时,不会立即更新,会将多次更新的diff内容保存到本地的js对象中,后将该js对象一次性attach到DOM树上,减少不必要的计算(减少js操作dom带来的性能消耗)
VUE渲染流程
- 解析语法生成AST(抽象语法树)
- 根据AST结果,完成data初始化
- 根据AST结果,data数据绑定情况,生成虚拟DOM
- 将虚拟DOM生成真正的DOM,插入页面渲染
Webpack
前端工程化:
- 模块化:将js,css,以功能为单元组织代码
- 组件化:将页面拆分成多组件,可复用
- 规范化:目录结构,编码,接口,文档规范化
- 自动化:自动构建,自动部署等
webpack:
现代javascript应用程序的静态模块打包器(module bundler),在处理应用程序时,把各个模块按照特定顺序组织,最终合并为一个或多个js文件
- 入口(entry):根上下文,第一个启动文件,告诉webpack从源码目录下哪个文件开始打包
- 输出(output):将打包好的文件放在哪里
- loader:将不同的文件转换成打包所需要的模块
- 插件(plugins)
Vue CLI:
VUE基于Webpack封装的便捷脚手架
生命周期
生命周期钩子
- beforeCreate:初始化实例前,data,methods等不可获取
- created:初始化实例完成,可获取data,methods,无法获取DOM
- beforeMount:虚拟DOM创建完成,未挂载到页面中,vm.$el可获取未挂载模板
- mounted:数据绑定完成,真实DOM已挂载到页面,vm.$el可获取真实DOM
- beforeUpdate:数据更新,DOM diff得到差异,未更新到页面
- updated:数据更新页面更新
- beforeDestroy:实例销毁前
- destroy:实例销毁完成
实例基本选项
el:提供一个页面中已存在的DOM元素作为实例的挂载目标
template:给VUE实例提供字符串模板
render:字符串模板的代替方案
data:vue实例的数据对象
props:接受来自父组件的数据
模板语法
语法 | 说明 |
---|---|
插值语法 {{}} | 文本插值 |
v-once | 一次性插值,数据改变时内容不会更新 |
v-html | 输出html,不会转义 |
v-bind | 用于绑定DOM属性 |
过滤器:
//局部定义
filters: {
test: function (value1, value2) {
}
}
//使用方法 插值,v-bind表达式
{{ number | test}}
<div :text="number | test"></div>
事件绑定:
//用 v-on 绑定DOM事件,@缩写
<template>
<button v-on:click="addCounter">Add 1</button>
<p>ans:{{ counter }}</p>
</template>
<script>
export default {
data() {
return {
counter: 0
};
},
methods: {
addCounter() {
this.counter += 1;
}
}
};
</script>
事件修饰符
- .stop:阻止事件继续传播
- .prevent:阻止默认事件
- .capture:添加事件监听器时使用事件捕获模式
- .once:只绑定一次
计算属性computed:
var vm = new Vue({
data: { a: 1 },
computed: {
// 仅读取
aDouble: function() {
return this.a * 2;
},
// 读取和设置
aPlus: {
get: function() {
return this.a + 1;
},
set: function(v) {
this.a = v - 1;
}
}
}
});
vm.aPlus; // => 2
vm.aPlus = 3;
vm.a; // => 2
vm.aDouble; // => 4
侦听器 watch:
监听数据变化
var vm = new Vue({
data: {
a: 1
},
watch: {
a: function(val, oldVal) {
console.log("new: %s, old: %s", val, oldVal);
}
});
vm.a = 2; // => new: 2, old: 1
组件的使用
组件是可复用的VUE实例,与new Vue()接受相同的选项,除了el(根实例特有)
注册方式
在模板中使用前,必须先注册以便VUE识别
全局注册
Vue.component("my-button", {
// 选项
// 除了 el 以外,组件的选项与 Vue 实例相同
});
局部注册
// 获取组件
import MyButton from "../components/my-button";
new Vue({
components: { MyButton }
});
单文件组件
<template>
<!-- 组件模板 -->
</template>
<script>
export default {
name: "MyComponent"
};
</script>
<style>
</style>
组件间通讯
封装的组件,通过prop提供给外部配置
使用,可在组件上注册一些自定义属性,来接收来自父组件的数据/属性值
Vue.component("my-component", {
// 简单的数组
props: ["propA", "propB", "propC", "propD", "propE", "propF"],
// 高级配置
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function() {
return { message: "hello" };
}
},
// 自定义验证函数
propF: {
validator: function(value) {
// 这个值必须匹配下列字符串中的一个
// 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
return ["success", "warning", "danger"].indexOf(value) !== -1;
}
}
}
});
父子组件间通讯
父级的prop的更新会向下流动到子组件中,反之不行。父组件可以通过v-on监听子组件实例的事件,总结来说就是
1、父组件用 props 把数据传给子组件
2、子组件用 emit让父组件监听自定义事件
vm.$emit( event, arg ) //触发当前实例上的事件
vm.$on( event, fn ) //监听event事件后运行 fn
关于$emit的用法可参考
slot插槽
父组件可以在子组件指定位置自定义的插入内容
与prop的区别:通过props,只能由父组件向子组件传递属性方法,插槽可传递所有带标签的内容,包括组件
- 匿名插槽
<slot>匿名插槽(无name属性),该字符是其默认值</slot>
- 具名插槽
<slot name="header"></slot>
<template v-slot:header><h1>header</h1></template>
- 作用域插槽
<Child> {{childUser.Name}} </Child>
调用子组件中的childUser数据- 解构插槽
<Child v-slot="{ childData= { Name: 'Guest' } }">{{ childData.Name }}</Child >
组件封装
常用指令与自定义指令
常用指令
条件渲染:
v-if
,v-else-if
,v-else
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Default Type</div>
v-show
始终被渲染并保留在DOM中,条件具备时显示,频繁切换用v-show
<div v-show="isShow">Something</div>
列表渲染:
v-for
用 item in items 遍历数组,对象,数字
<li v-for="(item, index) in items">
{{index}}: {{ item.message }}
</li>
数据更新检测:
Vue 无法检测到对象属性的添加或删除;数组中某个元素被替换、更新无法触发视图更新
// 数组处理方法1: 返回新数组
this.items = [...this.items, newItem];
// 数组处理方法2: Vue.set 或 vm.$set
Vue.set(vm.items, indexOfItem, newValue);
vm.$set(vm.items, indexOfItem, newValue);
// 对象处理方法1: 返回新对象
this.object = { ...this.object, key: newValue };
// 对象处理方法2: Vue.set 或 vm.$set
Vue.set(vm.object, key, value);
vm.$set(vm.object, key, value);
表单绑定:
v-model
指令在表单<input>
、<textarea>
及<select>
元素上创建双向数据绑定
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
自定义v-model
<template>
<div>
<input
type="text"
:value="content"
@input="content = $event.target.value"
/>
<div>{{ content }}</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
content: '',
}
},
}
</script>
<style></style>
路由
路由实现步骤
(1) 设置监听器,监听popstate
或者hashchange
事件。
(2) 通过 hash(location.href.hash)获取当前的路由位置。
(3) 根据当前匹配路径,判断后加载对应模块。
使用步骤
1.引入vue-router,配置入口文件main.js
// main.js
import Vue from "vue";
// 引入 vue-router
import VueRouter from "vue-router";
Vue.use(VueRouter); // 使用 vue-router
// 后面是 new Vue() 相关逻辑
new Vue({
router,
render: h => h(App)
}).$mount('#app')
2.控制路由index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
const router = new VueRouter({
routes
})
export default router
导航方式
(1) router编程式导航
router.push("/home");
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1);
(2) <router-link>
声明式导航
<router-link>
组件支持用户在具有路由功能的应用中 (点击) 导航。通过to属性指定目标地址,默认渲染成带有正确链接的<a>
标签,可以通过配置tag属性生成别的标签。
路由传参
(1) /page/detail?id=123,在 Vue Router 中用query表示
this.$router.push({ name: "Page2", query: { id: 123 } });
(2) /page/detail/123,在 Vue Router 中用params表示
const router = new VueRouter({
routes: [
{
path: "/home",
component: Home,
name: "Home",
children: [
// 动态路径参数以冒号 ":" 开头
{ path: "page1/:id", component: Page1, name: "Page1" },
{ path: "page2", component: Page2, name: "Page2" }
]
}
]
});
<template>
<!-- $route 可直接注入到模板 -->
<div>{{ $route.params.id }}</div>
</template>
监听路由
当我们使用单个组件绑定多个路由时,路由的跳转并不能再次触发组件的重建(无法重新触发一些生命周期钩子),此时需要对路由进行监视。例如,我们对某个页面进行传参,来控制是需要新建某个内容,还是需要修改某项内容。
<template>
<div>
<div>Detail</div>
<div>{{$route.query.id ? '修改' : '新建'}}</div>
<div>name: <input v-model="detail.name" /></div>
<div>text: <input v-model="detail.text" /></div>
</div>
</template>
<script>
// 下面是 Vue 组件
export default {
data() {
return {
detail: {
name: "",
text: ""
}
};
},
watch: {
$route(to, from) {
// 对路由变化作出响应,更新参数
this.updateDetail();
}
},
methods: {
updateDetail() {
const id = this.$route.query.id;
if (id) {
// 传入 id 则意味着修改,需要获取并录入原先内容
this.detail = {
name: `name-${id}`,
text: `text-${id}`
};
} else {
// 未传入 id 则意味着新建,需要重置原有内容
this.detail = {
name: "",
text: ""
};
}
}
}
};
</script>
导航守卫
const router = new VueRouter({ ... })
// 这里我们注册一个前置守卫
router.beforeEach((to, from, next) => {
if (to.name !== "Login") {
// 非 login 页面,检查是否登录
// 这里简单前端模拟是否填写了用户名,真实环境需要走后台登录,缓存到本地
if (!isLogin) {
// 未登录则需要跳转到登录页面
next({ name: "Login" })
}
}
// 其他情况正常执行
next()
});
路由懒加载
// 实现 Page2 页面懒加载
// 不会被打包到主包中,当匹配到对应的路由时候,才会被加载
const Page2 = () => import("./Page2.vue");