Vue
本文件已定稿,最后修改时间 20240822 16:27
一、Vue脚手架
vue-cli:框架 —-> 脚手架
安装
创建项目
vue create 项目名称
.vue文件
三大部分:
template:盒子布局的
script:js逻辑
style:css样式main.js
只要运行vue项目,main.js就会执行
index.html —> main.js —> App.vue
vue是”数据”驱动
定义数据:
<script>
export default {
data(){
return{
str:'123'
...
}
}
}
</script>模板语法
<template>
<div>
<h1>{{ str }}</h1>
</div>
</template>
二、Vue指令
指令(v-xxx)
v-bind
:单向绑定v-bind:属性名='数据'
简写:
:属性名='数据'
指令不需要加入{{}}
列表渲染和条件渲染
列表渲染(v-for)
<template>
<div id="app">
<ul>
<li v-for='(item,index) in arr' :key='index'>
{{ item }}
{{ index }}
</li>
</ul>
</div>
</template>条件渲染(v-if)
<h1 v-if='types=="张三"'>
11111
</h1>
<h1 v-else-if='types=="李四"'>
22222
</h1>
<h1 v-else>
33333
</h1>事件添加
<h1 v-on:click='方法'></h1>
简写:
<h1 @click='方法'></h1>
注意:vue-cli中的方法要添加在methods
【面试题】v-if 和 v-for 的优先级
v-for比v-if高
正常的使用上来说
v-if
v-for
v-if和v-show的区别
v-if
创建与删除的操作
v-show
显示和隐藏(
display:none;、display:block;
)
【面试题】两者区别
v-if 是创建和删除节点、v-show 是隐藏和显示节点
如果用户频繁切换的状态应该用v-show
如果一刷新进入页面的状态v-show的性能差一点
v-model的使用
v-bind
:单向绑定v-model
:双向绑定
计算属性
模板语法内如果添加特别复杂的逻辑,就让模板表达式比较重而且难以维护,对于任何复杂逻辑,都应当使用计算属性
<script>
export default {
data () {
return {
str:'123'
}
},
computed:{
changeStr(){
return this.str.split('').reverse().join('');
}
}
}
</script>计算属性(computed)是基于他们的响应式依赖进行缓存的
- 【面试题】computed 和 methods 区别
- computed是有缓存的(如果计算的属性没有发生变化,则执行缓存数据)
- methods是没有缓存的(也就是template只要render了 [或者重复render] 就会执行methods内所有的方法)
<template>
<div id="app">
<!-- {{ changeStr }} -->
单价:
<input type="" name="" v-model='price'>
数量:
<input type="" name="" v-model='num'>
总价:{{ total }}
<hr />
{{ str }}
<button @click='btn'>按钮</button>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
//str:'123'
price:'11',
num:2,
str:'你好'
}
},
computed:{
total(){
console.log('computed');
return this.price * this.num;
}
},
methods:{
btn(){
this.str = '不好';
},
totalConut(){
console.log( 'methods' );
return this.price * this.num;
}
}
// computed:{
// changeStr(){
// return this.str.split('').reverse().join('');
// }
// }
}
</script>- 【面试题】computed 和 methods 区别
class和style
class
对象的写法
:class='{ class名称:true|false }'
<template>
<div id="app">
<ul>
<li v-for='(item,index) in list' :key='index' :class='{active:false}'>
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:['a','b','c']
}
}
}
</script>
<style>
.active{
background:red;
}
</style>用于判断各种赋值的
:class=' currentClass == index ? "active" : "" '
点击某一个加上颜色
<template>
<div id="app">
<ul>
<li v-for='(item,index) in list' :key='index'
:class=' currentClass == index ? "active" : "" '
@click='btn(index)'>
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:['a','b','c'],
currentClass:0
}
},
methods:{
btn(index){
this.currentClass = index;
}
}
}
</script>
<style>
.active{
background:red;
}
</style>数组的写法
<div :class='[activeClass]'></div>
<template>
<div id="app">
<ul>
<li v-for='(item,index) in list' :key='index'
:class=' currentClass == index ? "active" : "" '
@click='btn(index)'>
{{ item }}
</li>
</ul>
<div :class='[activeClass]'>
aaa
</div>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:['a','b','c'],
currentClass:0,
activeClass:"active"
}
},
methods:{
btn(index){
this.currentClass = index;
}
}
}
</script>
<style>
.active{
background:red;
}
</style>
style
<template>
<div id="app">
<ul>
<li v-for='(item,index) in list' :key='index'
:class=' currentClass == index ? "active" : "" '
@click='btn(index)'>
{{ item }}
</li>
</ul>
<div :class='[activeClass]'>
aaa
</div>
<div :style='styleClass'>
这是style的写法
</div>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:['a','b','c'],
currentClass:0,
activeClass:"active",
styleClass:{
background:'red',
fontSize:'50px'
}
}
},
methods:{
btn(index){
this.currentClass = index;
}
}
}
</script>
<style>
.active{
background:red;
}
</style>
img的src路径问题
img :src
一旦是绑定的数据,那么他会类似于请求的数据了,需要从路径上查找没有绑定在src目录下找
<img src="./assets/img/log0.png">
绑定了在public目录下找
<img :src="">
事件处理
监听事件
v-on:事件名称=''
简写:
@事件名称=''
vue中的事件 —- 和dom事件一致
click、mouseover
<template>
<div id="app">
<div class='main' @click='btn($event,"你好")' ></div>
<div class='main' @mouseover='overBtn' @mouseout='outBtn'></div>
<input type="" name="" @keyup.enter='upBtn'>
</div>
</template>
<script>
export default {
name: 'App',
methods:{
btn( e,val ){
console.log(e,val);
},
overBtn(){
alert('移入');
},
outBtn(){
alert('移出');
},
upBtn(){
alert('111111');
},
}
}
</script>
<style type="text/css">
.container{
width: 500px;
height: 500px;
background: blue;
}
.main{
margin: 10px;
width: 300px;
height: 300px;
background: red;
}
</style>事件修饰符
.stop、.prevent、.capture、.self、.once、.passive
<input type="" name="" @keyup.enter='upBtn'>
<template>
<div id="app">
<div class='main' @click='btn($event,"你好")' ></div>
<div class='main' @mouseover='overBtn' @mouseout='outBtn'></div>
<input type="" name="" @keyup.enter='upBtn'>
<div class='container' @click='parentNode'>
<div class='main' @click.stop='childNode'></div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
methods:{
btn( e,val ){
console.log(e,val);
},
overBtn(){
alert('移入');
},
outBtn(){
alert('移出');
},
upBtn(){
alert('111111');
},
parentNode(){
alert('父');
},
childNode(){
alert('子');
}
}
}
</script>
<style type="text/css">
.container{
width: 500px;
height: 500px;
background: blue;
}
.main{
margin: 10px;
width: 300px;
height: 300px;
background: red;
}
</style>
Vue项目做自适应
局部引入(单独某一页面生效).vue文件中引入
在main.js中全局引入
import './assets/js/flexible.js'
三、Vue组件
什么是组件
把较大的网页,进行功能模块的拆分
组件的操作
组件的首字母要大写
父组件引入子组件
<template>
<div id="app">
<Header></Header>
<Swiper></Swiper>
<Icons></Icons>
</div>
</template>
<script>
import Header from './components/Header'
import Swiper from './components/Swiper'
import Icons from './components/Icons'
export default {
name:'App',
components:{
Header,
Swiper,
Icons
}
}
</script>
父组件传值给子组件
父组件
<Swiper :xxx='parentStr'></Swiper>
xxx 是一个名称 = ‘ 这里是属性值 ‘
<script>
import Swiper from './components/Swiper.vue'
export default {
name:'App',
data(){
return {
parentStr:'这是父组件的数据',
}
},
components:{
Swiper
}
}
</script>子组件
<template>
<div>
{{ xxx }}
</div>
</template>export default {
props:['xxx'],
}
-----------------
export default {
props:{
xxx:String,
}
}
子组件传值给父组件
父组件
<template>
<div>
<Child @changeEvent='fn'></Child>
</div>
</template>
<script>
import Child from './components/Child'
export default {
name:'App',
components:{
Child
},
methods:{
fn(val){
alert(val);
}
}
}
</script>子组件
<template>
<div>
子组件:{{ str }}
<button @click='btn'>按钮</button>
</div>
</template>
<script>
export default {
data(){
return {
str:'这是子组件的数据'
}
},
methods:{
btn(){
// changeEvent自定义事件名称
this.$emit('changeEvent',this.str);
}
}
}
</script>
兄弟组件之间的传值(bus)
bus.js
import Vue from 'vue'
export default new Vue;A.vue
<template>
<div>
AAA
<button @click='btn'>这是A的按钮</button>
</div>
</template>
<script>
import bus from './bus'
export default {
data(){
return {
aStr:'这是A的数据'
}
},
methods:{
btn(){
bus.$emit('changeStr',this.aStr);
}
}
}
</script>B.vue
<template>
<div>
BBB
</div>
</template>
<script>
import bus from './bus'
export default{
computed:{
bStr(){
bus.$on("changeStr",(res)=>{
alert(res);
})
return 111;
}
}
}
</script>
scoped以及原理
组件内样式局部化:
<style scoped></style>
原理
加入了scoped,就会在节点上添加自定义属性
data-v-xxx
css选择器,根据属性选择最终添加样式
slot使用以及场景
Header.vue
<slot>
<div>
{{ title }}
</div>
</slot>App.vue
<Header title='分类'></Header>
<Header>
<input placeholder="请输入">
</Header>使用场景:
Vue插件的使用(swiper插件为例)
下载
npm install vue-awesome-swiper -S
引入
全局引入在main.js
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper)
局部按需引入:单组件引入
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
export default {
components:{
swiper,
swiperSlide
}
}
配置
相关文档
样式穿透
通用(在选择器前面加入):
::v-deep
stylus:
>>>
sass和less:
/deep/
四、生命周期和接口请求
Vue生命周期
是什么?
vue中每一个组件都是独立的,每一个组件都有自己的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁
有哪些?
beforeCreate、created
beforeMount、mounted
beforeUpdate、updated
beforeDestroy、destroyed
打开一个组件会执行哪些生命周期
beforeCreate、created
beforeMount、mounted
this.$data
:组件的data数据 、created时有this.$el
:组件的template节点、mounted时有
axios和代理配置
vue项目中axios进行接口的请求
下载
npm install axios -S
引入
在main.js中
import axios from 'axios'
Vue.prototype.axios = axios;
使用
GET
<template>
<div>
<ul>
<li v-for='(item,index) in list' :key='index'>
{{ item.name }}
<img :src="item.mgurl">
</li>
</ul>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:[]
}
}
components:{
HelloWorld
},
created(){
this.axios({
url:'xxx',
params:{
page:1,
size:3
}
}).then(res=>{
this.list = res.data.data
// console.log(res.data.data);
})
}
}
</script>POST
<template>
<div>
<ul>
<li v-for='(item,index) in list' :key='index'>
{{ item.name }}
<img :src="item.mgurl">
</li>
</ul>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
list:[]
}
}
components:{
HelloWorld
},
created(){
this.axios({
url:'xxx',
method:"POST",
data:{
page:1,
size:3
}
}).then(res=>{
this.list = res.data.data
// console.log(res.data.data);
})
}
}
</script>
vue项目在开发阶段设置代理
项目根目录需要新建vue.config.js
配置代理
module.exports = {
devServer:{
proxy:{
'/api':{
// target:'<url>',
target:'http://localhost:3000',
changeOrigin:true
}
}
}
}
// /api === http://localhost:3000
axios({
url:'/api'
})
后端
全局安装
npm install express-generator -g
创建项目
express --view=ejs 项目名称
安装依赖
cd server
npm install
npm start
后端接口
server / routes / index.js
五、Vue路由
路由安装和介绍
(router、单页面应用、SPA)
单页面应用:一个url(只有一个html)
跳转不同组件
路径传值(url传值)
拦截
使用
vue create 项目名称
选择自定义安装:安装Router
目录结构:
- router / index.js:路由的配置
- views:页面(组件)
- components:页面模块(页面中的组件)
router-link相关配置
to:表示目标路由的链接
跳转形式
<router-link to="/my" tag='div'>我的</router-link>
<router-link :to='{path:"/my"}'>我的</router-link>
<router-link :to='{name:"Cart"}'>购物车</router-link>
路径传值
<router-link :to='{ name:"Cart", query:{a:1} }'>购物车</router-link>
tag:默认生成a标签,如果希望修改
tag='li'
组件形式的跳转方式
replace
<router-link :to='{ path:"Cart" replace }'>购物车</router-link>
append
exact:精准路由匹配模式
js的跳转方式
router.push
:打开新页面,并且可以返回上一页this.$router.push('/list');
router.replace
:打开新页面,不能返回上一页this.$router.replace('/list');
router.go
:打开新页面,跳转几层this.$router.go();
router.back
:返回上一页this.$router.back();
传值
this.$router.push({
path:'/list',
query:{
type:'selectList'
}
});
router内文件配置
const routes = {
// 直接引入
{
path:"/",
name:"Home",
component:Home,
// 二级路由
children:[
{
path:'city',
name:'city',
component:()=>import("../views/About.vue"),
},
]
},
// 懒加载路由
{
path:"/about",
name:"About",
component:()=>import("../views/About.vue"),
},
// 重定向
{path:'*',redirect:'/'}
};路径传值
传值
this.$router.push({
path:'/list',
query:{
a:1
}
});接收
created(){
console.log( this.$route.query.a );
}
导航守卫(拦截)
全局
router.beforeEach((to,from,next)=>{})
router.afterEach((to,from,next)=>{})
组件内守卫
beforeRouteEnter((to,from,next)=>{})
beforeRouteUpdate((to,from,next)=>{})
beforeRouteLeave((to,from,next)=>{})
路由独享
beforeEnter((to,from,next)=>{})
to
:这是你跳转到哪个路由对象from
:这是你要离开的路由对象next
:是一个方法,可以接受参数,这个方法必须调用next()
:告诉保安要过去,去哪里就是tonext(false)
:可以不通过,中断跳转next('/')
:保安不让过,你可以去另一个地方进行通过
router.beforeEach((to,from,next)=>{
let userInfo = false;
if(to.name == 'About'){
if(!userInfo){
router.push('/login')
}
}
next();
})watch:监听
监听路由的变化
watch:{
$route(to,from){
// to:最新的路由
// from:上一次的路由
console.log(to.path, from.path);
}
}监听数据的变化
监听b但是是b的c改变了,所以要监听b内所有的属性变化,就要用到深度监听
<template>
<div class="about">
<h1>A:{{ a }}</h1>
<h2>B:{{ b.c }} </h2>
<button @click='btn'>按钮</button>
</div>
</template>
<script type="text/javascript">
export default{
data () {
return {
a:1,
b:{
c:1
}
}
},
methods:{
btn(){
this.a = 2;
this.b.c = 2;
}
},
watch:{
a(to,from){
//to ==> 最新的
//from ==> 上一次的
console.log(to,from)
},
b:{
handler(to){
console.log('B:',to.c)
},
//深度监听
deep:true
}
}
}
</script>
六、Vuex
vuex:状态管理(集中式存储管理应用的所有组件的状态)
vuex的属性:
state: {}, // 放入数据[类似于组件中的data] |
Vuex中的state
state数据如何拿到
// 方式一
{{ $store.state.a }}// 方式二
{{ a }}
{{ b }}
{{ arr }}
<script>
import { mapState } from 'vuex'
export default{
computed:{
...mapState(['a','b','arr'])
}
}
</script>Vuex中的getters
/ store / index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
list:[
{
goods_id:1,
goods_name:'鞋',
goods_num:3,
goods_price:299
},
{
goods_id:2,
goods_name:'衣服',
goods_num:2,
goods_price:49
}
]
},
getters:{
total( state ){
let obj = {
count:0,
num:0
}
state.list.forEach(v=>{
//总价
obj.count += v.goods_num * v.goods_price;
//总数量
obj.num += v.goods_num;
})
return obj;
}
},
mutations: {},
actions: {},
modules: {},
});共:{{ total.num }}
总计:{{ total.count }}
<script>
import { mapState, mapGetters } from 'vuex'
export default{
computed:{
...mapState(['list']),
...mapGetters(['total'])
}
}
</script>Vuex中的mutations
<template>
<div class="home">
<h1>这是首页Home</h1>
<ul>
<li v-for='(item,index) in list' :key='index'>
{{item.appName}}
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
import {mapMutations,mapState} from 'vuex'
export default {
name: "Home",
created(){
this.getData();
},
computed:{
...mapState(['list'])
},
methods:{
...mapMutations(['initData']),
getData(){
axios({
url:'http://39.101.217.150:8075/apps/list',
params:{
page:1,
size:3
}
}).then(res=>{
this.initData(res.data.data.records);
})
}
}
};
</script>export default new Vuex.Store({
state: {
a:1,
list:[]
},
mutations: {
initData( state , data ){
state.list = data;
},
changeBtn( state ){
state.a = 'abc'
}
},
actions: {},
modules: {},
});Vuex中的actions
<template>
<div class="home">
<ul>
<li v-for='(item,index) in list' :key='index'>
<input type="radio" :checked='item.checked'>
</li>
</ul>
<label @click='checkedFn'>
<input type="radio" :checked='checkAll'/>全选
</label>
</div>
</template>
<script type="text/javascript">
import {mapState,mapGetters,mapActions} from 'vuex'
export default{
computed:{
...mapState(['list']),
...mapGetters(['checkAll'])
},
methods:{
...mapActions(['checkedFn'])
}
}
</script>import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
list:[ //===> length 6
{checked:false},
{checked:false},
{checked:false},
{checked:false},
{checked:false},
{checked:false}
],
selectedList:[]
},
getters: {
checkAll( state ){
return state.list.length == state.selectedList.length;
}
},
mutations: {
//全选
checkAll( state ){
state.selectedList = state.list.map(v=>{
v.checked = true;
})
},
//全不选
unCheckAll( state ){
state.list.forEach(v=>{
v.checked = false;
})
state.selectedList = [];
}
},
actions: {
checkedFn({commit,getters}){
getters.checkAll ? commit('unCheckAll') : commit('checkAll')
}
},
modules: {},
});mapState
、mapGetters
放在组件中的computed中mapMutations
、mapActions
放在组件中的methods中
actions和mutations的区别【面试题】
action 提交的是 mutation,而不是直接变更状态
mutations 是同步的、action 可以包含任意异步操作
action 更加容易调试
Vuex中的modules
目录结构:
// index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import path from './modules/path'
import order from './modules/order'
export default new Vuex.Store({
modules: {
path,
order
}
});// part.js
export default{
state:{
list:['地址1','地址2']
},
getters:{},
mutations:{
btn(){
alert(1);
}
},
actions:{}
}<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<button @click='btn'>按钮</button>
地址:{{pathList}}
订单:{{orderList}}
</div>
</template>
<script>
import {mapState,mapMutations} from 'vuex'
export default {
name: "Home",
computed:{
...mapState({
pathList:state=>state.path.list,
orderList:state=>state.order.list
})
},
methods:{
...mapMutations(['btn'])
}
};
</script>
七、Vue面试题
双向绑定的原理
通过数据劫持结合发布者订阅者模式,使用Object.defineProperty方法对每个属性的get和set进行拦截,当数据发现变化时,发布消息给订阅者,触发相应的监听回调,实现视图和数据的双向同步更新
Object.defineProperty(对象,'属性',{
// 设置劫持
set(){
},
// 获取劫持
get(){
}
})
<html>
<head>
<title></title>
</head>
<body>
<input type="" name="" id='btnInput'>
<h1 id='h1'></h1>
<h2 id='h2'></h2>
<script type="text/javascript">
let btnInput = document.getElementById("btnInput");
let h1 = document.getElementById('h1');
let h2 = document.getElementById('h2');
let obj = {};
//只要赋值,给我console.log( '赋值了' )
//obj.xx = '123';
Object.defineProperty(obj,'names',{
//设置劫持
set( val ){
h1.innerHTML = val;
h2.innerHTML = val;
console.log( '设置' );
names=val;
},
//获取劫持
get(){
return names;
}
})
btnInput.oninput = function(){
obj.names = this.value;
}
</script>
</body>
</html>Object.freeze性能优化
用于冻结对象,禁止对于该对象的属性进行修改(由于数组本身也是对象,因此该方法可以对数组使用)
async created(){
let res = await axios({
url:'xxx',
params:{
page:1,
size:3
}
})
this.list = Object.freeze(res.data.data.records);
}Vue生命周期
vue的生命周期有哪些
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed一旦进入组件或者页面,会执行哪些生命周期
beforeCreate
created
beforeMount
mounted如果使用了keep-alive会多出来俩个生命周期
activated
deactivated如果使用了keep-alive第一次进入组件会执行5个生命周期
beforeCreate
created
beforeMount
mounted
activated如果使用了keep-alive第二次或者第N次,每次都会执行一个生命周期
activated
v-show和v-if的区别
- v-show:显示和隐藏 :
display:none
进行隐藏 、display:block
进行显示 - v-if:创建和删除:remove、append
- 区别:
- 显示和隐藏用:v-show
创建和删除用:v-if - 频繁切换用:v-show
不频繁切换用:v-if - 首次加载:用v-if,不用v-show
- 如果用v-if可以没有这个盒子,然后再通过v-if进行创建(但是第一次进入页面是没有这个盒子,是不加载的)。
- 如果用v-show这个盒子不管是显示还是隐藏,该盒子都是在的(节点都是存在)
- 显示和隐藏用:v-show
- 使用场景:
- v-show : 加入购物车、分享、蒙层这种都基本上用v-show
v-if : 首页栏目切换的时候v-if
- v-show : 加入购物车、分享、蒙层这种都基本上用v-show
- v-show:显示和隐藏 :
v-if和v-for的优先级
v-for的优先级要比v-if的优先级高
证明这个事情,是在vue.js源码中10997行
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {注:v-if和v-for不要写在同一个节点上,这个性能很差。(v-if要写在父节点上)
ref、keep-alive、nextTick
ref
:获取dom场景:如果项目中使用插件,并且插件是要获取dom的,那么就可以使用ref了。
keep-alive
:缓存组件一旦使用keep-alive会多两个生命周期,activated、deactivated
功能:提升性能的
nextTick
:当dom更新完毕执行内部代码场景:使用插件的时候会用到。例如new Swiper这个插件可能会获取当前元素的宽度或者高度,等dom都加载完毕再去获取宽度和高度就不会有任何问题了。
computed、methods、watch区别
computed:计算属性
可以监听某些数据的变化,并且有缓存。
如果一进入页面调用,就会触发
methods : 可以放入函数
没有缓存
如果一进入页面调用,就会触发
watch :监听(路由和数据)
当数据发生改变时,才会触发
可以得到现在的值和过去的值
组件通信
父传子
父:
<HelloWorld :msg="str" />
<HelloWorld :msg="str" ></HelloWorld>
子:
props:['msg']
props: {
msg: String,
},子传父
子:
<button @click="changeParentName">改变父组件的name</button>
export default {
methods: {
//子组件的事件
changeParentName: function() {
this.$emit('handleChange', 'Jack') // 触发父组件中handleChange事件并传参Jack
// 注:此处事件名称与父组件中绑定的事件名称要一致
}
}
}
父:
<child @handleChange="changeName"></child>
methods: {
changeName(name) { // name形参是子组件中传入的值Jack
this.name = name
}
}兄弟组件传值
创建bus作为中转
import Vue from "vue";
export default new Vue;A组件:
<button @click='btn'>HelloWorld按钮</button>
data () {
return {
hlStr:"这是helloWorld组件的数据"
}
},
methods:{
btn(){
bus.$emit('selectItem',this.hlStr);
}
}
B组件:
created(){
bus.$on('selectItem',(val)=>{
console.log( val , 1111);
})
}
slot插槽
使用场景:组件中有些地方的布局可能大多一致,但是细微有些小小变化
路由
SPA单页面应用和传统页面跳转有什么区别?
SPA跳转是一个页面进行切换
传统页面跳转就是跳转不同的html了
SPA对于seo部分不是特别好,只能收录一个
传统的页面对于seo比较好,多个html文件收录
路径传值
显示:
传:
this.$router.push({
path:'/about',
query:{
key:'你好'
}
})
接:
this.$route.query隐示:
传:
this.$router.push({
name:'About',
params:{
key:'你好'
}
})
接:
this.$route.params路由的模式
mode: “history” http://localhost:8080/about
mode:”hash” http://localhost:8080/#/about
路由导航守卫(拦截、路由钩子函数)
全局
beforeEach
beforeResolve
afterEach路由独享
beforeEnter
组件内
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave场景:要去拦截,判断用户是否是登录状态。功能:进入地址管理,用户如果没有登录是进入不了地址管理(在进入之前判断拦截),需要先登录。
子路由、动态路由
子路由:
children
动态路由:path: '/user/:id'
Vuex
Vuex有哪些部分构成
state、getters、mutations、actions、modules
什么场景用Vuex
共享、方便管理、方便维护、组件传值……
项目:购物车数据,订单数据,用户的登录信息….
mutations和actions的区别
本质区别:
mutations 必须是同步函数
actions “可以包含”任意异步操作使用区别:mutations中可以放入函数,actions也可以放入函数,但是一般我们在mutations中放入函数而actions是提交mutations
v-model双向绑定原理
通过Object.defineProperty劫持数据发生的改变,如果数据发生改变了(在set中进行赋值的),触发update方法进行更新节点内容(),从而实现了数据双向绑定的原理。
diff算法
功能:提升性能
虚拟dom —-> 其实就是数据( 把dom数据化 )
主流:snabbdom、virtual-dom
snabbdom:https://www.npmjs.com/package/snabbdom
搭建环境
npm init -y
cnpm install webpack@5 webpack-cli@3 webpack-dev-server@3 -S
cnpm install snabbdom -S
新建webpack.config.js
配置webpack.config.js虚拟节点 和 真实节点
虚拟节点:
{
children: undefined
data: {}
elm: h1
key: undefined
sel: "h1"
text: "你好h1"
}真实节点:
<h2>你好</h2>
新老节点替换的规则
如果新老节点不是同一个节点名称,那么就暴力删除旧的节点,创建插入新的节点。
只能同级比较,不能跨层比较。如果跨层那么就暴力删除旧的节点,创建插入新的节点。
如果是相同节点,又分为很多情况
新节点有没有children
如果新的节点没有children,那就证明新节点是文本,那直接把旧的替换成新的文本
新节点有children
新的有children,旧的也有children —-> 就是diff算法的核心了【3】
新的有children,旧的没有 —-> 创建元素添加(把旧的内容删除清空掉,增加新的)diff算法的核心(最复杂的情况)
旧前 和 新前
匹配:旧前的指针++ 、 新前的指针++
旧后 和 新后
匹配:旧后的指针– 、 新后的指针–
旧前 和 新后
匹配:旧前的指针++ 、 新后的指针–
旧后 和 新前
匹配:旧后的指针– 、 新前的指针++
以上都不满足条件 —-> 查找
新的指针++,新的添加到页面上并且新在旧的种有,要给旧的复制成undefined
创建或者删除
注意:如果要提升性能,一定要加入key,key是唯一标示,在更改前后,确认是不是同一个节点。
snabbdom
手写diff算法 — 生成虚拟dom
// h.js
import vnode from './vnode'
export default function( sel, data, params){
// h函数的 第三个参数是字符串类型,意味着没有子元素
if(typeof params == 'string'){
return vnode(sel, data, underfined, params, underfined)
}else if(Array.isArray(params)){
let children = [];
for(let item of params){
children.push(item);
}
return vnode(sel, data, children, underfined, underfined)
}
}// index.js
import h from './h'
let vnode1 = h('div', {}, '你好');
let vnode2 = h('ui', {}, [
h('li', {}, 'a'),
h('li', {}, 'b'),
h('li', {}, 'c'),
h('li', {}, '你好'),
])
console.log(vnode2)// vode.js
export default function(sel, data, children, text, elm){
return {
sel,
data,
children,
text,
elm
}
}手写diff算法 — patch不是同一个节点 — 相同节点有没有chrildren
// vode.js
export default function(sel, data, children, text, elm){
let key = data.key;
return {
sel,
data,
children,
text,
elm,
key
}
}<!-- index.html -->
<body>
<div id="container">
这是container
</div>
</body>// index.js
import h from './h'
import patch from './patch'
// 获取真实dom节点
let container = document.getElementById('container');
// 虚拟节点
let vnode1 = h('div', {}, '你好');
let vnode2 = h('ui', {}, [
h('li', {key:'a'}, 'a'),
h('li', {key:'b'}, 'b'),
h('li', {key:'c'}, 'c'),
h('li', {key:'d'}, 'd'),
])
patch(container, vnode1);
patch(container, vnode2);// patch.js
import vnode from './vnode'
import createElement from './createElement'
import newVnode from './patchVnode'
export default function(oldVnode, newVnode){
// 如果oldVnode没有sel,就证明是虚拟节点(让他变成虚拟节点)
if(oldVnode.sel == underfined){
oldVnode = vnode(
oldVnode.tagName.toLowerCase(), // sel
{}, // data
[],
underfined,
oldVnode
)
}
// 判断两个节点是否是同一个节点
if(oldVnode.sel === newVnode.sel){
// 判断条件复杂
patchVnode(oldVnode, newVnode);
}else{
// 不是同一个节点,那么就暴力删除旧的节点,创建插入新的节点
// 把新的虚拟节点创建为dom节点
let newVnodeElm = createElement(newVnode);
// 获取旧的虚拟节点,elm就是真正的节点
let oldVnodeElm = oldVnode.elm;
// 创建新的节点
if(newVnodeElm){
oldVnodeElm.parentNode.insertBefore(newVnodeElm, oldVnodeElm)
}
// 删除旧的节点
oldVnodeElm.parentNode.removeChild(oldVnodeElm);
}
}// createElement.js
// vnode为新节点,就是要创建的节点
export default function createElement(vnode){
let domNode = document.createElement(vnode.sel);
// 判断有没有子节点 children 是不是为underfined
if(vnode.children == underfined){
domNode.innerText = vnode.text;
}else if(Array.isArray(vnode.children)){
// 新的节点有children(子节点),需要递归创建节点
for(let child of vnode.children){
let childDom = createElement(child);
domNode.appendChild(childDom);
}
}
// 补充elm属性
vnode.elm = domNode;
return domNode;
}// patchVnode.js
import createElement from './createElement'
import updateChildren from './updateChildren'
export default function patchVnode(oldVnode, newVnode){
// 判断新节点有没有children
if(newVnode.children === underfined){
// 没有子节点
// 新节点的文本合旧节点的文本内容是不是一样的
if(newVnode.text !== oldVnode.text){
oldVnode.elm.innerText = newVnode.text
}
}else {
// 有子节点
// 新节点虚拟节点有,旧的虚拟节点有
if(oldVnode.children !== underfined && oldVnode.children.legth > 0){
// 最复杂,diff核心
updateChildren(oldVnode.elm, oldVnode.children, newVnode.children)
}else{
// 新节点虚拟节点有,旧的虚拟节点没有
// 把旧节点内容清空
oldVnode.elm.innerHTML = '';
// 遍历新的子节点,创建dom元素
for(let child of newVnode.children){
let childDom = createElement(child);
oldVnode.elm.appendChild(childDom);
}
}
}
}// updateChildren.js
import patchVnode from './patchVnode'
import createElement from './createElement'
// 判断两个虚拟节点是否为同一个节点
function sameVnode(vNode1, vNode2){
return vNode1.key == vNode2.key;
}
// 参数1:真实dom节点
// 参数2:旧的虚拟节点
// 参数3:新的虚拟节点
export default (parentElm, oldCh, newCh) => {
let oldStartIdx = 0; // 旧前指针
let oldEndIdx = oldCh.length - 1; // 旧后指针
let newStartIdx = 0; // 新前指针
let newEndIdx = newCh.length - 1; // 新后指针
let oldStartVnode = oldCh[0]; // 旧前虚拟节点
let oldEndVnode = oldCh[oldEndIdx]; // 旧后虚拟节点
let newStartVnode = newCh[0]; // 新前虚拟节点
let newEndVnode = newCh[newEndIdx]; // 新后虚拟节点
while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
if(oldStartVnode == underfined){
oldStartVnode = oldCh[++oldStartIdx];
}
if(oldEndVnode == underfined){
oldEndVnode = oldCh[--oldEndIdx];
}else if(sameVnode(oldStartVnode, newStartVnode)){
// 旧前 和 新前
patchVnode(oldStartVnode, newStartVnode);
if(newStartVnode){
newStartVnode.elm = oldStartVnode?.elm;
}
oldStartVnode = oldCh[++oldStsrtIdx];
newStartVnode = newCh[++newStsrtIdx];
}else if(sameVnode(oldEndVnode, newEndVnode)){
// 旧后 和 新后
patchVnode(oldEndVnode, newEndVnode);
if(newEndVnode){
newEndVnode.elm = oldEndVnode?.elm;
}
oldEndVnode = oldCh[--oldEndVnode];
newEndVnode = newCh[--newEndVnode];
}else if(sameVnode(oldStartVnode, newEndVnode)){
// 旧前 和 新后
patchVnode(oldStartVnode, newEndVnode);
if(newEndVnode){
newEndVnode.elm = oldStartVnode?.elm;
}
// 把旧前指定的节点移动到旧后指向的节点的后面
parentElm.insertBefore(oldEndVnode.elm, oldEndVnode.elm.nextSibling)
oldStartVnode = oldCh[++oldStartVnode];
newEndVnode = newCh[--newEndVnode];
}else if(sameVnode(oldEndVnode, newStartVnode)){
// 旧后 和 新前
patchVnode(oldEndVnode, newStartVnode);
if(newStartVnode){
newStartVnode.elm = oldEndVnode?.elm;
}
// 把旧后指定的节点移动到旧前指向的节点的前面
parentElm.insertBefore(oldStartVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndVnode];
newStartVnode = newCh[++newStartVnode];
}else{
// 以上都不满足查找
// 创建一个对象,存虚拟节点(判断新旧有没有相节点)
const keyMap = {};
for(let i = oldStartIdx; i <= oldEndIdx; i++){
const key = oldCh[i]?.key;
if(key){
keyMap[key] = i;
}
}
// 在旧节点中查找新前指向节点
let idxInOld = keyMap[newStartVnode.key];
// 如果有,说明数据在新旧虚拟节点中都存在
if(idxInOld){
const elmMove = oldCh[idxInOld];
patchVnode(elmMove, newStartVnode);
// 处理过的节点,在旧虚拟系欸但的数组中,设置为underfined
oldCh[idxInOld] = underfined;
parentElm.insertBefore(elmMove.elm, oldStartVnode.elm);
}else{
// 如果没有找到,说明是一个新的节点(创建)
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm);
}
newStartVnode = newCh[++newStartIdx];
}
}
// 结束循环只有两种情况,新增和删除
// 1、oldStartIdx > oldEndIdx
// 2、newStartIdx > newEndIdx
if(oldStartIdx > oldEndIdx){
const before = newCh[newEndIdx+1] ? newCh[newEndIdx+1].elm : null;
for(let i = newStartIdx; i <= newEndIdx; i++){
parentElm.insertBefore(createElement(newCh[i]), before);
}
}else{
// 进入删除操作
for(let i = oldStartIdx; i <= oldEndIdx; i++){
parentElm.removeChild(oldCh[i].elm)
}
}
}谈一下MVVM框架
web1.0时代:
文件全在一起,就是前端和后端的代码全在一起
问题:
- 前端和后端都是一个人开发,技术没有侧重点或者责任不够细分
- 项目不好维护
- html、css、js页面的静态内容没有,后端是没办法工作的(没办法套数据)
mvc…都是后端先出的
web2.0时代:
ajax出现了,前端和后端数据分离了。
解决问题:后端不用等前端页面弄完没,后端做后端的事情(写接口),前端布局、特效、发送请求
问题:
- html、css、js都在一个页面中,单个页面可能内容也是比较多的(也会出现不好维护的情况)
出现前端的框架MVC、MVVM
解决问题:可以把一个特别大的页面进行拆分(组件化),单个组件进行维护
什么是MVVM:
Model-View-ViewModel 的简写
view:视图【dom在页面中展示的内容】
model:模型【数据层:vue中的data数据】
ViewModel:视图模型层【就是vue源码】