作者:叫我阿琛
转发链接:
https://mp.weixin.qq.com/s/ph3aUt-H4QtBgw9z-VFlHA
目录
万字总结Vue(包含全家桶),希望这一篇可以帮到您(一)本篇
基础使用
以下代码均经过自己测试,可以复制直接看效果。「注意引入Vue文件」
渲染优先级
- render>template>data的插值表达式
- {{}} 放的是表达式的时候会 「输出结果」,内部转为函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基本概念</title>
<script src="vue.js"></script>
</head>
<body>
<h1>显示优先级</h1>
<ul>
<li>第一个是render 有render方法就输出render的渲染结果</li>
<li>第二个是template 有template方法就输出template的内容</li>
<li>最后一个是data,如果两者不存在 则输出data里面的插值表达式</li>
{{ }} 当这里面放的是一个表达式的时候,会输出表达式的结果 原因 会转化成一个函数 render
</ul>
<p>指令修饰符,有好多 自己官网看</p>
<div id="app">
{{ msg }}
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: '我是data',
}
},
template: '<div>我是template</div>',
render(h) {
return h('div', ['我是render'])
},
method: {
fn(e) { // 不添加括号自动添加事件源, 添加括号 手动传入事件源
console.log('内部已经使用bind 绑定了this ,再次绑定也没什么用')
console.log(this)
},
},
})
</script>
</body>
</html>
v-model
v-model 实际上是一个 「语法糖」
<input type="text" :value = 'msg' @input="handleInput">
<!-- 实际上是上述的语法糖-->
<input type="text" v-model="msg">
v-model 的应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-model</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
<p>@input</p>
<input type="text" :value = 'msg' @input="handleInput">
<p>这个是@chage</p>
<input type="text" :value = 'msg' @change="handleInput">
<p>v-model 是上面@input的语法糖</p>
<input type="text" v-model="msg">
<p>@input 和@change 区别 一个是 聚焦的时候 一个是 失去焦点的时候</p>
<p>下拉列表</p>
{{ selected }}<br>
<select v-model="selected">
<option value="" disabled>请选择</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>下拉列表多选 这样绑定的值必须是一个列表</p>
{{ selectedMore }}<br>
<select v-model="selectedMore" multiple>
<option value="" disabled>请选择</option>
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
<p>复选框</p>
{{ checked }}<br>
游泳 <input v-model="checked" type="checkbox" value="游泳">
洗澡 <input v-model="checked" type="checkbox" value="洗澡">
睡觉 <input v-model="checked" type="checkbox" value="睡觉">
<p>单选框</p>
{{ radioed }}<br>
男 <input type="radio" value="男" v-model="radioed">
女 <input type="radio" value="女" v-model="radioed">
<p>v-model 修饰符</p>
<p>{{ attr }}</p>
<input type="number" v-model.number="attr">
<p>{{ attrText }}作用类似@chage</p>
<input type="text" v-model.lazy="attrText">
<p>{{ attrText }} 去除空格</p>
<input type="text" v-model.trim="attrText">
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: '我是data',
selected:'',
selectedMore:[],
checked:[],
radioed:'',
attr:0,
attrText:''
}
},
methods: {
handleInput(e){
this.msg = e.target.value
}
},
})
</script>
</body>
</html>
watch
观测值的变化 执行对应函数
三种写法:
- 添加deep属性,表明要深度遍历
- 添加immediate属性,表明 立即执行
- 添加 name属性,执行methods的这个方法
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
{{ msg }}
{{ name }}
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
msg: { a: '123' },
name:'456'
}
},
methods: {
fn() {
console.log('这是methods')
},
},
// 第一种
// watch:{
// msg:{
// handler(oldValue,newValue){
// console.log(oldValue,newValue) // 如果是对象的不到老值
// },
// deep: true // 如果是对象继续深度遍历
// }
// }
watch: {
msg: [
// {
// handler(oldValue, newValue) {
// console.log(oldValue, newValue) // 如果是对象的不到老值
// },
// immediate: true, // 立即执行
// },
// 'fn', // 不知道为什么不行
],
name:'fn'
},
})
setTimeout(() => {
vm.msg.a = '456'
vm.name = '789'
}, 1000)
</script>
</body>
</html>
computed
经常使用get,但是还有一个set
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
全选: <input type="checkbox" v-model="checkAll">
<hr>
<input type="checkbox" v-for="check in checks" v-model="check.check">
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
checks: [{ check: true }, { check: true }, { check: true }],
}
},
computed: {
checkAll: {
get() {
// 有一个不满足 返回false 并且不往下进行
return this.checks.every((item) => item.check)
},
set(newValue) {
this.checks.forEach(item => item.check = newValue)
},
},
},
})
</script>
</body>
</html>
watch 和computed区别
- computed不会立马取值,用到的时候才会取值. 并且有缓存,依赖数据不改变不更新结果
- watch 「立即执行」,会先算出来一个老值.数据变化就执行函数
filter
过滤器,将属性进行格式化后在进行展示
分为 「全局」和 「局部」两种
会接受两个参数,一个是要格式化的数据,一个是格式化的规则
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>局部</p>
{{ timer | format1('YYYY:MM:DD') }}
<p>全局</p>
{{ timer | format('YYYY:MM:DD') }}
</div>
<script>
Vue.filter('format', function (timer, format) {
return dayjs(timer).format(format)
})
let vm = new Vue({
el:'#app',
data() {
return {
timer: 123456789,
}
},
filters:{
format1(timer, format){
return dayjs(timer).format(format)
}
}
})
</script>
</body>
</html>
指令
同样分为 「局部」 和 「全局」
使用的时候 在想要使用的标签上添加 v-xxx xxx为指令名字就可以
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>指令</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<p>自动获取焦点</p>
<input type="text" v-focus>
<p>点击显示 日历效果</p>
<div v-click-outside="hide">
<input type="text" @focus="show">
<div v-if="isShow">
日历显示 时间
</div>
</div>
<h1>指令有生命周期.有钩子</h1>
<ul>
<li>bind 绑定上的时候会执行一次</li>
<li>inserted 插入的时候</li>
<li>update 当引用数据发生变化的时候</li>
<li>componentUpdate 模板更新</li>
<li>unbind 解除绑定</li>
<li>默认写成一个函数 bind+update</li>
</ul>
<h1>指令传入三个参数的含义</h1>
<ul>
<li>el 当前元素 </li>
<li>bindings 有关指令的各个属性</li>
<li>vNode 虚拟节点</li>
<li>vNode.context Vue实例</li>
</ul>
</div>
<script>
// 全局注册指令
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
},
})
let vm = new Vue({
el: '#app',
// 局部指令
directives: {
clickOutside: {
bind(el, bindings, vNode) {
el.handler = function (e) {
// console.log(e.target)
console.log(vNode.context)
if (!el.contains(e.target)) {
vNode.context[bindings.expression]()
}
}
document.addEventListener('click', el.handler)
},
unbind(el) {
document.removeEventListener('click', el.handler)
},
},
},
data() {
return {
isShow: false,
}
},
methods: {
show() {
this.isShow = true
},
hide() {
this.isShow = false
},
},
})
</script>
</body>
</html>
实例属性
介绍一些常用的 「实例属性」
- $mount() 挂载,参数写要挂载的节点。如果不写,则挂载的$el属性上,可以手动挂载(比如写Message弹框)
- $options 获取用户写的配置
- $watch 跟watch 用法一样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>实例属性</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
</div>
<script>
let vm = new Vue({
// el:'#app',
data() {
return {
msg: '我是data',
}
},
template: '<div>我是template</div>',
render(h) {
return h('div', ['我是render'])
},
})
vm.$mount() // 挂载 提供值了就挂载到对应的 节点上
// 不提供就挂载到$el 属性上 代表要手动挂载
console.log(vm.$el) // 获取真实节点
document.body.appendChild(vm.$el)
console.log(vm.$options) // 用户参数
console.log(vm.$watch('msg', function (oldValue, newValue) {
console.log(oldValue, newValue)
})) // 就是 watch 另一种写法 批量更新 只更新一次 内部有队列
</script>
</body>
</html>
进阶
动画
动画分为两种,一种是CSS动画,一种是js动画。各位按照需求选择
因为个人推荐使用CSS作动画,所以JS版本就不再写出来了。有兴趣的朋友可以点击这里
「css版本」
就是把 「要做动画的DOM元素用transition包裹一下」
然后记住一下6个名字,分别对应动画不同的周期
- .v-enter 进入动画时候
- .v-enter-active 进入动画过程中
- .v-enter-to 进入动画进行到最后
- .v-leave 这个没有实际意义,为了美感
- .v-leave-active 离开动画过程中
- .v-leave-to 离开动画结束
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动画</title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<p>transiton 可以有name 属性 给改名字,这样一些 v-leave 则变成的 name-leave</p>
<transition>
<div v-show="isShow" class="box" style=" width: 100px;height: 100px;">
</div>
</transition>
<button @click="handleShow">点我</button>
<p>transition Vue动画标签 transition-group 动画组</p>
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
isShow: false,
}
},
methods: {
handleShow() {
this.isShow = !this.isShow
},
},
})
</script>
<style>
.box {
background-color: red
}
/*进入动画时候的颜色*/
.v-enter {
background-color: blue;
}
/*动画过程中*/
.v-enter-active {
transition: all 2s linear;
}
/*动画进行到最后*/
.v-enter-to {
background-color: yellow;
}
/* 进行完之后会变成红色*/
/*这个没有实际意义,为了美感*/
.v-leave {
background-color: purple;
}
.v-leave-active{
transition: all 2s linear;
}
.v-leave-to{
background-color: blue;
}
</style>
</body>
</html>
动画组
与上一个不一样的是,这个数多组动画。
「区别」 使用了 transition-group
「动画名称」
- enter-class
- enter-active-class
- enter-to-class (2.1.8+)
- leave-class
- leave-active-class
- leave-to-class (2.1.8+)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动画</title>
<script src="../vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app">
<p>vue中动画组</p>
<input type="text" v-model="content">
<transition-group
enter-active-class="animated bounceInLeft"
leave-active-class="animated bounceOutRight"
>
<li v-for="arr in computedArr" :key="arr">{{ arr }}</li>
</transition-group>
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
content:'',
arrs:['abc','basd','zxcw','awqec','kjea']
}
},
methods: {
handleShow() {
this.isShow = !this.isShow
},
},
computed:{
computedArr(){
return this.arrs.filter(item => item.includes(this.content))
}
}
})
</script>
<style>
li{
width: 200px;
background-color: blue;
line-height: 35px;
}
</style>
</body>
</html>
组件
组件通讯(重点)
我总结了 一下,大概以下几种
- props+emit
- provide+inject 单项 数据流
- $parent+$children 直接触发父/子类的事件
- $broadcast + $dispatch 自己在原型上写的
- $attrs+$listeners 通过所有属性和方法的集合获取
- $bus 类似Vuex
- Vuex Vue插件
props+emit
// parents
<template>
<div>
<h1>Parent</h1>
<h2>第一种</h2>
<Son :money="money" :changMoney="changMoney"></Son>
<p>第二中方法 click2是自己定义的名字,不是原生事件</p>
<Son :money="money" @click2="changMoney"></Son>
</div>
</template>
<script>
import Son from './Son'
export default {
name: 'Parent',
data() {
return {
money: 200,
}
},
components: {
Son
},
methods: {
changMoney(value) {
this.money = value
},
changMoney2(val) {
this.money += val
},
},
}
</script>
// son
<template>
<div>
<h1>Son</h1>
<p>子组件接收到之后,利用props 属性接受,然后可以直接使用</p>
<p>子组件可以使用父组件传递的属性和函数</p>
我是爸爸给我的钱{{ money }}
<h2>第一种</h2>
<button @click="changMoney(500)">改变父亲钱数</button>
<h2>第二种方法</h2>
<button @click="change">改变父亲钱数2</button>
</div>
</template>
<script>
export default {
props:{
money: {
type:Number,
default:100
},
changMoney:{
type:Function,
default: ()=>{}
}
},
methods:{
change(){
this.$emit('click2',300)
}
}
}
</script>
第一种是 传递一个属性还有一个函数,子代接收到之后,可以在使用
第二种是 利用$emit, 直接触发 「在父级定义的函数」
「特别注意」,这个click2「不是原生的」,你把它叫做 a , b 之类等都可以
provide+inject
「官方建议」:
❝
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
❞
这个就比较简单。类似于react的redux
// parent
<template>
<div>
<h1>Parent</h1>
<h1> 关于son2 跨代通讯</h1>
<Son2 @eat="eat"></Son2>
</div>
</template>
<script>
import Son2 from './Son2'
export default {
provide(){
return {parent:this}
},
name: 'Parent',
data() {
return {
money: 200,
}
},
components: {
Son2,
},
methods: {
eat(){
console.log('patent中的eat方法')
}
},
}
</script>
// son2
<template>
<div>
Son2
<GrandSon></GrandSon>
</div>
</template>
<script>
import GrandSon from './GrandSon'
export default {
name:'Son2',
components: {
GrandSon,
},
}
</script>
// grandSon
<template>
<div>
GrandSon
<p>跨代通讯的值</p>
{{ parent.money }}
<button @click="$parent.$emit('eat')">触发父组件的eat方法</button>
</div>
</template>
<script>
export default {
inject:['parent']
}
</script>
写一个Son2的作用,就是让大家明白,隔代也是可以的。一个提供,一个接收之后就可以使用
$parent+$children
这个我就直接用上面的代码了。这个比较简单。就是通过$parent/$children 找到它的父/子级。然后 使用或者触发他们的属性或者方法
$broadcast + $dispatch
再次引用官方的话
❝
$dispatch 和 $broadcast 已经被弃用。请使用更多简明清晰的组件间通信和更好的状态管理方案,如:Vuex。
❞
当然,我们还是介绍一些这两个方法,各位看需要使用(小声bb一下,我觉得Vuex真香)
// 在main.js上
import Vue from 'vue'
import App from './App';
/**
* 找父节点触发事件
* @param eventName
* @param ComName
* @param Value
*/
Vue.prototype.$dispatch = function (eventName, ComName = '', Value = '') {
let parent = this.$parent;
while (parent) {
if (ComName && parent.$options.name === ComName) {
parent.$emit(eventName, Value)
return
} else {
parent.$emit(eventName, Value)
parent = parent.$parent
}
}
}
/**
* 找子节点触发事件
* @param eventName
* @param ComName
* @param value
*/
Vue.prototype.$broadcast = function (eventName, ComName = '', value = '') {
let children = this.$children // 获取得是数组
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
if (ComName === child.$options.name) {
child.$emit(eventName, value)
return
} else {
if (child.$children) {
broadcast(child)
}
}
}
}
broadcast(children)
}
这两个方法利用了$parent和$children。不断获取父/子节点,触发相对应的事件。
我这个$dispatch的else写的是,如果不是这个组件的事件,我也触发了。其实应该把这句删除。只 继续往上找就可以
「使用」
// 直接这样使用就好
<button @click="$parent.$emit('eat')">触发父组件的eat方法</button>
$attrs+$listeners
官方定义
// APP.vue
<template>
<div>
<Test :a="1" :b="2" :c="3" :d="4" @click="click"></Test>
</div>
</template>
<script>
import Test from './test'
export default {
data() {
return {
msg: 'hello',
}
},
components: {
Test,
},
methods:{
click(){
console.log('我是APP中的click')
}
}
}
</script>
// test.vue
<template>
<div>
我是test
<h1>使用$attrs可以获得,但是会绑定在DOM元素上</h1>
<ul>
<li>设置 <strong>inheritAttrs:false </strong>就不会绑定了</li>
<li>当props接收后,arrts将不会显示已经被接收的</li>
{{ $attrs }}
<li>这样子代传递</li>
<button @click="$listeners.click">触发APP中的click</button>
<test2 v-bind="$attrs" v-on="$listeners"></test2>
</ul>
</div>
</template>
<script>
import test2 from './test2';
export default {
props:['a'],
name:'Test',
inheritAttrs:false,
components:{
test2
}
}
</script>
//test2.vue
<template>
<div>
<h1>我是test2</h1>
{{ $attrs }}
<button @click="$listeners.click">触发APP中的click</button>
</div>
</template>
<script>
export default {
name: 'test2',
}
</script>
「注意」
- 父级这样传递属性的过程中,会把这个属性绑定在DOM元素上,(被props接收的不会被绑定),可以在子类中使用inheritAttrs:false,来设置取消绑定
- 使用得时候,直接使用$attrs.x/$listeners.x使用
- 往下一代传递的时候,直接使用v-bind="$attrs" v-on="$listeners",就可以把没有被props接收过的都传给下一代使用
$bus
就是挂载了一个Vue实例
// APP.vue
<template>
<div>
<h1>子组件如何监听父组件的mounted</h1>
<p>组件挂载, 先挂载父组件 -》渲染子组件,子mounted -》 父mounted</p>
<p>可以实现任意组件之间的通讯,但只适合小规模的</p>
<bus></bus>
</div>
</template>
<script>
import bus from './$bus使用';
export default {
data() {
return {
msg: 'hello',
}
},
mounted(){
this.$bus.$emit('监听事件','hello')
},
components: {
bus
},
}
</script>
// $bus使用
<template>
<div>
bus
{{ $bus.a }}
</div>
</template>
<script>
export default {
name: 'bus',
mounted() {
// 发布订阅模式 可以多次订阅
this.$bus.$on('监听事件', function (value) {
console.log(value)
})
},
beforeDestroy(){
// 解绑组件
this.$bus.$off('监听组件')
}
}
</script>
Vuex
请往后面看
插槽
<template>
<div>
<h1>插槽</h1>
<test1>
我是标签里面的内容
</test1>
<h1>具名插槽</h1>
<p>新版写法只可以用 template</p>
<p>这里插值表达式的数据用的是父类的</p>
<test1>
<!-- 老版本写法-->
<!-- <div slot="header">asd</div>-->
<!-- <div slot="footer">qwe</div>-->
<template v-slot:header>header {{ msg }}<br></template>
<template v-slot:footer>footer</template>
</test1>
<h1>作用域插槽</h1>
<p>这样用的是子类的数据</p>
<test1>
<!-- 老版本写法-->
<!-- <div slot="header" slot-scope="{a,b}">{{ a }}{{ b }}</div>-->
<template v-slot:header="{a,b}" >{{ a }},{{ b }}</template>
</test1>
</div>
</template>
<script>
import test1 from './test1';
export default {
data() {
return {
msg: 'hello',
}
},
components:{
test1
}
}
</script>
// test1
<template>
<div>
<h1>我是test1</h1>
<slot></slot>
<slot name="header" a="1" b="2"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: 'test1',
}
</script>
这个比较简单,就不再多多叙述。强调一点,新老版本区别
- 「新版本」只可以用template进心包裹
- 「老版本」可以用div等
总结
看完上面的内容可以尝试模仿写一下 element-ui的表单组件。他们使用了async-validator作为校验。
Vue
同样有一个简单版本Vue数据响应式和编译原理分析 和 模拟实战.这个版本没有用到虚拟Dom等。
虚拟dom。个人也总结了一篇帮你深入了解虚拟DOM和DOM-diff,希望能帮到各位
仅仅是一个简单的实现。但是实现了 「部分指令」
完整部分(即这次总结的,带上虚拟dom等等),这个内容由于太多(标题细分太多。不好去寻找)。我另写了一篇文章,还在整理中,1号大概可以放出来。
「贴一个图证明一下。实在是考虑的太多,所以写出来比较慢」
UTOOLS1593399637478.png
vueX
推荐一下自己的另一篇文章Vuex的简单实现,感觉这一篇写的相对简单一点
Vuex 用法
这个就不多做解释了。不太熟练的朋友可以先去看官方文档
给出一下我的数据定义
// store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './../vuex2'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
age: 10
},
strict: true,
getters: {
myAge(state) {
return state.age + 30
}
},
mutations: {
// 同步更改state 在严格模式下不可以使用异步
change(state, payload) {
state.age += payload
}
},
actions: {
// 异步更改state
asyncChange({ commit }, payload) {
setTimeout(()=>{
commit('change', payload)
}, 1000)
}
}
})
export default store
本篇未完结,请见下一篇
作者:叫我阿琛
转发链接:
https://mp.weixin.qq.com/s/ph3aUt-H4QtBgw9z-VFlHA