标签:comm pen 简单 收购 回调函数 问题 reverse getters button
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()
来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty()
将它们转为 getter/setter
。用户看不到 getter/setter
,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}
),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
Vue简单使用:
<div id="app"> <h1>{{msg}} {{num+1}}</h1> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", num:1 } }) </script>
v-bind 简写 :
动态地绑定一个或多个特性,或一个组件 prop 到表达式。
<img v-bind:src="imageSrc"> <!-- 缩写 --> <img :src="imageSrc">
<a v-bind:href="href">百度</a> <a :href="href">百度</a> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el:"#app", data:{ href:"https://www.baidu.com", } }) </script>
<!-- 通过 prop 修饰符绑定 DOM 属性 --> <div v-bind:text-content.prop="text"></div> <!-- prop 绑定。“prop”必须在 my-component 中声明。--> <my-component :prop="someThing"></my-component> <!-- 通过 $props 将父组件的 props 一起传给子组件 --> <child-component v-bind="$props"></child-component>
动态class 可以绑定对象、数组:
<style> .isActive{color:green;} .isError{color:red;} </style> <div> <a v-bind:href="href" :class="{‘isActive‘:see, ‘isError‘:!see}">百度</a> </div> <script> var app = new Vue({ el:"#app", data:{ see:true, } })
例子:
<div> <a v-bind:href="href" :class="[‘aaa‘,‘msg‘,{‘isActive‘:see}]" :style="[{fontSize:‘20px‘,background:‘yellow‘},{color:‘black‘}]">百度</a> </div> //也可以初始化到data里 <div> <a v-bind:href="href" :class="[‘aaa‘,‘msg‘,{‘isActive‘:see}]" :style="styles">百度</a> </div> <script> var app = new Vue({ el:"#app", data:{ styles:[{fontSize:‘20px‘,background:‘yellow‘},{color:‘black‘}], } })
v-if v-else v-show条件渲染
<div v-if="see">if true</div> <div v-else>if false</div> <div v-show="see">show true</div> <script> var app = new Vue({ el:"#app", data:{ see:false, } }) </script>
v-show为false时只是是隐藏了,相当于hidden。
for循环:
<ul> <li v-for="item in person">我是{{item.name}},我{{item.age}}岁了</li> </ul> <ul> <li v-for="item in obj">{{item}}</li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:‘111‘,age:‘20‘}, {name:‘222‘,age:‘22‘},{name:‘333‘,age:‘26‘}, ], obj:{name:‘444‘,age:"30"}, //对象循环的是value值 } }) </script>
如果要循环key的话:第二参
<ul> <li v-for="(item, index) in person">{{index+1}}-我是{{item.name}},我{{item.age}}岁了</li> </ul> <ul> <li v-for="(item, name) in obj">{{name}}:{{item}}</li> </ul>
key:标识,禁止复用。当给li添加key后,就不会在出现Dom复用。
例子:上面的例子如果添加一个按钮,点击按钮删除第一条数据,如果不写key的话,实际删除的是Dom里的第三条li,因为Dom复用的原因,加上key,就不会出现Dom复用,删除的就是第一个li
<button @click="person.splice(0,1)">删除</button> <!--点击删除第一条数据--> <ul> <li v-for="(item, index) in person" :key="item.name"> {{index+1}}-我是{{item.name}},我{{item.age}}岁了 </li> </ul>
input例子:
//input也要加key避免复用 <div v-if="see"> 登录:<input type="text" placeholder="登录" key="login" /> </div> <div v-else> 注册:<input type="text" placeholder="注册" key="register" /> </div> //不过一般input都会绑定v-model监听 <div v-if="see"> 登录:<input type="text" placeholder="登录" v-model="login" /> </div> <div v-else> 注册:<input type="text" placeholder="注册" v-model="register" /> </div>
v-on 简写@ 用到监听
//点击see的值取反,上面条件区域是否显示 <div> <h1>条件渲染</h1> <div v-if="see">if true</div> <div v-else>if false</div> <div v-show="see">show true</div> </div> <div> <h1>v-on</h1> <button v-on:click="see = !see">click me</button> </div>
复杂的功能可以写个方法:
<div> <h1>v-on</h1> <button @click="click">click me</button> </div> <script> var app = new Vue({ el:"#app", data:{ see:false, }, methods: { click:function(){ this.see = !this.see; //this指向当前app } }, }) </script>
传参:
<div> <h1>v-on</h1> <button @click="click(‘hello‘)">click me</button> </div> <script> var app = new Vue({ el:"#app", data:{ see:false, }, methods: { click:function(val){ this.see = !this.see; //this指向当前app alert(val); } }, }) </script>
监听input事件:
<input v-on:input="click" />
v-model 双向绑定:
<div><input type="text" v-model="textVal" />{{textVal}}</div> <script> var app = new Vue({ el:"#app", data:{ textVal:"", //初始化 }, methods: { //事件监听 }, }) </script>
单选:
<div> <input type="radio" value="0" id="a" v-model="radioVal" /><label for="a">A</label> <input type="radio" value="1" id="b" v-model="radioVal" /><label for="b">B</label> <input type="radio" value="2" id="c" v-model="radioVal" /><label for="c">C</label> {{radioVal}} </div> <script> var app = new Vue({ el:"#app", data:{ radioVal:"", }, }) </script>
复选:
<div> <input type="checkbox" value="0" id="aa" v-model="checkboxVal" /><label for="aa">A</label> <input type="checkbox" value="1" id="bb" v-model="checkboxVal" /><label for="bb">B</label> <input type="checkbox" value="2" id="cc" v-model="checkboxVal" /><label for="cc">C</label> {{checkboxVal}} </div> <script> var app = new Vue({ el:"#app", data:{ checkboxVal:[], //初始化数组 } })
下拉选择框:
<div> <select v-model="selectVal"> <option value="0">A</option> <option value="1">B</option> <option value="2">C</option> <option value="3">D</option> </select> {{selectVal}} </div> <script> var app = new Vue({ el:"#app", data:{ selectVal:0, //初始为0默认选第一 } }) </script>
上面的都可以设置初始值:
data:{ textVal:"111", radioVal:"0", checkboxVal:[0], selectVal:0, }
计算属性:computed
<h1>{{msg.split(‘‘).reverse().join(‘‘)}}</h1> //dlroW olleH 截取反转拼接 //computed实现 <h1>{{msg}}</h1> <h2>{{reversedMsg}}</h2> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", }, computed: { //计算属性 reversedMsg:function(){ return this.msg.split(‘‘).reverse().join(‘‘); } }, }) </script> //也可以通过方法实现 <h1>{{msg}}</h1> <h2>{{reversedMsg()}}</h2> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", }, methods: { //事件监听 reversedMsg:function(){ return this.msg.split(‘‘).reverse().join(‘‘); } }, }) </script>
不建议用方法的形式,推荐使用计算属性。
结果:
例子:展示年龄大于30岁的数据
<ul> <h1>v-for</h1> <li v-for="(item, index) in person" v-if="item.age > 30"> {{index+1}}-我是{{item.name}},我{{item.age}}岁了 </li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:‘111‘,age:‘20‘}, {name:‘222‘,age:‘28‘},{name:‘333‘,age:‘36‘}, ], }, }) </script> //官方推荐用计算属性写法 <ul> <li v-for="(item, index) in newPerson"> {{index+1}}-我是{{item.name}},我{{item.age}}岁了 </li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:‘111‘,age:‘20‘}, {name:‘222‘,age:‘28‘},{name:‘333‘,age:‘36‘}, ], }, computed: { //计算属性 newPerson:function(){ return this.person.filter(function(item){return item.age > 30}); } }, }) </script>
监听属性:watch
//输入问题时答案显示waiting,1秒钟后显示404 <div> question:<input type="text" placeholder="enter" v-model="question" /><br> answer:{{answer}} </div> <script> var app = new Vue({ el:"#app", data:{ question:"", answer:"no answer", }, watch: { //监听属性 question:function(){ this.answer = "waiting"; var _this = this; setTimeout(function(){ _this.answer = "404"; },1000); } }, }) </script>
有两种情况变动的数组,是VUE不能检测到的,也不会触发视图的更新。
举个例子:
var vm = new Vue({ data: { items: [‘a‘, ‘b‘, ‘c‘] } }) vm.items[1] = ‘x‘ // 不是响应性的 vm.items.length = 2 // 不是响应性的
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue
相同的效果,同时也将触发状态更新:
// Vue.set Vue.set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,可以使用 splice
:
vm.items.splice(newLength)
组件注册:
在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了:
Vue.component(‘my-component-name‘, { /* ... */ })
该组件名就是 Vue.component
的第一个参数。
定义组件名的方式有两种:
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>
。
Vue.component(‘my-component-name‘, { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name>
和 <MyComponentName>
都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
Vue.component(‘MyComponentName‘, { /* ... */ })
全局注册:
到目前为止,我们只用过 Vue.component
来创建组件:
Vue.component(‘my-component-name‘, { // ... 选项 ... })
这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue
) 的模板中。比如:
Vue.component(‘component-a‘, { /* ... */ }) Vue.component(‘component-b‘, { /* ... */ }) Vue.component(‘component-c‘, { /* ... */ }) new Vue({ el: ‘#app‘ }) <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
局部注册:
可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
然后在 components
选项中定义你想要使用的组件:
new Vue({ el: ‘#app‘, components: { ‘component-a‘: ComponentA, ‘component-b‘: ComponentB } })
对于 components
对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { ‘component-a‘: ComponentA }, // ... }
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from ‘./ComponentA.vue‘ export default { components: { ComponentA }, // ... }
props
/ $emit
父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
(1)父组件给子组件传值:父组件通过 props 向下传递数据给子组件。
//父Home.vue <Nav :NavActiveIndex="activeIndex"></Nav> <script> import Nav from "./Nav.vue"; export default { name: "home", components: { Nav }, data() { return { activeIndex: ‘/home/buycar‘, activeIndex2: ‘1‘ }; }, </script> //子Nav.vue {{NavActiveIndex}} //home/buycar 实现默认绑定购物车 <el-menu :default-active="NavActiveIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect" background-color="#545c64" text-color="#fff" router active-text-color="#ffd04b"> <script> export default { name: "nav", props:{ NavActiveIndex:String }, }, </script>
(2)子组件向父组件传值:
通过触发组件自定义方式来实现给父组件传值:
//父组件 Home.vue //绑定自定义事件fromNavVal <Nav v-if="NavOpen" :NavActiveIndex="activeIndex" @fromNavVal="fromNavVal"></Nav> <div v-else>折叠{{activeIndex2}}</div> <script> import Nav from "./Nav.vue"; export default { name: "home", components: { Nav }, data() { return { activeIndex: ‘/home/buycar‘, activeIndex2: ‘1‘, NavOpen: true, //Nav打开折叠 }; }, methods: { fromNavVal(val){ //自定义方法fromNavVal this.activeIndex2 = val; this.NavOpen = false; } } }; </script> //子组件 Nav.vue <button @click="click">折叠Nav</button> <script> export default { name: "nav", props:{ NavActiveIndex:String }, data() { return { activeIndex: ‘1‘, activeIndex2: ‘123‘ }; }, methods: { handleSelect(key, keyPath) { console.log(key, keyPath); }, click(){ //$emit(事件名,传出的参数) this.$emit(‘fromNavVal‘,this.activeIndex2) } } }; </script>
2、event bus 事件处理中心
var Event=new Vue(); Event.$emit(事件名,数据); Event.$on(事件名,data => {});
例子:实现购物车数量。
先建立一个事件处理中心js文件,bus.js:
import Vue from "vue"; const EventBus = new Vue(); export default EventBus;
在Nav.vue里引入bus.js
<el-menu-item index="/home/buycar">购物车{{buycarCount}}</el-menu-item> <!--4展示购物车数量--> //1首先在Nav.vue里引入bus.js <script> import bus from ‘@/assets/bus.js‘; export default { name: "nav", props:{ NavActiveIndex:String }, data() { return { activeIndex: ‘1‘, activeIndex2: ‘123‘, buycarCount: 0, //3购物车数量 }; }, created() { //2注册组件 bus.$on("buycarCountChang",(num) =>{ //监听事件buycarCountChang this.buycarCount = num; }) }, }
在buycar.vue里引入bus.js
<script> //1.引入bus.js import bus from ‘@/assets/bus.js‘; export default { 。。。 watch: { //2.监听list改变 list:{ //list监听:当key是list,值是函数的时候,是不深度遍历的,也就是对象改变的时候不监听,所以用这种写法 handler:function(){ //handler处理函数 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) bus.$emit("buycarCountChang",count); //购物车数量改变的时候触发buycarCountChang事件 }, deep:true //true的时候深度监听 } } }
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
核心模块:State、Getters、Mutations、Actions、Module
Getter相当于vue中的computed计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,这里我们可以通过定义vuex的Getter来获取,Getters 可以用于监听、state中的值的变化,返回计算后的结果。
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。但是,mutation只允许同步函数。
官方并不建议我们直接去修改store里面的值,而是让我们去提交一个actions,在actions中提交mutation再去修改状态值。可以异步操作
Action 类似于 mutation,不同在于:
Vuex 允许我们将store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
例子:购物车数量改为用VueX管理
在store/index.js里
export default new Vuex.Store({ state: { buycarCount: 0 //1初始化buycarCount数量0 }, mutations: { //2定义同步方法改变count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: {}, modules: {} });
Nav.vue:
<el-menu-item index="/home/buycar">购物车{{this.$store.state.buycarCount}}</el-menu-item> <!--2直接获取值--> <script> //import bus from ‘@/assets/bus.js‘; 不用了注释掉 export default { 。。。 1.list不需要监听事件了,注释掉 created() { //生命周期 // bus.$on("buycarCountChang",(num) =>{ //监听事件buycarCountChang 购物车数量改变的时候触发事件 // this.buycarCount = num; // }) }, }; </script>
buycar.vue:
<script> //import bus from ‘@/assets/bus.js‘; 同样注释掉 export default { 。。。 watch: { list:{ //监听list handler:function(){ //handler处理函数 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //购物车数量改变时触发buycarCountChang事件 //1注释上面代码改为下面代码 this.$store.commit("changbuycarCount", count); //购物车数量改变时触发changbuycarCount方法 }, deep:true //true的时候深度监听 } } }; </script>
OK,这样就实现了VueX改变接收购物车数量。
当然如果觉得{{this.$store.state.buycarCount}}这种获取数据太繁琐,可以用映射函数来实现。
Nav.vue:
<el-menu-item index="/home/buycar">购物车{{buycarCount}}</el-menu-item> <!--3直接使用{{buycarCount}}--> <script> //import bus from ‘@/assets/bus.js‘; import {mapState} from "vuex"; //1映射函数 结构赋值 映射到计算属性 export default { name: "nav", props:{ NavActiveIndex:String }, data() { return { activeIndex: ‘1‘, activeIndex2: ‘123‘, //buycarCount: 0, //4注释掉上面定义的购物车数量 重名冲突 }; }, computed: { //计算属性 ...mapState([‘buycarCount‘]), //2用...展开运算符把buycarCount展开在资源属性里 }, }; </script>
好了,用映射函数同样实现VueX改变接收购物车数量的功能。
那么我们的mutations也是有映射函数的,在buycar.vue里也用映射函数的方式来实现:
<script> //import bus from ‘@/assets/bus.js‘; import {mapMutations} from ‘vuex‘ //1映射函数 映射到方法 export default { 。。。 methods: { //2...展开运算符,传一个数组映射成同名的方法changbuycarCount ...mapMutations([ "changbuycarCount" ]), 。。。 }, watch: { //watch是vue的监听,一旦监听对象有变化就会执行相应操作 list:{ //监听list 当key是list,值是函数的时候,是不深度遍历的,也就是说对象改变的时候不监听,所以用这种写法 handler:function(){ //handler处理函数 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //购物车数量改变时触发buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //购物车数量改变时触发changbuycarCount方法 //3注释上面代码改为下面代码 this.changbuycarCount(count); //this实例changbuycarCount方法,把count传过来 }, deep:true //true的时候深度监听 } } }; </script>
OK,功能同样实现。
下面action来实现异步操作:
在store/index.js里定义方法:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { buycarCount: 0 //初始化0 }, mutations: { //定义同步方法改变count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: { //异步改变 第1个参数是上下文 第2个接收调用传参 asyncchangbuycarCount(content, num) { //一般实现ajax,这里就不调用接口,实现一个定时器功能 setTimeout(() => { content.commit("changbuycarCount", num); //content对象的commit方法,来触发mutations }, 1000); } }, modules: {} });
修改buycar.vue:
<script> //import bus from ‘@/assets/bus.js‘; import {mapMutations} from ‘vuex‘ //映射函数 映射到方法 export default { 。。。 watch: { //watch是vue的监听,一旦监听对象有变化就会执行相应操作 list:{ //监听list 当key是list,值是函数的时候,是不深度遍历的,也就是说对象改变的时候不监听,所以用这种写法 handler:function(){ //handler处理函数 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //购物车数量改变时触发buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //购物车数量改变时触发changbuycarCount方法 //this.changbuycarCount(count); //this实例changbuycarCount方法,把count传过来 //1注释上面代码改为下面代码 this.$store.dispatch("asyncchangbuycarCount", count); }, deep:true //true的时候深度监听 } } }; </script>
现在实现操作,1秒钟后改变购物车数量。
action同样有映射函数,直接修改buycar.vue:
<script> //import bus from ‘@/assets/bus.js‘; import {mapMutations, mapActions} from ‘vuex‘ //1映射函数 添加mapActions export default { 。。。 methods: { ...mapMutations(["changbuycarCount"]), //...展开运算符,传一个数组映射成同名的方法changbuycarCount ...mapActions(["asyncchangbuycarCount"]), //2展开 asyncchangbuycarCount }, watch: { //watch是vue的监听,一旦监听对象有变化就会执行相应操作 list:{ //监听list 当key是list,值是函数的时候,是不深度遍历的,也就是说对象改变的时候不监听,所以用这种写法 handler:function(){ //handler处理函数 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //购物车数量改变时触发buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //购物车数量改变时触发changbuycarCount方法 //this.changbuycarCount(count); //this实例changbuycarCount方法,把count传过来 //this.$store.dispatch("asyncchangbuycarCount", count); //异步的改变asyncchangbuycarCount //3注释掉上面代码改为下面代码 this.asyncchangbuycarCount(count); }, deep:true //true的时候深度监听 } } }; </script>
同样实现1秒钟后改变购物车数量。
下面用getters来实现在导航Nav上显示名字的功能。
在store/index.js里定义:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { buycarCount: 0, //初始化0 //1建立用户信息 userinfo: { name: "latte", age: 20 } }, mutations: { //定义同步方法改变count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: { //异步改变 第1个参数是上下文 第2个接收调用传参 asyncchangbuycarCount(content, num) { //一般实现ajax,这里就不调用接口,实现一个定时器功能 setTimeout(() => { content.commit("changbuycarCount", num); //content对象的commit方法,来触发mutations }, 1000); } }, //2.getters返回计算属性 getters: { userName(state) { return state.userinfo.name; //返回用户name } }, modules: {} });
修改Nav.vue:
<div>{{userName}}</div> <!--3展示在nav--> <script> //import bus from ‘@/assets/bus.js‘; import {mapState, mapGetters} from "vuex"; //1直接用映射函数 添加mapGetters export default { 。。。 computed: { //计算属性 ...mapState([‘buycarCount‘]), //用...展开运算符把buycarCount展开在资源属性里 ...mapGetters([‘userName‘]), //2...展开 }, 。。。 }; </script>
效果实现了。
注意: 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。当然,还是需要您自己来决定。
标签:comm pen 简单 收购 回调函数 问题 reverse getters button
原文地址:https://www.cnblogs.com/joe235/p/12015420.html