vue 的基础应用(下)
上篇聚焦于基础知识的介绍;本篇聚焦于基础知识的应用。
递归组件
组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事。我们实现一个自定义树的组件。请看示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src='vue.js'></script> </head> <body> <div id='app'> <custom-tree :list='treeData'></custom-tree> </div> <script> // 递归组件 - 自定义树 Vue.component('custom-tree', { // 给组件命名 name: 'custom-tree', // {1} props: ['list'], template: ` <ul> <li v-for='item in list'> {{item.name}} <!-- v-if 指定退出的条件,防止无线递归 --> <custom-tree :list='item.children' v-if='item.children' ></custom-tree> </li> </ul> ` }) const app = new Vue({ el: '#app', // 数据也需要符合递归的条件 data: { treeData: [ // {2} { name: 'a', children: [ { name: 'b' }, { name: 'c' } ] }, { name: 'd', children: [ { name: 'e', children: [ { name: 'f' }, { name: 'g' } ] } ] } ] } }) </script> </body> </html>
// 页面输出: a b c d e f g
有3点需要注意:
给组件设置 name (行{1})
使用一个条件来结束无限递归。这里使用了 v-if
数据得满足递归(行{2})
Tip: 后续不在提供完整的代码,省略 head、body 等。
动态组件
vue 提供了 来动态的挂载组件。请看示例:
<div id='app'> <!-- vue 提供了 <component> 来动态的挂载组件 --> <component v-bind:is="currentComponent"></component> <button @click='switchHandle'>切换组件</button> </div> <script> var comC = { template: `<p>我是组件 C</p>` }; var app = new Vue({ el: '#app', data: { currentComponent: 'comB' }, // 局部注册。components 选项中定义你想要使用的组件 components: { comA: { template: `<p>我是组件 A</p>` }, comB: { template: `<p>我是组件 B</p>` }, comC: comC }, methods: { switchHandle: function(){ let map = { 'comA': 'comB', 'comB': 'comC', 'comC': 'comA' }; // 动态切换组件 this.currentComponent = map[this.currentComponent] } } }) </script>
// 页面输出: 我是组件 A 切换组件 // 点击按钮(‘切换组件’),依次显示'我是组件 B'、'我是组件 C'...
内置的组件 component 根据属性 is 的值来决定哪个组件被渲染。
nextTick
Vue.nextTick( [callback, context] )
用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。—— 不明白的化,请看示例:
<div id="example">{{message}}</div> <script> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改数据 // {20} console.log(vm.$el.textContent === 'new message') // false {21} Vue.nextTick(function () { console.log(vm.$el.textContent === 'new message') // true {22} }) </script>
更改数据后(行{20},dom 元素中的内容其实没有得到更新,输出 false(行{21});在 Vue.nextTick() 方法中才被更新,输出 true(行{22})。
这里涉及到 vue 中一个概念:异步更新。
假如在更改状态(行{20})后,dom 元素立马得到更新(行{21}),也就是输入出 true,那么用户使用 for 循环改变某个状态 100 次,dom 元素就得更新 100 次,是否觉得浪费!所以 vue 的策略是:使用异步更新,也就是不会马上更新 dom。
手动挂载
vm.$mount( [elementOrSelector] )
用法:
如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态。我们可以使用 vm.$mount() 方法手动。
我们创建一个组件,三秒后再挂载它。请看示例:
<div id="app"></div> <script> // Vue.extend() 使用基础 Vue 构造器,创建一个“子类” var MyComponent = Vue.extend({ template: '<div>Hello!</div>' }) // 创建并挂载到 #app (会替换 #app) setTimeout(function(){ // 3 秒后页面上才会看见 Hello! new MyComponent().$mount('#app') }, 3000) </script>
3 秒后,页面上才会看见 Hello!。
数字输入框组件
需求:数字输入框只能输入数字,有两个按钮,分别是减1和加1。此外还可以设置初始值、最大值、最小值,数字改变时通知父组件。
请看完整代码:
<div id='app'> 父组件 value = {{value}} <!-- v-model 实现双向绑定 --> <custom-number v-model='value' :max-value='maxValue' :min-value='minValue' :step='10' ></custom-number> </div> <script> Vue.component('custom-number', { props:{ // 必须是一个数字 value: { type: Number, default: 0 }, maxValue: { type: Number, // 正无穷大 default: Number.POSITIVE_INFINITY }, minValue: { type: Number, // 负无穷大 default: Number.NEGATIVE_INFINITY }, // 默认加减 1 step: { type: Number, default: 1 } }, data: function(){ return { inputValue: this.value } }, created: function(){ // 处理:初始值不在最大值和最小值的范围内 this.update(this.value) }, computed: { // 减(-)不可点 minIsDisabled: function(){ return this.inputValue <= this.minValue }, maxIsDisabled: function(){ return this.inputValue >= this.maxValue } }, watch: { // 监听 inputValue,通知父组件 inputValue: function(val, oldVal){ this.$emit('input', val) }, // 父组件改变值,子组件的值也跟着改变 value: function(val){ this.update(val); } }, template: ` <div> <button :disabled="minIsDisabled" @click='minus'> - </button> <input :value='inputValue' @change='changeHandle' > <button :disabled="maxIsDisabled" @click='add'> + </button> <p> 子组件 inputValue = {{inputValue}} </p> </div> `, methods: { // 如果输入值不是数字,则不会更改值 changeHandle: function(e){ var val = e.target.value; this.update(val, e.target) }, // obj 是否是数字。摘自 jquery isNumeric: function(obj) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, minus: function(){ this.update(this.inputValue - this.step); }, add: function(){ this.update(this.inputValue + this.step); }, // 是数字才更新 update: function(val, target={}){ if(!this.isNumeric(val)){ // 将 input 值置为上次的值 target.value = this.inputValue; return; } val = Number(val); // 大于最大值,则重置为最大值 if (val > this.maxValue){ val = this.maxValue }else if(val < this.minValue){ val = this.minValue } this.inputValue = val; } } }); var app = new Vue({ el: '#app', data: { value: 10, maxValue: 100, minValue: 1 } }) </script>
// 页面输出: 父组件 value = 10 - 10 + 子组件 inputValue = 10 // 点击 +(每次加减10) 父组件 value = 20 - 20 + 子组件 inputValue = 20 // 继续点击2次 // 减号(-)变为不可点 父组件 value = 1 - 1 + 子组件 inputValue = 1
Tabs 标签页
需求:实现一个常用的组件 —— tabs 标签页。
注:不明白需求的可以看element-ui-tabs
思路:
定义组件 el-tabs
定义 el-tabs 的子组件 tab-pane
父子组件通信使用 vm.KaTeX parse error: Expected 'EOF', got '和' at position 8: parent 和̲ vm.children
请看完整代码:
<style> ul{margin:0;padding: 0;border-bottom: 1px solid;margin-bottom: 10px;} li{display:inline-block;margin-right:10px;cursor:pointer;} .active{color:#409eff;} </style> <div id='app'> <el-tabs v-model="activeKey"> <el-tab-pane label="用户管理"> 用户管理内容 <p>我是 A</p> </el-tab-pane> <el-tab-pane label="配置管理" name="second">配置管理内容</el-tab-pane> <el-tab-pane label="角色管理"> 角色管理内容 <p>我是 C</p> </el-tab-pane> <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿内容</el-tab-pane> </el-tabs> </div> <script> // 父组件 Vue.component('el-tabs', { props:{ value:{ type: [String, Number] } }, data: function(){ return { currentTab: this.value, // 存放 tab tabLists: [] } }, watch: { currentTab: function(){ this.updateStatus(); }, // 处理:父组件更改 value value: function(val, oldVal){ this.currentTab = val } }, template: ` <div> <ul> <li v-for='(item, index) in tabLists' :class='{active: item.name === currentTab}' @click='handleClick(index)' >{{item.label}}</li> </ul> <slot></slot> </div> `, methods: { // 取得 tab-pane getTabPanes: function(){ return this.$children.filter(item => { return item.$options.name === 'tab-pane' }) }, // 更新 tabLists updateTabLists: function(){ let tabLists = []; this.getTabPanes().forEach((item, index) => { if(!item.id){ item.id = index } tabLists.push({ label: item.label, name: item.id }) // 默认展示第一个 if(index === 0){ if(!this.currentTab){ this.currentTab = item.id; } } }) this.tabLists = tabLists; this.updateStatus() }, handleClick: function(index){ this.currentTab = this.tabLists[index].name; console.log(`name = ${this.currentTab}`) this.updateStatus() }, // 让子组件显示或隐藏 updateStatus: function(){ this.getTabPanes().forEach(item => { item.updateShow(this.currentTab === item.id) }) } } }); // 子组件 Vue.component('el-tab-pane', { name: 'tab-pane', props: { // 标签标题 label:{ type: String, default: '' }, // pane 的名字,不必填 name: [String, Number] }, data: function(){ return { // 显示与否,由父组件决定 show: false, // 不允许通过父组件更改 props 中的属性 name // 用 id 来替代 name 属性 id: this.name } }, created: function(){ this.$parent.updateTabLists(); }, template: ` <div v-if='show'> <slot></slot> </div> `, methods: { updateShow: function(v){ this.show = v; } } }); const app = new Vue({ el: '#app', data: { // 当前选中的 tab activeKey: 2 } }) </script>
// 页面输出: // 第一行是 4 个 tab,现在是`角色管理`被选中 用户管理 配置管理 角色管理 定时任务补偿 角色管理内容
我是 C
作者:彭加李
原文出处:https://www.cnblogs.com/pengjiali/p/14673504.html