定义
mvc
Model - 模型,数据保存
View - 视图 用户界面
Controller - 控制器 业务逻辑
mvvm框架?
M - Model
V - View
VM - ViewModel
采用 双向绑定(data-binding):View的变动,自动反映在 ViewModel,ViewModel 会自动去更新 Model 数据,反之亦然(即当 Model 发生改变时也会自动反映到 ViewModel 上,触发 View 的自动更新渲染)
官网
现行两大版本:vue2.x vue3.x
vue架构中包含了mvvm…
Vue2.x 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性(Object.defineProperty()
)
使用 直接引入:
方式引入,会添加一个全局变量 Vue
npm
$ npm install vue
vue-cli (常用)
1.商品渲染
html
<div id="app">
<h1>购物车h1>
<h1>推荐商品:h1>
<ul>
<li v-for="curr in products">
{{ curr.id }}-{{ curr.title }}-{{curr.price}}-{{curr.num}}
<button>加入购物车button>li>
ul>
div>
<script src="./libs/vue.js">script>
<script>
// 定义数组,保存推荐商品(模拟数据)
const _products = new Array(8).fill(null).map((item, index, array) => {
return {
id: index + 1,
title: '商品标题:' + (index + 1),
price: (Math.random() * 100).toFixed(2),
num:0,
}
})
//vue 创建一个新的 Vue 实例
const vm = new Vue({
el: '#app', //view,element
data:{ //model
products:_products, //推荐商品
},
computed:{
属性一(){
return ...
},...
},
methods:{
方法一(){
},...
}
})
script>
2.添加到购物车
<div id="app">
<h1>推荐商品:h1>
<ul>
<li v-for="curr in products">
{{ curr.id }}-{{ curr.title }}-{{curr.price}}-{{curr.num}}
<button v-on:click="addToCart(curr)">加入购物车button>li>
ul>
div>
<script src="./libs/vue.js">script>
<script>
//vue 创建一个新的 Vue 实例
const vm = new Vue({
el: '#app', //view,element
data: { //model
products: _products, //推荐商品
cart: [], //购物车数组
},
methods: { //方法
//加入购物车
addToCart: function (curr) {
// 如果当前商品已选购,则叠加数量
const product = this.cart.find(item => item.id == curr.id)
if (product) {
product.num++
} else {
this.cart.push({
...curr,
})
}
}
}
})
script>
3.计算勾选商品总价
<div id="app">
<h1>购物车h1>
<ul>
<li v-for="curr in cart">
<input type="checkbox" v-model="curr.checked">
{{ curr.id }}-{{ curr.title }}-{{curr.price}}-{{curr.num}}
<button>删除商品button>
li>
ul>
<div>
合计商品总价:{{ total }}
div>
<script src="./libs/vue.js">script>
<script>
//vue 创建一个新的 Vue 实例
const vm = new Vue({
el: '#app', //view,element
data: { //model
products: _products, //推荐商品
cart: [], //购物车数组
},
computed:{ //计算属性
total(){
return this.cart.reduce((resault,curr,index,arr)=>{
return curr.checked?resault + (curr.price * curr.num):resault
},0)
}
},
})
script>
4.删除商品
<div id="app">
<h1>购物车h1>
<ul>
<li v-for="curr in cart">
<input type="checkbox" v-model="curr.checked">
{{ curr.id }}-{{ curr.title }}-{{curr.price}}-{{curr.num}}
<button v-on:click="removeCart(curr)">删除商品button>
li>
ul>
<script src="./libs/vue.js">script>
<script>
//vue 创建一个新的 Vue 实例
const vm = new Vue({
el: '#app', //view,element
data: { //model
},
computed:{ //计算属性
}
},
methods: { //方法
//从购物车移除
removeCart(curr){
this.cart = this.cart.filter(item=>item.id!==curr.id)
}
}
})
script>
vue沿用了mvvm特性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPYLtcAU-1654654274388)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652088957608.png)]
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
<div id="app">
{{ message }}
div>
<script src="./libs/vue.js">script>
<script>
new Vue({
el: '#app', // View
data: { // Model
message: 'Hello Vue.js'
},
})
script>
现在数据和 DOM 已经被建立了关联,所有东西都是响应式的,示例如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HoIHiUtR-1654654274389)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652153464068.png)]
const vm = new Vue({
el: '',
data: {},
methods: {},
})
注意:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRMZgiHl-1654654274390)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652173816092.png)]
vm.
来调用 data 中的数据)。注意,在 data 中尽量不要定义以 $
或 _
开关的字段,因为可能和 Vue 对象自身的属性冲突(以 $
或 _
开头的数据,不会挂载到 Vue 实例下)vm
(ViewModel 的缩写) 这个变量名表示 Vue 实例。data
对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。data
中的 property 才是响应式的。<div id="app">
{{message}}
<br>
{{nodata}}
<br>
{{ meth1() }}<br>
{{ meth2() }}<br>
{{ meth3() }}<br>
div>
<script src="./libs/vue.js">script>
<script>
const vm = new Vue({
//选项
el: '#app',
data: {
message: 'nihao'
},
methods: {
meth1:function(){
console.log("meth1",this);
return "method---1" //vue
},
meth2() {
console.log("meth2",this);
return "method---2" //vue
},
meth3:()=>{
console.log("meth3",this);
return "method---3" // window,通常不使用箭头函数来书写
},
},
created() { //data外,无响应式渲染
this.nodata = "data外数据--不响应式渲染"
}
})
script>
所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。
“Mustache”语法 (双大括号)
{{ expression }}
{{ }}
中包含的是 JS 的表达式,会进行 html 文本转义,主要是为了避免 XSS
攻击<div v-text="expression">div>
v-text
来绑定渲染文本表达式的值<div v-html="expression">div>
<div v-text="message" v-bind:title="title">div>
v-bind 可简写为 `:`
v-bind:属性名="属性值"
<div v-text="message" :title="title">div>
<span v-html="message" title='title'>
v-html:会解析html标签: => innerHtml
span><br>
<span v-text="message" v-bind:title="title">
v-text:不会解析html标签: =>innerText
span><br>
<span v-once :title="title">
v-once指令:这个值不会响应变化 {{message}}
span>
const vm = new Vue({
el: "#app",
data: {
message: "<input type='text'>输入框",
title:"提示符"
}
})
指令,是在标签中添加的自定义属性,以 v-
作为前缀,有特殊的意义(在 Vue 中会对这些属性进行特殊处理)。
v-
之后的部分,即为指令名称
指令名称之后以冒号表示
以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定
文本:
条件渲染:满足条件的节点即显示,否则隐藏
v-show:
v-if:
<div id="app">
<button @click="visible=!visible">显示/隐藏button>
<div v-if="visible">v-if-显示/隐藏div>
<div v-show="visible">show-显示/隐藏元素2div>
div>
<script src="./libs/vue.js">script>
<script>
const vm = new Vue({
el:"#app",
data:{
visible:true,
}
})
script>
v-else-if:
v-else:
<button v-if="visible">显示/隐藏button>
<button v-else-if="visible2">显示/隐藏2button>
<div v-else>show-显示/隐藏元素div>
<script>
const vm = new Vue({
el:"#app",
data:{
visible:false,
visible2:false,
}
})
script>
v-if
vsv-show
【面试】
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。操作dom树的节点的消失和出现,条件满足创建节点,条件未false,销毁节点
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
列表渲染:通常是对数组进行遍历
<div v-for="n in 10">{{n}}-重复渲染的内容div>
<div v-for="(curr,index) in arr">当前{{curr}},索引{{index}}div>
<div v-for="(val,key,index) in obj">属性值:{{val}},属性:{{key}},索引:{{index}}div>
<div v-for="(val,key) of obj">属性值:{{val}},属性:{{key}}div>
<div v-for="(curr,index) of arr">当前of:{{curr}},索引{{index}}div>
<div v-for="n of 10">{{n}}-重复渲染的内容div>
注意:在进行列表渲染时,尽量为渲染的每一项绑定 key
属性。建议尽可能在使用 v-for
时提供 key
attribute
v-if 与 v-for 一起使用
当在同一个节点中同时使用 v-if 与 v-for 时:
vue2.x:v-for 的优先级高于 v-if
vue3.x:v-if 的优先级高于 v-for
不推荐 同时使用
v-if
和v-for
。
事件处理:
v-on:可简写为 @
可使用 v-on
去接收一个需要调用的方法名称(事件处理程序函数名称)
在内联 JavaScript 语句中调用方法,当在事件处理程序调用时需要显式传递参数时,通常使用这种方式
<div id="app">
<button v-on:click="alertW">点击弹框button>
<button @click="alertM('hi')">点击弹框2button>
div>
<script src="./libs/vue.js">script>
<script>
const vm = new Vue({
el:"#app",
data:{
message:"你好哇"
},
methods:{
alertM(message){
alert(message)
},
alertW(){
alert('what')
}
}
})
script>
属性绑定:
:
表单处理:
你可以用 v-model
指令在表单 input
、textarea
及 select
元素上创建双向数据绑定。
它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。
语法糖解释:class => 并不是真正的类,只是原型链的简单写法,class为语法糖
v-model实现原理语法糖:
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:v-bind:value v-on:input
- text 和 textarea 元素使用
value
property 和input
事件;- checkbox 和 radio 使用
checked
property 和change
事件;- select 字段将
value
作为 prop 并将change
作为事件。
插槽:
其它:
v-pre:跳过这个元素和它的子元素的编译过程,可以用来显示原始 Mustache 标签
<span v-pre>{{这是不被解析的}}span> //{{这是不被解析的}}
v-cloak:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
<style>
[v-cloak]{
display: none;
}
style>
<div id="app" v-cloak>
{{message}}
div>
<script src="./libs/vue.js">script>
<script>
setTimeout(() => {
const vm = new Vue({
el: "#app",
data: {
message: ""
}
})
}, 5000)
script>
未编译时候,渲染{{message}},避免客户不好体验,设置v-cloak属性不可见,编译完毕
v-once:一次
<span v-once>{{message}}span>
v-once指令:这个值不会响应变化 {{message}}
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>Documenttitle>
<style>
* {
padding: 0;
margin: 0;
}
#app {
margin: 100px;
}
ul {
margin-top: 10px;
list-style: none;
}
.finished {
background-color: #ccc;
text-decoration: line-through;
width: fit-content;
}
style>
head>
<body>
<div id="app">
<h3>待办事项h3>
<input type="text" placeholder="请输入新的待办事项" v-model.trim="inputValue" ref="input" @keydown.enter="addtodo">
<button v-on:click="addtodo">添加button>
<div v-if="todos.length === 0">
待办事项列表为空,请添加新待办事项
div>
<ul v-else>
<li v-for="curr in todos" :key="todo.id" :class="{finished: todo.status, abc: todo.status}"
:style="{marginTop: '2px', paddingBottom: '4px'}">
<input type="checkbox" v-model="curr.status">
<span>{{curr.title}} -- {{curr.status?"已":"未"}} 完成span>
<button v-on:click="removeTodo(curr.id,$event)">删除button>
li>
ul>
<input type="checkbox" :checked="checkedAll" @click="chandleCheckAll">
<span>已完成{{completeCount}}/全部{{todos.length}}span>
<button v-on:click="clearAll">清除所有button>
div>
<script src="./libs/vue.js">script>
<script>
let index = 3;
const vm = new Vue({
el: "#app",
data: {
/* 1.动态渲染数组todos数据 */
todos: new Array(3).fill(null).map((_, index) => ({
id: index + 1,
title: "代办事项标题" + (index + 1),
status: Math.random() > 0.5
})),
inputValue: "", //输入框双向绑定
// todos:new Array(3).fill(null).map((curr,index)=>{
// return {
// id:index+1,
// title:"代办事项标题"+ (index+1),
// }
// })
},
computed: {
/*5.全选状态 */
checkedAll() { // 计算是否所有待办事项已完成
// return this.todos.every(item=>item.status==true)
return this.todos.every(item => item.status)
},
completeCount() { //已完成的数量
return this.todos.filter(item => item.status).length
}
},
methods: {
/* 2.点击添加按钮,获取输入框内数据,向数组todos添加一条数据 */
addtodo() {
/*2.1文本框自动获取焦点*/
// document.querySelector('.input').focus() //class="input"
this.$refs.input.focus() //ref="input"
/*2.2输入框空白判断*/
if (this.inputValue !== '') {
this.todos.push({
id: ++index,
title: this.inputValue,
})
/*清空输入框*/
this.inputValue = ''
}
},
/*6.处理全选--点击全选,数组中每一项都为选中*/
chandleCheckAll(event) {
let checked = event.target.checked
this.todos.forEach(item => item.status = checked)
},
/*7.删除当前事*/
removeTodo(id, event) {
console.log("id", id, event);
this.todos = this.todos.filter(item => item.id !== id)
},
/*8.清除所有待办=>3种方法*/
clearAll() {
this.todos = []
}
}
})
script>
body>
html>
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件
可以使用计算属性来代替在表达式中进行的复杂运算,以便于能够更方便的维护与复用逻辑。
计算属性是在选项对象中使用 computed
字段来定义。
特点:计算属性是可被缓存的(计算属性是基于它的响应式依赖进行缓存的,只有当依赖项发生变化时,才会重新计算并缓存)
计算属性有缓存,方法没有缓存
方法中可包含如网络请求类似的副作用操作,计算属性中没有
const vm = new Vue({
el: "#app",
data: {
message: "hello,sea"
},
computed: { //计算属性控制台只打印一次...
//1.计算属性的简写形式,相当于是计算属性的 getter 方法定义
reversedMessage() {
console.log("计算属性...");
return this.message.split('').reverse().join('')
},
//2.计算属性完整写法
reversedMsg: {
get() { // getter,用于获取属性值,比如:console.log(this.reversedMsg)
console.log('计算 reversedMsg...')
return this.message.split('').reverse().join('')
},
set(val) { // setter,用于设置(修改)属性值,this.reversedMsg = 'abc'
console.log('为 reversedMsg 赋值:', val)
},
}
},
methods: { //方法请求n次,控制台打印n次...
reversed() {
console.log("方法...");
return this.message.split('').reverse().join('')
}
}
})
一种更通用的方式来观察和响应 Vue 实例上的数据变动。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
侦听属性是在选项对象中使用 watch
字段来定义。
<div id="app">
{{ message }}
div>
<script src="./libs/vue.js">script>
<script>
const vm = new Vue({
el: '#app', // View
data: { // Model
message: 'Hello Vue.js',
obj: {name: 'infomation', address: { xx: {} }},
},
watch: {
// 1.简写,相当于 handler 处理器
//语法:监听对象(修改后值,修改前值){ }
message(newVal, oldVal) {
console.log('message 变化了...,修改后:', newVal, ',修改前:', oldVal)
// TODO.......
},
// 2.侦听器完整写法
obj: {
handler(newVal, oldVal) { // 处理器
console.log('info 变化了...', newVal, oldVal)
},
immediate: true, // 立即执行(初始渲染时,就执行一个侦听器)
deep: true, // 深度监听
}
},
})
vue 对这七个方法进行了重写,当调用这些方法进行数组修改时,会触发响应式渲染
当调用非变更方法时,不会触发响应式渲染,则可以使用替换数组的方式来进行数组更新。
filter()
、concat()
和slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
组件,即构建应用程序所使用到的部件。
组件系统,允许我们使用小型、独立和通常可复用的组件构建大型应用
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。
const options = {
template: '模板-视图的结构(布局)',
data() {
return {}
},
}
注意:
template 中定义的结构,必须使用单个根元素包裹
data 必须是函数结构,在函数体内部返回一个普通对象
为什么 data 要是函数?
组件是可被复用的,当创建同一个组件的不同实例时,如果 data 是普通对象,则不同的实例引用到的是同一个 data 对象,当任意一个实例中对 data 数据进行更新时,其它实例都会受影响,通常这与实际业务不符。
定义成函数,则创建各组件实例时,会调用 data 函数生成组件实例自身私有用到的对象数据,各组件实例间数据是独立的,互不受影响。
全局注册
Vue.component(name, options)
全局注册的组件可在任意组件中使用到
局部注册
const options = {
components: {
//"name包括-短横线,引号必加" => 组件名字
name: {componentOptions}
}
}
局部注册的组件仅在其父组件内部可使用
<div id="app">
{{message}} <br>
<hello-component>hello-component>
<hello>hello>
div>
<script src="./libs/vue.js">script>
<script>
/*定义组件的选项对象*/
const options = {
//view模板 字符串模板!需要单个根节点元素
template: `
{{ title }}
副标题
{{reversed}}
`,
// data语法:一个函数,return对象
data() { //数据
return {
title: "主标题"
}
},
methods:{
add(){
console.log('按钮事件');
this.title="改变成点击事件"
}
},
computed:{
reversed(){
return this.title.split('').reverse().join('')
}
}
}
//全局组件注册
Vue.component("helloComponent", options)
/*1.创建vue实例*/
const vm = new Vue({
el: "#app",
data: {
message: "hello,baobao"
},
components: {
//局部组件注册 hello标签 == template
hello: {
template: "局部注册的组件
"
}
}
})
script>
利用组件名称作为自定义标签名称使用,来渲染组件。
注意:在使用自定义组件名作为标签名称使用时,要完整书写独立的结束标签,标签名称应该使用短横线命名
的规范。
组件间进行数据的传递称为组件通信。
父传子:利用属性(props)的方式传递数据。
子组件定义时,在选项中使用 props 定义组件可接收的属性。
父组件中使用到子组件(标签)时,在标签内部书写需要传递给子组件的数据(是 name=value 键值对格式的属性)。
<div id="app">
<father>father>
div>
<script>
/*1.全局父组件的name,sex,phone 传递 给子组件*/
Vue.component("father", {
template: `
我是父组件father
`, //未传sex,为默认值
data(){
return {
phone:"1008611"
}
}
,
components: {
son: {
template: `
我是子组件son--{{name}}--{{sex}}--{{phone}}
`,
// props:["name","sex","phone"] /// 声明组件可接收的属性名称
props:{ //声明组件可接收的属性名称及其校验规则
name:String,
sex:{
type:String, //字符串类型
default:"女", //未传值默认项
},
phone: {
type: String,
required: true, //该项为必填项
}
}
}
}
})
/*! 初始化在后面位置*/
const vm = new Vue({
el: "#app",
data: {
message: "我是app"
}
})
script>
子传父:利用事件方式传递数据。
父组件中使用到子组件标签时,利用
v-on
注册一个自定义的事件监听,通常引用在父组件中定义的 methods 方法去处理接收到的数据。在子组件中需要传递数据时,调用
this.$emit(eventName, data)
触发在父组件中绑定的自定义事件并传递数据即可($emit() 的第二个参数是需要传递给父组件的数据)
div id="app">
<father>father>
div>
<script src="./libs/vue.js">script>
<script>
/*1.父组件*/
Vue.component("father", {
template: `
我是父组件father
`,//addFather:父组件的方法 ;add:触发子传父时 this.$emit('add',"data")
methods:{
addFather(data){
console.log("这是子传父的数据",data);
}
},
components: {
son: {
template: `
我是子组件son
`,//addSon:子方法
/*子传父*/
methods:{
addSon(){
console.log("向父组件传递...");
//调用(触发)在父组件中绑定的事件,向父组件传递数据
this.$emit('add', {code: 200, data: {userInfo: {name: '张三', age: 18}}})}
}
}
}
})
/*! 初始化在后面位置*/
const vm = new Vue({
el: "#app",
data: {
message: "我是app"
}
})
script>
转换为父子关系
event-bus(事件总线):·面试
借助 Vue 实例中的 $on()
与 $emit()
方法来实现:首先创建一个全局的 Vue 实例对象(bus
)
//1.全局变量
const eventBus = new Vue()
//2.原型链 new实例之前
Vue.prototype.$bus = new Vue()
在需要接收数据的组件中,利用 bus
来注册事件监听(绑定自定义事件)
created() { //生命周期钩子函数
//1当前组件(APP)会接收数据,调用 eventBus.$on() 绑定事件
eventBus.$on("removeItem", this.removeTodo)
// 2
this.$bus.$on("removeItem", this.removeTodo)
}
在需要传递数据的组件中,利用 bus
来触发事件并传递数据。
methods:{
// 3.1.2当前组件(APP)会传递数据id,调用 eventBus.$emit() 绑定事件
removeTodo(){
// 1.
eventBus.$emit('removeItem',this.item.id)
//2.
this.$bus.$emit('removeItem',this.item.id)
}
}
vuex 插件
问?
生命周期,指的是vue实例从开始创建,到最终销毁,所经历的整个过程。即指从创建,初始化数据,编译模板,挂载Dom到渲染,更新到渲染,销毁等一系列过程,主要分为8阶段…以及一些特殊场景的生命周期,说一下生命周期流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIeRY5dK-1654654274391)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1653124318172.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDLfbYep-1654654274391)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1653124247775.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VrR299MO-1654654274391)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1653124266958.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2on8uRj-1654654274392)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1653124154081.png)]
这个过程中会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
beforeCreate():在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用
created():在实例创建完成后被立即同步调用
beforeCreate() {
console.log('before create:', this.message);
},
created() {
console.log("created", this.message);
},
beforeMount():在挂载开始之前被调用
mounted():实例被挂载后调用,这时 el
被新创建的 vm.$el
替换了。
beforeMount() {
console.log('before mount:',this.message)
},
mounted(){
console.log("mounted",this.message);
},
beforeUpdate():在数据发生改变后,DOM 被更新之前被调用
updated():在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用
beforeUpdated(){
console.log("before updated",this.message);
},
updated(){
console.log("updated",this.message);
},
通常会销毁:启动的定时器、未完成的网络请求、打开的网络socket连接等
beforeDestroy():实例销毁之前调用
destroyed():实例销毁后调用
boforeDestory(){
console.log("destoryed",this.message);
},
destory(){
console.log("destoryed",this.message);
}
作用:内容分发
在组件定义时,可使用
内置组件来占位,定义插槽内容。
//todoHeader.vue文件
待办事项列表
TODOLIST...
//app.vue文件
这是todolist
//渲染的时候
这是todolist
会代替原来全部的h2
在一个组件中,可以使用多个
来定义插槽,要区分这些不同的插槽,就需要给
添加 name
属性,这就是命名插槽(具名插槽)。
待办事项列表
TODOLIST
渲染命名插槽是的语法:
2.6 之前: 即在标签中使用
slot
属性指定插槽名称
这是todolist
2.6 及之后:可使用 v-slot
指令:
副标题
ex:
这是todolist
v-slot 可简写为 #
,即:
副标题
ex:
这是todolist
single-file components
文件后缀(扩展)名为 .vue
解决的问题:
\
如何定义选项?
单文件组件语法:
模板语法
template
:视图结构
script
:交互逻辑
style
:样式
scoped:表示所书写的样式仅支持在当前组件中使用,其它组件不受影响
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件
vue工具:vue Devtools || vue cli
基于 Vue.js 进行快速开发的完整系统。
$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli
安装完毕后,可以在 cmd 命令行中输入:
$ vue --version
如果能够查看到版本信息,则说明安装成功,否则安装失败或环境变量配置有问题。
$ vue ui
$ vue create project-name
显示创建项目的向导
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features #手动配置项目
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and
<enter> to proceed)
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
>(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
? Choose a version of Vue.js that you want to start the project with
3.x
> 2.x
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
Sass/SCSS (with dart-sass)
> Less
Stylus
? Pick a linter / formatter config:
ESLint with error prevention only
ESLint + Airbnb config
> ESLint + Standard config
ESLint + Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to pr
oceed)
>(*) Lint on save
( ) Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
In package.json
? Save this as a preset for future projects? (y/N)
如果是 yes,则还需要输入预设项名称
$ cd project-name
$ npm run serve # 或 yarn serve
public 目录中放置的是应用的 html 文件(通常只有一个 index.html)
src 目录中放置我们自己项目中所书写的源代码
src 下 main.js 是应用的入口 JS 文件,名字勿改
.eslintrc.js
是 ESLint 的配置文件
babel.config.js
是 Babel 的配置文件
package.json
是项目配置文件
vue.config.js
是 Vue CLI 的配置文件(在 VueCLI4.x 中这个文件需要自己手动创建)
// 老项目维护:
module.exports = { ... }
{
"scripts": {
"start": "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
}
ESLint :代码规范报错
Vetur :让代码具有高亮
对于自己的代码习惯,可以添加在eslintrc.js
文件中的rules
,根据提示跳转到对于配置页面,找到options
,将代码复制进文件中
'comma-dangle': ['erro', 'always'],
’ erro ’ == 3 红色报错
’ warn ’ == 2 黄色警告
’ off ’ == 1 关闭提示
设置后,原代码不符用户习惯,出现很多报错,cmd输入命令
npm run lint //根据规则自动格式化
注意:vscode 只打开项目文件,多余文件出现Eslint可能不会识别
标准规则注意点:单文件组件要用多个单词命名
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。---- 工具,用于管理应用中组件间共享的状态(数据)
vuex4 – vue 3
vuex3 – vue 2
采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
$ npm i [email protected]
# 或
$ yarn add [email protected]
src/store/index.js
文件import Vue from 'vue'
import Vuex from 'vuex'
// 使用核心插件 Vuex
Vue.use(Vuex)
// 创建 Store
const store = new Vuex.Store({
state: { // 组件间共享的数据
todos: [],
},
getters: { // store的计算属性
/* 7.是否所有都完成? */
allChecked (state) {
return state.todos.every(item => item.completed)
},
mutations: { // 唯一进行同步更新状态数据的方法
/**
* 添加新待办事项
*/
addTodoItem(state, payload) {
state.todos.push({
id: Math.random(),
title: payload.title,
completed: false,
})
},
},
})
export default store
main.js
注入 storeimport Vue from 'vue'
// 引入 store
import store from './store' // import store from './store/index.js'
new Vue({
store, // 注入 store,这样,在所有后代组件中,都可以使用 this.$store 获取 vuex 的 Store 使用
// render: h => h(App),
}).$mount('#app')
store
中的mutations getters
//使用store中的state
this.$store.state.todos
//辅助函数
import { mapGetters, mapMutations, mapState, mapActions } from 'vuex'
methods: {
...mapMutations(['clear', 'checkAll'])
// clear () {
// this.$store.commit('clear')
// },
// checkAll (event) {
// this.$store.commit('checkAll', { checked: event.target.checked })
// }
},
computed: { // 获取仓库的getters 和 公共数据
...mapGetters(['allChecked', 'checkCount', 'todoCount'])
todos () {
return this.$store.state.todos
}
// allChecked () {
// return this.$store.getters.allChecked
// },
// checkCount () {
// return this.$store.getters.checkCount
// },
// todoCount () {
// return this.$store.getters.todoCount
// }
},
样式css美化
Bootstrap – 响应式布局 栅格系统
Bulma – css类名库
字体图标库
安装
npm install bulma
src/main.js
/* 引入模块*/
import 'bulma/css/bulma.min.css'
官网copy喜欢的结构components
让图标显示
<script src="https://kit.fontawesome.com/304fcfb799.js" crossorigin="anonymous">script>
单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新
该页面的Web应用程序。
利用 hash 或 history 改变不会向后端发起新的 html 请求特点
hash:利用 URL 中 #hash
hash 值改变后,不会发送新的网络请求的特点,来实现的路由。使用如 #/home
、#/login
类似的方式来表示所请求的前端 URL 资源。
window.onhashchange = function ()
{ console.log('hash changes', location.hash)}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-14OAV0SJ-1654654274393)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652690682250.png)]
history:利用 h5 中 history 新增的 API pushState()
(往历史记录添加)、replaceState()
( 替换当前历史记录 ) 来实现。其路由的格式与服务端路由 URL 格式一致(比 hash 模式来说,没有多余的如 #
之类的符号),所以,这种路由模式要用好,还需要服务端配置(运维工作)(刷新当前页面会认为向服务端请求资源,会出现404所以需要服务端配置)。
是 Vue.js 官方的路由管理器,是一个核心插件,与 Vue.js 高度集成。
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举
$ npm i [email protected]
# 或
$ yarn add [email protected]
src/views/Home.vue | src/views/About.vue
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home'
import Login from '@/views/Login'
// 使用路由插件
Vue.use(VueRouter)
// 创建 VueRouter 对象
const router = new VueRouter({
mode: 'hash', // 路由模式,可取 hash、history,默认为 hash
routes: [ // 静态路由的配置信息
{
path: '/home', // 当 URL 中访问地址为 /home 时
component: Home, // 拿 Home 组件渲染
},
{
path: '/login',
component: Login,
},
],
})
// 导出路由对象
export default router
在 Vue 组件中使用 VueRouter 时,可使用 VueRouter 提供的两个内置组件:
import App from './routerApp.vue'
// 引入 router
import router from './router'
new Vue({
router,
// render: h => h(App),
}).$mount('#app')
当在 Vue 根实例中注入了 router 后,在 routerApp.vue 组件中会自动注入如下两个属性:
3.src/routerApp.vue
home主页 |
about关于我们
npm run serve
/* 重定向:在访问根目录的时候直接访问当前地址 */
routes: [ // 静态路由的配置信息
{
path: '/',
redirect: '/home' // 重定向
},
{
path: '/home', // 当 URL 中访问地址为 /home 时
component: Home // 拿 Home 组件渲染
},
导航到不同的位置 注意:在 Vue 实例中,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
导航方法:
handleClick () {
//push 当用户点击浏览器后退按钮时,会回到之前的 URL。
this.$router.push('./todo')
//replace 当用户点击浏览器后退按钮时,不会回到之前的 URL是取代。
this.$router.replace('./todo')
//path 属性
router.push({ path: '/home', replace: true })
// =====
router.replace({ path: '/home' })
//除了path之外,也可以通过name
this.$router.push({
name: 'todo'
})
}
back() {
this.$router.back()
this.$router.forward()
this.$router.go()
},
时,内部会调用这个方法,所以点击
相当于调用 router.push(...)
路由传参:
/* 字符串 =>传参 */
//1.
this.$router.push('./todo?id=12345&title=你好')
location.href=> http://localhost:8080/#/todo?id=12345&title=%E4%BD%A0%E5%A5%BD
//2.
this.$router.push({
name: 'todo',
query: {
id: 345,
title: 'lanlan',
price: 212.123,
amount: 9
}
})
location.href=> http://localhost:8080/#/todo?id=345&title=lanlan&price=212.123&amount=9
=> app2.vue
created () {
//route 激活路由
console.log(this.$route)
}
log=> obj.query
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEX7oFK0-1654654274394)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652700930073.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nS8QPBLJ-1654654274394)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652700830455.png)]
/* 动态路径传参 */
{
path: '/todo/:id', // /:id动态路径传参
name: 'todo',
component: todoList // 渲染App2 组件
}
routerApp.vue
this.$router.push({
name: 'todo',
params: {
id: 2000
}
})
//注意:path会忽略 params
//一般只传 1-2 个参数
location.href =>http://localhost:8080/#/todo/2000
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZSbLNpF-1654654274395)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1652702078267.png)]
//主页 /category
获取方法1:点击获取id,发起请求
获取方法2:路由嵌套
<div class="right">
<router-view></router-view>
</div>
//子路由 /category/sub
import SubCategory from '@/views/category/sub-category'
{
path: '/category',
name: 'category',
// component: Category,
components: {
footer: AppTabbar,
default: Category,
header: AppNavbar,
},
children: [ // 子路由,嵌套路由
{ /* 'sub/:id' => 完整路径:/category/sub,:id 是定义的动态路径参数,用于接收传递的分类id
* /sub,/ 代表根路径 => /sub
*/
path: 'sub/:id',
name: 'sub',
component: SubCategory,
},
],
},
通用的已封装好的基础组件,可在各种类型项目中使用。
印记中文–vue( mobile ) vue( pc )
=>难 云信 音频视频,
前端后端配合
电商 – 移动端
利用 vue cli 创建项目:选择 vue、babel、vue-router、vuex、less、linter 待功能
配置 eslint 规则(特别是团队协作时,eslint 规范很重要)
.eslintrc/
/* 配置规则总有逗号 */
'comma-dangle': ['error', 'always-multiline'],
'vue/multi-word-component-names': 0,
配置应用开发服务器,如:端口、主机、自动打开浏览器功能等…
vue.config.js/
老版本如下内容:
module.exports = {
devServer: {
port: 3000,
host: 'localhost', // 0.0.0.0 别人用你的ip就能看到界面
open: true,
},
}
创建页面级 .vue 文件
views/home/index.vue
views/category/index.vue
...
定义路由
router/routes.js =>单独写所有页面的路由
/* 由于每个组件页面下的文件命名都为index.vue所以书写路径可省略 */
import Home from '@/views/home'
import Detail from '@/views/detail'
import Login from '@/views/login'
import Mine from '@/views/mine'
import Cart from '@/views/cart'
import Category from '@/views/category'
const routes = [
{ /* 重定向 */
path: '/',
redirect: 'home',
},
{
path: '/home',
name: 'home',
component: Home,
},
{
path: '/detail',
name: 'detail',
component: Detail,
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/mine',
name: 'mine',
component: Mine,
},
{
path: '/cart',
name: 'cart',
component: Cart,
},
{
path: '/category',
name: 'category',
component: Category,
},
{ /* 通配符路由 */
path: '*',
component: Notfount,
},
]
export default routes
{ path: '*' }
通常用于客户端 404 错误引入项目使用的 UI 组件库
移动端–vant2–vue2
安装
npm i [email protected] #版本号去npm.js找对应版本下载量最多的
示例工程
引入组件:
2.1 推荐使用自动按需引入组件
# 安装插件
npm i babel-plugin-import -D
# babel.config.js 中配置文件 || 在.babelrc 中添加配置(老版本)
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true,
}, 'vant'],
],
# 接着你可以在代码中直接引入 Vant 组件
插件会自动将代码转化为方式二中的按需引入形式
import { Button } from 'vant';
ex:全局注册
import Vue from 'vue'
import { Button } from 'vant'
// 方式一. 通过 Vue.use 注册
// 注册完成后,在模板中通过 或 标签来使用按钮组件
Vue.use(Button)
主页
按钮
src/utils/vant-import.js
import Vue from 'vue'
import {
Button, Cell, CellGroup,
} from 'vant'
// 方式一. 通过 Vue.use 注册
// 注册完成后,在模板中通过 或 标签来使用按钮组件
Vue.use(Button)
Vue.use(Cell)
Vue.use(CellGroup)
...
src文件入口/main.js
/* 引入单独的vantjs文件 */
import './utils/vant-import'
2.2 导入所有组件=>可能vant组件你全部都用到
src入口文件/main.js
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
axios 二次封装
接口文档使用
import 异步任务,实现懒加载技术
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
每个人页面主题不一致,颜色不一致,难看,需要统一
按需引入样式
babel.config.js配置
plugins: [
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
// 指定样式路径
style: (name) => `${name}/style/less`,
},
'vant',
],
],
修改样式变量
// vue.config.js
module.exports = {
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
// modifyvars 修改变量:里面自定义
modifyVars: {
// 直接覆盖变量
'text-color': '#111',
'border-color': '#eee',
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "your-less-file-path.less";`,
},
},
},
},
},
};
vue-cli 搭建的项目,可以在 vue.config.js
中进行配置。
i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称
ajax( 步骤分散 ) => JQuery( 封装ajax $ajax) =>ES6 promise ( 避免回调地狱 )=> axios (基于 ajax 和 promise 进行封装的库,核心原理是 Ajax,封装使用 promise管理,解决异步并行问题 ) => fetch ( 内置的类,进行数据请求的,天生就是基于promise 进行管理的 )
优点:
减少对外部依赖项的加载,不用安装三方包
写法简单
兼容使用polyFill
fetch()
必须接受一个参数——资源的路径。无论请求成功与否,它都返回一个 Promise 对象,resolve 对应请求的 Response
对象。你也可以传一个可选的第二个参数 init
(参见 Request
)
IE
兼容问题
默认请求方式get
,可以fetch(一参,二参)
测试接口:get请求
home.vue组件实现:
created () {
/* 网络请求方法1:fetch API */
const result = fetch('https://jsonplaceholder.typicode.com/users')
console.log('result', result)
result
.then(res => res.json())
.then(data => {
console.log(data)
})
// 调用两次then
},
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
特性
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
使用 npm:
$ npm install axios qs
qs: 对象转化为ur url转化成对像
axios二次封装 【面试】
2.配置scripts字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NdQgVo9n-1654654274395)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1654246759625.png)]
src/utils/request.js
import axios from 'axios'
import { Toast } from 'vant'
/* 定义 baseURL -- 适应不同环境请求不同接口 */
// const baseURL = process.env.NODE_ENV === 'development' ? '开发测试接口' : '生产服务器接口'
// const baseURL = process.env.NODE_ENV === 'development' ? 'http://102.666.6.6' : 'http://www.test-site.com'
const baseURL = ''
/* 创建 axios 实例 */
const service = axios.create({
baseURL,
timeout: 10000,
})
// 拦截请求
service.interceptors.request.use(config => {
// 加载提示
Toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0, // 展示时间,0为不消失
})
// 添加请求头中的token 认证数据
// ...
return config
})
// 拦截响应
service.interceptors.response.use(resData => {
// 关闭提示
Toast.clear()
// 响应数据处理:根据前后端接口规范来处理数据结果
if (resData.status === 200) {
return resData.data
}
return Promise.reject(new Error('接口请求异常...'))
})
export default service
调用方法:
home.vue组件实现:
import request from '@/utils/request'
created () {
/* 网络请求方法2:axios */
request({
url: 'https://jsonplaceholder.typicode.com/users',
method: 'GET',
}).then(data => console.log(data))
// request.get()
// request.post()
},
/* 引入二次封装的axios文件 */
import request from './utils/request'
Vue.prototype.$http = request
每个组件中使用方便
created () {
/* 网络请求方法2:axios */
this.$http({
url: 'https://jsonplaceholder.typicode.com/users',
method: 'GET',
}).then(data => console.log('$http', data))
},
this.$http
名字 是axios
封装的 还是 插件vue-resourse
去公司会遇到各种格式…
txt | doc | swagger | 公司工具…
swagger文档理解
案例接口地址:http://quanzhan.site:3000/swagger/
统一处理网络请求
src/api/constants.js
/* 很多页面需要调用当前接口,采取变量保存;
改版接口时,只需要改一次变量值,方便维护
*/
const API = {
/* api/tabs 获取所有分类信息 */
TABS_API: 'api/tabs',
}
export default API
面试刚需:你有自己写过组件吗?
意思是说自己封装使用组件的这个过程,每个公司业务不一样,需要你进行整理封装复用
vant – tabbar标签栏
不使用vant组件,自己如何封装定义vue插件tabbar呢?
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到
Vue.prototype
上实现。- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
src/components/tabbar/tab-bar.vue
定义插件
// 底部导航的布局
this.$router.push(path).catch(()=>{})
静默处理(对于异常什么也不做)src/components/tabbar/index.js
开发插件
import Tabbar from './tab-bar'
/* 开发插件 */
// 自定义 Vue 插件 暴露install方法 官网
const plugin = {
install (Vue) {
// 全局注册自定义的 tabbar 组件
Vue.component('tao-tabbar', Tabbar)
},
}
export default plugin
home/index.vue
渲染插件
home/index.vue
定义tabbar 动态数组
/* tabbar数组 */
const list = [
{
pagePath: '/home', // 点击跳转路径
text: '首页',
icon: 'index', // icon图标
},
{
pagePath: '/category',
text: '分类',
icon: 'cat',
},
{
pagePath: '/cart',
text: '购物车',
icon: 'cart',
},
{
pagePath: '/mine',
text: '我的',
icon: 'mine',
},
]
多个页面跳转,当前tabbar
要激活
将之前定义的组件外部添加标签
router-link
配置 tag
属性生成别的标签
class
与style
绑定
router-link
标签带来的class类router-link-exact-active
| router-link-active
v-slot
作用域插槽 是个对象,无slot
生成a
标签
v-slot="{ href, route, navigate, isActive, isExactActive }"
href
:解析后的 URL。将会作为一个a
元素的href
attribute。route
:解析后的规范化的地址。navigate
:触发导航的函数。会在必要时自动阻止事件,和router-link
同理。isActive
:如果需要应用激活的 class 则为true
。允许应用一个任意的 class。isExactActive
:如果需要应用精确激活的 class 则为true
。允许应用一个任意的 class。
激活两种方法