Object.defineProperty数据代理 &Observer数据劫持
数据代理,通过一个对象代理对另一个对象中属性的操作(读/写)
数据代理+数据劫持 ? 数据的双向绑定(vue中)
Vue 利用 Object.defineProperty 创建一个 observer 来劫持监听所有的属性,把这些属性全部转为 getter 和 setter。
Vue 中每个组件实例都会对应一个 watcher 实例,它会在组件渲染的过程中把使用过的数据属性通过 getter 收集为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
修改data属性值-》重新解析模板-》生成新的(虚拟)dom-》diff算法对比-》生成更新后页面
let number=10
let person={
name:"lisi",
sex:"男",
age:number,
}
Object.defineProperty(person,"age",{
// value:19,
// enumerable:true,//可枚举
// writable:true //可修改
get(){
debugger
return number
},
set(value){//value为 person.age=1 中的1
debugger
number=value
}
})
person.age=1
// for in 即可遍历数组也可遍历对象
// for (const key in person) {
// if (Object.hasOwnProperty.call(person, key)) {
// const element = person[key];
// console.log(element)
// }
// }
console.log( Object.keys(person))
let obj1={
"x":12
}
let obj2={
name:"lisi",
}
Object.defineProperty(obj2,"x",{
set(value){
obj1.x=value
},
get(){
return obj1.x
}
})
添加class样式
字符串写法
<template>
<div class="hello" :class="a" @click="changeColor()">
{{msg}}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
a:"red"
}
},
methods:{
changeColor(){
this.a="green"
}
}
}
</script>
数组写法
<div class="hello" :class="classarr" @click="changeColor()">
data(){
return{
classarr:["red","green","orange"]
}
},
对象写法
<div class="hello" :class="classobj" @click="changeColor()">
data(){
return{
classobj:{
"red":true,
"green":false,
"orange":false,
},
}
},
绑定style样式
方式1
<template>
<div class="hello" :style="styleobj" @click="changeColor()">
{{msg}}
</div>
</template>
data(){
return{
styleobj:{
fontSize:"40px"
}
}
},
方式2
<div class="hello" :style="{fontSize:fontsize}" @click="changeColor()">
方式3
:style[a,b]
a和b为样式对象 如:
styleobj:{
fontSize:"40px"
}
v-if
<div v-if="n===1">1</div>
<div v-else-if="n===2">1</div>
<div v-else-if="n===3">1</div>
<div v-else>1</div>
计算属性
计算属性回调函数
2个时刻调用:初始化和方法里面的数据改动
<template>
<div class="hello" >
<input type="text" v-model="inputtext">
<button @click="sortAge=1">年龄升序</button>
<button @click="sortAge=2">年龄降序</button>
<button @click="sortAge=0">原顺序</button>
<ul >
<li v-for="item in filperson" :key="item.id">
{{ item.name }} ------ {{ item.age }}
</li>
</ul>
</div>
</template>
<script>
/* eslint-disable */
import { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
sortAge:0,//1升序,2降序,0原顺序
arr:[
{"id":"1","name":"lisi1","age":12},
{"id":"2","name":"lisi2","age":11},
{"id":"3","name":"wangwu1","age":33},
{"id":"4","name":"wang2","age":55}
],
endarr:[],
inputtext:""
}
},
methods:{
},
computed:{
// 默认执行一次,this.inputtext发生变化则计算一次
filperson(){
let starr= this.arr.filter((item)=>{
return item.name.includes(this.inputtext)
})
if(this.sortAge){
debugger
starr.sort((a,b)=>{
return this.sortAge==1?a.age-b.age :b.age-a.age
})
}
return starr
}
},
}
</script>
条件过滤
<template>
<div class="hello" >
<input type="text" v-model="inputtext">
<ul>
<li v-for="item in endarr" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
/* eslint-disable */
import { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
arr:[
{"id":"1","name":"lisi1"},
{"id":"2","name":"lisi2"},
{"id":"2","name":"wangwu1"},
{"id":"3","name":"wang2"}
],
endarr:"",
inputtext:""
}
},
methods:{
},
watch:{
// 简写形式
// inputtext(newvalue){
// this.endarr= this.arr.filter((item)=>{
// return item.name.includes(newvalue)
// })
// }
// 完整形式
inputtext:{
// 初始化的时候立即执行
immediate:true,
handler(newvalue){
this.endarr= this.arr.filter((item)=>{
return item.name.includes(newvalue)
})
}
}
}
}
</script>
后期给对象添加属性
<template>
<div class="hello" >
<p>姓名:{{person.name}}</p>
<p>姓名:{{person.age}}</p>
<button @click="updata"> person添加属性</button>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
// import Vue from 'vue/types/umd';
// import { person } from '../../../hellovue3/src/types/index';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
person:{
"name":"lisu",
}
}
},
methods:{
updata(){
// 方法1
// Vue.set(this.person,"age","boo")//只可给data里面的对象上添加属性,不可给data添加
// 方法2
this.$set(this.person,"age","boo")
}
},
}
</script>
收集表单里面的信息
<template>
<div class="hello" >
<!-- prevent组织默认刷新行为 -->
<form @submit.prevent="sub">
账号:<input type="text" v-model.trim="person.acount"><br/><br/>
密码:<input type="password" v-model="person.password"><br/><br/>
年龄:<input type="number" v-model.number="person.age"><br/><br/>
性别:<label for="nan">男</label> <input v-model="person.personSex" type="radio" name="sex" id="nan" value="male">
<label for="nv">女</label> <input v-model="person.personSex" type="radio" name="sex" id="nv" value="famale"><br/><br/>
爱好:学习<input type="checkbox" v-model="person.like" value="lea">
打游戏<input type="checkbox" v-model="person.like" value="dyx">
吃美食<input type="checkbox" v-model="person.like" value="cms"><br/><br/>
喜欢城市:<select name="" v-model="person.city" aria-placeholder="请选择喜欢的城市" id="">
<option value="1">上海</option>
<option value="2">北京</option>
<option value="3">深圳</option>
<option value="4">广州</option>
</select><br/><br/>
<!-- v-model.lazy失去焦点更新数据 -->
其他信息:<textarea name="" v-model.lazy="person.other" id="" cols="30" rows="10">
</textarea><br/><br/>
<!--checkbox不设置value值 则是否打勾为true或者false -->
<input type="checkbox" v-model="person.agree">我同意<br/><br/>
<button>提交</button>
</form>
</div>
</template>
<script>
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
person:{
acount:"",
password:"",
like:[],
personSex:"",
city:"",
age:0,
other:"",
agree:''
},
}
},
methods:{
sub(){
console.log("提交")
console.log(JSON.stringify(this.person))
}
},
}
</script>
v-html指令安全问题
cookie中 k1 ,k2的值被篡改,则不安全;
被其他网站获取到也不安全,获取到可直接登录(httponly可避免通过代码获取cookie)
指令
v-clock
指令+样式 当网络延迟时,动态渲染的页面字段不显示,当页面渲染结束后,vue自动删除v-clock
<template>
<div class="hello" >
<p v-cloak>{{name}}</p>
<p v-cloak>{{age}}</p>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
name:"1111",
age:"2"
}
},
methods:{
},
}
</script>
<style scoped>
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
v-pre
跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
v-once 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。
自定义指令
自定义指令的两种实现方式
<template>
<div class="hello" >
<!-- 指令v-big,num放大10倍 -->
<!-- v-focus 页面刷新时得到焦点 -->
<p >{{num}}</p>
<!-- 放大10倍后的n -->
<p v-big="num"></p>
<button @click="num++">num加一</button><br/>
<input v-focus="num" type="text"><br/>
<input v-big-number="num" type="text">
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
// 全局自定义指令两种方法
// Vue.directive("big",function (element,binding){
// element.innerText=binding.value*10
// })
// Vue.directive("focus",{
// // 指令和元素成功绑定时(一上来,并不是放入页面时)
// bind(element,binding){
// element.value=binding.value
// },
// // 指令所在元素被插入页面时
// inserted(element,binding){
// element.focus()
// },
// // 指令所在的模板被从新解析时
// update(element,binding){
// element.value=binding.value
// }
// })
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
num:1,
}
},
methods:{
},
directives:{
// big函数何时会被调用?
// 1.指令和元素成功绑定时(一上来,并不是放入页面时)
// 2.指令所在的模板被从新解析时
big(element,binding){
element.innerText=binding.value*10
},
focus:{
// 指令和元素成功绑定时(一上来,并不是放入页面时)
bind(element,binding){
element.value=binding.value
},
// 指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被从新解析时
update(element,binding){
element.value=binding.value
}
},
'big-number'(element,binding){
console.log(element,binding.value)
element.value=binding.value*10
}
}
}
</script>
<style scoped>
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
一个重要的内置关系
function Person(){
this.name="lisi",
this.age=10
}
let p=new Person()
Person.prototype===p.__proto__ //显示原型属性和隐式原型属性 true
VueComponent.prototype.__proto__=Vue.prototype
为什么 要有这个方法?让组件实例对象(vc)可以访问到vue原型上的属性/方法
ref和props的使用
ref给节点打标识
1.放在节点上可获取html节点 //<div>1</div>
2.放在组件上可获取子组件中定义的值
//父
<template>
<div id="app">
<div v-text="numapp" ref="div"></div>
<HelloWorld ref="vc" msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return{
numapp:1
}
},
mounted(){
console.log(this.$refs.div);
console.log(this.$refs.vc.num);//num为子组件定义变量
}
}
</script>
//子
<template>
<div class="hello" >
<div>{{ msg }}</div>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return{
num:1,
}
},
methods:{
},
}
</script>
props(父传子)的两种写法:
props不可改指的是修改对象的整个地址,可修改对象里面某个属性值和基本数据类型(不建议)
1. props:["msg","age","name"]
2.
props:{
msg:{
type: String,
required:true,
default:"222"//一般不和 required一起定义
}
}
mixin混入
可以把多个组件公用的配置提取成一个混入对象
1,局部;2,全局
//mixins.js
export let hunru={
methods:{
tc(){
alert(this.msg)
}
},
}
export let init={
mounted(){
console.log("init....");
}
}
//组件1
<template>
<div class="hello" >
<p @click="tc">person</p>
<div>{{ msg }}</div>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
import {hunru,init} from '../mixins'
export default {
name: 'HelloWorld',
props: {
msg:{
type: String,
required:true,
default:"222"
}
},
data(){
return{
num:1,
}
},
mixins:[hunru,init],
// mounted(){
// console.log("init....");
// }
// methods:{
// tc(){
// alert(this.msg)
// }
// },
}
</script>
//组件2
<template>
<div class="hello" >
<p @click="tc">HelloWorld</p>
<div>{{ msg }}</div>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
import {hunru} from '../mixins'
export default {
name: 'HelloWorld',
props: {
msg:{
type: String,
required:true,
default:"222"
}
},
data(){
return{
num:1,
}
},
mixins:[hunru],
methods:{
},
}
</script>
全局
引入插件
插件
功能:用于增强Vue
本质:包含安装方法的一个对象,安装程序的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
对象。安装=函数(选项){
//1.添加全局过滤器
Vue.filter(....)
// 2.添加全局指令
Vue.directive(....)
//3.配置全局混入(合)
Vue.mixin (....)
//4.添加实例方法
Vue.prototype.$myMethod =function () {...}
Vue.prototype.$myProperty=xxxX
}
使用插件:Vue.use()
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from'./plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用(使用)插件
Vue.use(plugins)
Vue.use(plugins)//其他插件
Vue.use(plugins)//其他插件
Vue.use(plugins)//其他插件
//创建vm
I
new Vue({
el:'#app',
render: h => h(App)
})
安装less-loade
npm i less-loader
// "less-loader": "^8.0.0", webpack为5.0.0以及以上才可兼容
组件化编程todos小demo
1.组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一
些组件在用:
1).一个组件在用:放在组件自身即可。
2).一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
2. props适用于:
(1).父组件==>子组件 通信
(2).子组件==>父组件通信(要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props
是不可以修改的!
4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推
荐这样做。
app.vue
<template>
<div id="app">
<MyHeader ref="headerText" :getobj="getobj"/>
<MyList text="text" :deleteList="deleteList" :list="list" :checktodo="checktodo"/>
<MyFooter :list="list" :checkAllList="checkAllList"/>
</div>
</template>
<script>
/* eslint-disable */
import MyHeader from './components/todos/MyHeader.vue'
import MyFooter from './components/todos/MyFooter.vue'
import MyList from './components/todos/MyList.vue'
export default {
name: 'App',
components: {
MyHeader,
MyFooter,
MyList
},
data(){
return{
"text":"",
list:JSON.parse(localStorage.getItem("list"))||[],
}
},
mounted(){
this.$bus.$on("checktodo",this.checktodo)
this.$bus.$on("deleteList",this.deleteList)
this.$bus.$on("updateList",this.updateList)
},
beforeDestroy(){
this.$off(['checktodo','deleteList','updateList'])
},
methods:{
//修改
updateList(id,title){
for (let index = 0; index < this.list.length; index++) {
if(this.list[index].id==id){
this.list[index].title=title
}
}
},
// 添加
getobj(obj){
this.list.unshift(obj)
},
// 勾选
checktodo(id){
for (let index = 0; index < this.list.length; index++) {
if(this.list[index].id==id){
this.list[index].done=!this.list[index].done
}
}
},
// 删除
deleteList(id){
console.log(id)
this.list=this.list.filter((item)=>{
return item.id!==id
})
console.log( this.list)
},
// 全选或者全部选
checkAllList(status){
for (let index = 0; index < this.list.length; index++) {
this.list[index].done=status
}
}
},
watch:{
list:{
deep:true,
handler(value){
localStorage.setItem("list",JSON.stringify(value))
}
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
}
</style>
MyHeader.vue
<template>
<div>
<input type="text" @keyup.enter="mykeyup($event)">
</div>
</template>
<script>
/* eslint-disable */
import {nanoid} from 'nanoid'
export default {
name:"MyHeader",
props:['getobj'],
data(){
return{
"text":""
}
},
methods:{
mykeyup(event){
if(event.target.value){
let text= event.target.value
this.text=text
let newobj= {id:nanoid(),title:this.text,done:false}
// 通知app组件去添加一个newobj
this.getobj(newobj)
event.target.value=''
}
}
},
}
</script>
<style scoped>
input{
width: 400px;
height: 36px;
line-height: 36px;
border: 1px solid #dedede;
outline: none;
}
</style>
MyList.vue
<template>
<div class="list">
<MyItem v-for="item in list" :key="item.id" :deleteList="deleteList" :item="item" :checktodo="checktodo"/>
</div>
</template>
<script>
/* eslint-disable */
import MyItem from './MyItem.vue'
export default {
name:"MyList",
components:{
MyItem
},
props:['list','checktodo','deleteList'],
data(){
return{
mylist:this.list,
currtext:this.text
}
},
mounted(){
},
}
</script>
<style scoped>
.list{
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
}
</style>
MyItem.vue
<template>
<div>
<div class="item" >
<input type="checkbox" :checked="item.done" @click="change(item.id)">
<span v-show="!isEdit">{{item.title}}</span>
<input v-show="isEdit" type="text"
:value="item.title"
@blur="handlebule(item,$event)">
<button @click="xgitem(item)"> 编辑</button>
<button @click="itemdelete(item.id)"> 删除</button>
</div>
</div>
</template>
<script>
/* eslint-disable */
export default {
name:"MyItem",
data(){
return{
ishide:"hide",
isEdit:false
}
},
props:['item'],
methods:{
divhover(){
this.ishide="show"
},
leave(item,e){
// this.$bus.$emit("updateList",item.id,e.target.value)
this.ishide="hide"
},
itemdelete(id){
// this.deleteList(id)
this.$bus.$emit("deleteList",id)
// for (let index = 0; index < this.itemlist.length; index++) {
// if(this.itemlist[index].id==id){
// this.itemlist.splice(index,1)
// }
// }
},
change(id){
// this.checktodo(id)
this.$bus.$emit("checktodo",id)
},
// 编辑
xgitem(item){
this.isEdit=true
},
// 失去焦点
handlebule(item,e){
this.$bus.$emit("updateList",item.id,e.target.value)
this.isEdit=false
}
}
}
</script>
<style scoped>
.item{
display: flex;
width: 100%;
}
.item button{
display: none;
}
.item:hover button{
display: block;
}
.hide{
display: none;
}
.isEdit{
display: none;
}
.show{
display: block;
}
</style>
MyFooter.vue
<template>
<div>
<div><input type="checkbox" @change="checkAll" name="" :checked="isall" id=""> 已完成{{countnum}},总共 {{ list.length }} </div>
</div>
</template>
<script>
/* eslint-disable */
export default {
name:"MyFooter",
props:['list','checkAllList'],
data(){
return{
num:0
}
},
methods:{
checkAll(e){
let status=e.target.checked
console.log(status)
this.checkAllList(status)
}
},
computed:{
countnum(){
let i=0
for (let index = 0; index < this.list.length; index++) {
if(this.list[index].done){
i+=1
}
}
this.num=i
return this.num
},
isall(){
if(this.num==this.list.length&&this.num>0){
return true
} else{
return false
}
}
}
}
</script>
<style scoped>
</style>
子传父的三种方法
1,:zcf1="zcfmeth1"
2, @zcf2="zcfmeth2"
3, this.$refs.fcz3.$on("zcf3",this.zcfmeth3)
app.vue(父)
<template>
<div id="app">
<!-- 子传父方法1 方法2 v-bind-->
<HelloWorld :zcf1="zcfmeth1" @zcf2="zcfmeth2"/>
<!-- 子传父方法3 $emit -->
<!-- 不加.native,不是原生事件,则为自定义事件 -->
<Person ref="fcz3" @click.native="show"/>
<hr/>
</div>
</template>
<script>
/* eslint-disable */
import Person from './components/Person'
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
Person,HelloWorld
},
data(){
return{
personSex:'male'
}
},
mounted(){
},
methods:{
zcfmeth1(getnum){
console.log("getnum:"+getnum)
},
zcfmeth2(getnum){
console.log("getnum2:"+getnum)
},
zcfmeth3(getnum){
console.log("getnum3:"+getnum)
},
show(){
console.log("原生事件")
}
},
mounted(){
// zcf2触发的时候,调用zcfmeth3方法
// this.$refs.fcz3.$on("zcf3",this.zcfmeth3)
this.$refs.fcz3.$once("zcf3",this.zcfmeth3)
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
}
</style>
HelloWorld.vue
<template>
<div class="hello" >
<p >HelloWorld</p>
<button @click="getValue1">子传父</button>
<button @click="unbind">解绑事件</button>
<button @click="death">销毁当前组件的实例</button>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: ['zcf1'],
data(){
return{
num:1,
}
},
methods:{
getValue1(){
// 方法1
this.zcf1(this.num)
// 方法2
this.$emit("zcf2",this.num)
},
unbind(){
// this.$off("zcf2")//解绑一个事件
// this.$off(["zcf2","shijian2"]);解绑多个事件
this.$off()//解绑所有
},
death(){
this.$destroy()//销毁当前组件的实例,销毁后所有该组件的事件都不奏效了
}
},
}
</script>
<style scoped lang="less">
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
pemainrson.vue
<template>
<div class="hello" >
<p >person</p>
<button @click="getValue3">子传父</button>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
},
data(){
return{
num:1,
}
},
methods:{
getValue3(){
// 方法3
this.$emit("zcf3",this.num)
}
}
}
</script>
<style scoped>
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
全局事件总线:任意组件通信(本质自定义事件)推荐
借助VueComponent.prototype.__proto__=Vue.prototype
todos案例中是逐层传递
更改为
main.js
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus=this
}
}).$mount('#app')
app.vue
mounted(){
this.$bus.$on("checktodo",this.checktodo)
this.$bus.$on("deleteList",this.deleteList)
},
beforeDestroy(){
this.$off(['checktodo','deleteList'])
},
<template>
<div id="app">
<MyHeader ref="headerText" :getobj="getobj"/>
<MyList text="text" :list="list" />
<MyFooter :list="list" :checkAllList="checkAllList"/>
</div>
</template>
<script>
/* eslint-disable */
import MyHeader from './components/todos/MyHeader.vue'
import MyFooter from './components/todos/MyFooter.vue'
import MyList from './components/todos/MyList.vue'
export default {
name: 'App',
components: {
MyHeader,
MyFooter,
MyList
},
data(){
return{
"text":"",
list:JSON.parse(localStorage.getItem("list"))||[],
}
},
mounted(){
this.$bus.$on("checktodo",this.checktodo)
this.$bus.$on("deleteList",this.deleteList)
},
beforeDestroy(){
this.$off(['checktodo','deleteList'])
},
methods:{
getobj(obj){
this.list.unshift(obj)
},
checktodo(id){
for (let index = 0; index < this.list.length; index++) {
if(this.list[index].id==id){
this.list[index].done=!this.list[index].done
}
}
},
// 删除
deleteList(id){
console.log(id)
this.list=this.list.filter((item)=>{
return item.id!==id
})
console.log( this.list)
},
// 全选或者全部选
checkAllList(status){
for (let index = 0; index < this.list.length; index++) {
this.list[index].done=status
}
}
},
watch:{
list:{
deep:true,
handler(value){
localStorage.setItem("list",JSON.stringify(value))
}
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
}
</style>
MyItem.vue
itemdelete(id){
// this.deleteList(id)
this.$bus.$emit("deleteList",id)
},
change(id){
// this.checktodo(id)
this.$bus.$emit("checktodo",id)
}
<template>
<div>
<div class="item" @mouseenter="divhover" @mouseleave="leave">
<input type="checkbox" :checked="item.done" @click="change(item.id)">
<span>{{item.title}}</span>
<button :class="ishide" @click="itemdelete(item.id)"> 删除</button>
</div>
</div>
</template>
<script>
/* eslint-disable */
export default {
name:"MyItem",
data(){
return{
ishide:"hide",
}
},
props:['item'],
methods:{
divhover(){
this.ishide="show"
},
leave(){
this.ishide="hide"
},
itemdelete(id){
// this.deleteList(id)
this.$bus.$emit("deleteList",id)
// for (let index = 0; index < this.itemlist.length; index++) {
// if(this.itemlist[index].id==id){
// this.itemlist.splice(index,1)
// }
// }
},
change(id){
// this.checktodo(id)
this.$bus.$emit("checktodo",id)
}
}
}
</script>
<style scoped>
.item{
display: flex;
}
.hide{
display: none;
}
.show{
display: block;
}
</style>
消息订阅
安装包 npm i pubsub-js
app.js
<template>
<div id="app">
<HelloWorld />
<Person />
<hr/>
</div>
</template>
<script>
/* eslint-disable */
import Person from './components/Person'
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
Person,HelloWorld
},
data(){
return{
personSex:'male'
}
},
mounted(){
},
methods:{
},
mounted(){
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
}
</style>
helloword给person传值
HelloWorld.vue
<template>
<div class="hello" >
<p >HelloWorld</p>
<button @click="sendperson">信息传给person</button>
</div>
</template>
<script>
/* eslint-disable */
import Vue, { watch } from 'vue';
import pubsub from 'pubsub-js'
export default {
name: 'HelloWorld',
data(){
return{
}
},
methods:{
sendperson(){
pubsub.publish('hello',123)
}
},
}
</script>
<style scoped lang="less">
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
person.vue
<template>
<div class="hello" >
<p >person</p>
</div>
</template>
<script>
/* eslint-disable */
import pubsub from 'pubsub-js'
import Vue, { watch } from 'vue';
export default {
name: 'HelloWorld',
props: {
},
data(){
return{
}
},
methods:{
},
mounted(){
this.pubid=pubsub.subscribe("hello",(mesName,param)=>{
console.log("hello",mesName,param)
})
},
beforeDestroy(){
pubsub.unsubscribe(this.pubid)
}
}
</script>
<style scoped>
[v-cloak]{
display: none;
}
.hello{
width: 100%;
height: 100px;
/* border: 1px solid #dedede; */
text-align: left;
}
</style>
$nextTick(在节点更新后再执行)
<template>
<div>
<div class="item" >
<input type="checkbox" :checked="item.done" @click="change(item.id)">
<span v-show="!isEdit">{{item.title}}</span>
<input v-show="isEdit" type="text"
:value="item.title"
@blur="handlebule(item,$event)"
ref="inputTitle">
<button @click="xgitem(item)"> 编辑</button>
<button @click="itemdelete(item.id)"> 删除</button>
</div>
</div>
</template>
<script>
/* eslint-disable */
export default {
name:"MyItem",
data(){
return{
ishide:"hide",
isEdit:false
}
},
props:['item'],
methods:{
divhover(){
this.ishide="show"
},
leave(item,e){
// this.$bus.$emit("updateList",item.id,e.target.value)
this.ishide="hide"
},
itemdelete(id){
// this.deleteList(id)
this.$bus.$emit("deleteList",id)
// for (let index = 0; index < this.itemlist.length; index++) {
// if(this.itemlist[index].id==id){
// this.itemlist.splice(index,1)
// }
// }
},
change(id){
// this.checktodo(id)
this.$bus.$emit("checktodo",id)
},
// 编辑
xgitem(item){
this.isEdit=true//还没有解析模板,所以inputTitle找不到
// $nextTick会在下一次dom更新后再执行
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
},
// 失去焦点
handlebule(item,e){
this.$bus.$emit("updateList",item.id,e.target.value)
this.isEdit=false
}
}
}
</script>
<style scoped>
.item{
display: flex;
width: 100%;
}
.item button{
display: none;
}
.item:hover button{
display: block;
}
.hide{
display: none;
}
.isEdit{
display: none;
}
.show{
display: block;
}
</style>
animate.css动画库
安装 npm i animate.css
引入 import 'animate.css'(哪个页面使用哪个页面引用的方法)
使用
<transition name='fade'
enter-active-class='animated swing'
leave-active-class='animated flash' >
<div v-if="cssanimate">hello css animate</div>
</transition>
<button @click='cssclick'>css动画</button>
axios和跨域问题
解决跨域
1.cors方法 后台开发配置响应头
2.jsonp 利用script src 只可解决get请求的跨域请求
3.代理服务器
vue中使用vue-cli开启代理服务器
服务器与服务器之间不存在跨域问题
4.fecth
5.vue-resouce(目前很少用,vue1.0用的多,同axios使用一样,只需把 axios.get改成 this.$http即可)
代理服务器 解决跨域
axios (名字一样,优先匹配前端资源)
1.安装axios
npm install axios
2.安装脚手架(有则不需要安装)
npm install -g @vue/cli
配置vue.config.js文件
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,//关闭语法检测
//开启代理服务器
devServer: {
proxy: { //api为前缀
'/api': {
target: 'http://localhost:5000/',
pathRewrite:{'^/api':''},
// ws: true, //支持websocket
changeOrigin: true //用于控制请求中的host的值,ture 代理服务器“说谎” false 真实
},
// '/foo': { target: 'http://localhost:5001/' }
}
}
})
3.页面引用
import axios from 'axios'
// 全选或者全部选
checkAllList(status){
// axios.get("http://localhost:8080/api/student").then(
axios.get("http://localhost:8080/text.txt").then(
response=>{
console.log("成功")
},
error=>{
console.log("error")
}
)
}
引入bootstrap
1.在public建文件夹css/bootstrap.min.css(只把bootstrap.min.css放在css文件夹中) 在index.html文件中引入,只用样式
<!DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="<%= BASE_URL %>css/bootstrap.min.css">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2.npm i bootstrap@5.3.0-alpha1 (依赖jquery,需引入jquery)
搜索git用户列表
app.vue
<template>
<div id="app">
<Search @getText="getText" />
<List :userlist="userlist" :loading="loading" />
</div>
</template>
<script>
// https://api.github.com/search/users?q=xxx cors解决了跨域
/* eslint-disable */
import axios from "axios";
import Search from "./components/search/Search";
import List from "./components/search/List";
export default {
name: "App",
components: {
Search,
List,
},
data() {
return {
text: "",
userlist: [],
loading: false,
};
},
mounted() {
// this.getlist("");
},
beforeDestroy() {},
methods: {
getlist(paras) {
this.loading = true;
axios.get("https://api.github.com/search/users?q=" + paras).then(
(response) => {
console.log("成功");
this.loading = false;
this.userlist = response.data.items;
},
(error) => {
console.log("失败");
this.loading = true;
}
);
},
getText(text) {
this.getlist(text);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
}
</style>
Search.vue
<template>
<div>
<input type="text" v-model="text" /><button @click="gettext">搜索</button>
</div>
</template>
<script>
export default {
name: "Search",
data() {
return {
text: "",
};
},
methods: {
gettext() {
this.$emit("getText", this.text);
},
},
};
</script>
<style>
</style>
List.vue
<template>
<div>
<ul>
<li v-for="item in userlist" :key="item.id">
<img :src="item.avatar_url" alt="" />
<p>{{ item.login }}</p>
</li>
</ul>
<div class="loading" v-if="loading && userlist.length == 0">加载中</div>
</div>
</template>
<script less>
export default {
name: "List",
props: ["userlist", "loading"],
data() {
return {};
},
mounted() {
debugger;
console.log(this.userlist);
},
};
</script>
<style scoped lang="less">
ul {
display: flex;
width: 100%;
flex-wrap: wrap;
li {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 10px;
img {
height: 100px;
width: 100px;
}
}
}
</style>
插槽
1.默认插槽
app.vue
<template>
<div id="app">
<div class="app-con">
<!-- 驼峰命名引入方法 -->
<defaut-slot title="书本">
<img
class="siot-img"
src="https://docs.ruthout.com/files/pcsyrightup/20240722110643_71142.jpg?v=1721617604?v=1"
alt=""
/>
</defaut-slot>
<defaut-slot title="爱好"></defaut-slot>
</div>
</div>
</template>
<script>
/* eslint-disable */
import axios from "axios";
import defautSlot from "./components/slot/DefautSlot";
export default {
name: "App",
components: {
defautSlot,
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
.app-con {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
}
</style>
DefautSlot.vue
<template>
<div class="slot">
<h3>{{ title }}</h3>
<!-- 插槽的样式可以写在本文件中,也可以写在父组件中 -->
<slot>我是默认内容</slot>
</div>
</template>
<script>
export default {
name: "DefautSlot",
props: ["title"],
};
</script>
<style scoped lang="less">
.slot {
width: 300px;
height: 500px;
background-color: aquamarine;
h3 {
background-color: yellowgreen;
text-align: center;
}
.siot-img {
width: 100%;
}
}
</style>
2.具名插槽
app.vue
<template>
<div id="app">
<div class="app-con">
<!-- 驼峰命名引入方法 -->
<defaut-slot title="书本">
<img
slot="center"
class="siot-img"
src="https://docs.ruthout.com/files/pcsyrightup/20240722110643_71142.jpg?v=1721617604?v=1"
alt=""
/>
<!-- 方法1 -->
<div slot="footer">
<a href="">《王阳明心学》</a>
<a href="">《清朝其实很有趣》</a>
</div>
</defaut-slot>
<defaut-slot title="爱好">
<!-- 方法2 -->
<template v-slot:footer>
<a href="">跳舞</a>
<a href="">学习</a>
</template>
</defaut-slot>
</div>
</div>
</template>
<script>
/* eslint-disable */
import axios from "axios";
import defautSlot from "./components/slot/DefautSlot";
export default {
name: "App",
components: {
defautSlot,
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
.app-con {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
}
</style>
DefautSlot.vue
<template>
<div class="slot">
<h3>{{ title }}</h3>
<slot name="center"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: "DefautSlot",
props: ["title"],
};
</script>
<style scoped lang="less">
.slot {
width: 300px;
height: 500px;
background-color: aquamarine;
h3 {
background-color: yellowgreen;
text-align: center;
}
.siot-img {
width: 100%;
}
}
</style>
3.作用域插槽(子向父传数据)
app.vue
<template>
<div id="app">
<div class="app-con">
<!-- 驼峰命名引入方法 -->
<defaut-slot title="书本">
<img
slot="center"
class="siot-img"
src="https://docs.ruthout.com/files/pcsyrightup/20240722110643_71142.jpg?v=1721617604?v=1"
alt=""
/>
<!-- 方法1 -->
<template slot-scope="{ book }">
<div v-for="item in book" :key="item.id">
<a href="">{{ item.name }}</a>
</div>
</template>
</defaut-slot>
</div>
</div>
</template>
<script>
/* eslint-disable */
import axios from "axios";
import defautSlot from "./components/slot/DefautSlot";
export default {
name: "App",
components: {
defautSlot,
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
.app-con {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
}
</style>
DefautSlot.vue
<template>
<div class="slot">
<h3>{{ title }}</h3>
<slot :book="book"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: "DefautSlot",
props: ["title"],
data() {
return {
book: [
{ name: "book1", id: "1" },
{ name: "book2", id: "2" },
],
};
},
};
</script>
<style scoped lang="less">
.slot {
width: 300px;
height: 500px;
background-color: aquamarine;
h3 {
background-color: yellowgreen;
text-align: center;
}
.siot-img {
width: 100%;
}
}
</style>
vuex
在vue中是实现集中式状态(数据)管理的一个vue插件
vue2中只能用vuex3版本(npm i vue@3或者npm install vuex@3 --force)
vue3中只能用vuex4版本(npm i vue)自动4版本,不用指定
src下面创建文件夹store>index.js
// 该文件用于创建vuex中最为核心的store
import Vue from 'vue'
// actions用于响应组件中的动作
import Vuex from 'vuex'
Vue.use(Vuex)
const actions={
jia(context,value){
console.log("actions");
context.commit("JIA",value)
}
}
// mutations用于操作数据(store)
// mutations里面方法一般大写
const mutations={
// 对应方法1
// addcount(state,count){
// state.count=count
// },
// 对应方法2
ADDCOUNT(state){
state.count++
},
JIA(state,value){
console.log("mutations");
state.dispnum=++value
}
}
// state用于存储数据
const state={
// 设置全局访问的state对象
count:2,
num:0,
changeShow:true,
dispnum:0
}
// state中的数据进行加工
const getters={
//实时监听state的变化
getCount(state){
console.log(state);
return state.count*10
},
isShow(state){
return state.changeShow
}
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
main.js中修改
import Vue from 'vue'
import App from './App'
// import Vuex from 'vuex'
import store from './store'
Vue.config.productionTip = false
console.log("@@@@@@@@@@@@"+App)
new Vue({
render: h => h(App),
store,
beforeCreate(){
Vue.prototype.$bus=this
}
}).$mount('#app')
文件中引用和修改
<template>
<div id="app">
<div class="app-con">
从store中获取到的值
<!-- {{ this.$store.state.count }} -->
{{ count }}
{{ num }}
<br />
调用 dispatch:
{{ $store.state.dispnum }}
<br />
从store中getters获取到的值
<!-- {{ this.$store.getters.getCount }} -->
{{ getCount }}
</div>
<button @click="add">count++</button>
<!-- 使用mapActions时需要传参数 -->
<button @click="adddisp(n++)">n++</button>
</div>
</template>
<script>
/* eslint-disable */
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
import store from "./store";
export default {
name: "App",
components: {},
data() {
return {
n: 0,
};
},
computed: {
// 程序员自己写的方法
// count() {
// return this.$store.state.count;
// },
// getCount() {
// return this.$store.getters.getCount;
// },
// 借助mapState和mapGetters
//mapState({ count: "count", getCount: "getCount" })为对象,
// 添加...就把里面的值展开放在computed中
...mapState({ count: "count", num: "num" }),
...mapGetters({ getCount: "getCount" }),
},
mounted() {},
methods: {
// 修改store中的值
// add() {
// // 方法1
// // this.$store.commit("ADDCOUNT", this.$store.state.count++);
// //方法2;
// this.$store.commit("ADDCOUNT");
// },
// adddisp() {
// this.$store.dispatch("jia", ++this.n);
// },
// 借助mapMutations去生成对应的方法,方法中会调用commit去联系mutations
...mapMutations({ add: "ADDCOUNT" }),
// 借助mapActions去生成对应的方法,方法中会调用dispatch去联系actions
...mapActions({ adddisp: "jia" }),
},
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
justify-content: flex-start;
color: #2c3e50;
margin-top: 60px;
display: flex;
flex-direction: column;
.app-con {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
button {
width: 80px;
height: 38px;
}
}
</style>
index.js模块化