包含内容#NavigationBar
、#TabBar
、#MainContext
;css
为何#NavigationBar
、#TabBar
分在Layout
中,而不是components
中?vue
代码上其实是没有差异的,只是认为#NavigationBar
、#TabBar
是加载一次的,而非复用,且属于页面布局内容。webpack
Vue实例化的根组件,咱们在这里进行布局:web
src/App.vue
文件:json
<template> <div id="app"> <navigation-bar></navigation-bar> <router-view></router-view> <tab-bar></tab-bar> </div> </template> <script> import TabBar from "@/views/layout/TabBar"; import NavigationBar from "@/views/layout/NavigationBar"; export default { name: 'App', components: { 'navigation-bar': NavigationBar, 'tab-bar':TabBar, } } </script> <style> ...//未动原有样式; </style>
<template />
标识 其内部的HTML为Vue Template。<template />
内部必有一个且惟一的节点(这里是div#app
)包裹内容(即便只是一串字符)-->若存在同级节点,则会报错(这是由于VNode会经过createElement('div')来建立真实节点,只能是单个元素);components
属性以键值对的形式引入组件,模板(HTML)中使用的标签名为键名(自定义元素VNode),值为导入的组件模块;components
定义组件使用的方式,限制了组件应用的范围。即:若是你在其它文件直接使用<navigation-bar></navigation-bar>
,控制台会报错:组件未注册-->这就是组件的局部注册。components
注册一次。import TabBar from "@/views/layout/TabBar";
路径以@
起,这是由于build/webpack.base.conf.js
中配置的路径别名'@' === "resolve('src')"
:resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), //能够追加当前项目下,想快捷访问的文件目录 } },
临时定义的组件文件:segmentfault
src/views/layout/NavigationBar.vue
文件:数组
<template> <header>NavigationBar</header> </template> <style scoped> header{ border-bottom: 4px solid #821910; } </style>
style[scoped]
定义一份样式,其做用范围仅限于当前文件(又可称模块)模板中的元素。TabBar.vue
中的header
元素就没有使用到该文件中的对应样式,这就是局部做用域的样式。<template/>
中的元素起做用,想改变body
的样式,很差意思,请全局导入或不使用局部做用域。src/views/layout/TabBar.vue
文件:app
<template> <div> <header>测试是否和NavigationBar同样的效果</header> <footer>TabBar</footer> </div> </template> <style scoped> footer{ border-bottom: 4px solid #07776e; } </style>
#NavigationBar
中分左右结构,左边按钮后退,右边按钮更新页面。ide
更新页面只是更新数据,而不是整个页面的刷新,每一个页面更新数据的接口不一样,因此,要做为组件属性传入。函数
在src/views/layout/navigationBar.vue
中:
<template> <header class="navigation_bar"> <button @click="goBack" class="navigation_back"> <i class="arrow"></i> <span class="back_tip">关闭</span> </button> <h2 v-if="hasTitle" class="navigation_title">{{title}}</h2> <button class="refresh" @click="onRefresh">刷新</button> </header> </template>
#NavigationBar
的Template部分。@click
是v-on:click
的简写,用于绑定点击事件。v-if
是Vue
中的条件指令,根据返回的布尔值动态添加或移除DOM元素。<script> /** * @title:头部标题; * @refresh:刷新处理函数; */ export default { props: ['title', 'refresh'], computed: { hasTitle() { return this.title && this.title.trim(); }, }, methods: { goBack() { this.$router.back() }, onRefresh() { this.refresh(); }, }, } </script>
#NavigationBar
的组件配置对象。props
为父级(调用该组件的组件)传过来的属性。
传值方式<navigation-bar title="我是标题" :refresh="refresh"></navigation-bar>
(须要在src/App.vue
中定义refresh
函数)
title
传的值为字符串,不须要:
前缀;:refresh
传的值为非字符串(数字、布尔值、函数、数组、对象...),:
为前缀,值为Javascript表达式计算结果;this.title
引用props
的值。{{title}}
引用。methods
为该组件内,元素绑定的事件处理函数。
this.refresh()
引用。@click="onRefresh"
调用,传入的是函数应用;若传参,如@click="onRefresh(param)"
调用。computed
自己写法和函数定义一致,然而,其自己是一个data
(数据源),字段名为函数名,值为函数的返回值。
props
一致。区别 | method | computed |
---|---|---|
类型 | 函数 | 数据变量 |
参数 | 能够带参 | 不带参(非函) |
触发 | 交互时触发 | 声明内部的this属性的值变化时执行 |
这里样式请你们随意设定,我使用的是flexBox布局。
点击刷新,我定义了console.log('refresh success')
。
#TabBar
分如下状况:
每一个视图中#TabBar
按钮是不一样的,因此,按钮的配置要看成组件属性传入(控制变化的量)。
const tabBars = [ { label: '提交', eventType: 'click', disabled: false, callBack(vm) { console.log('单击,提交'); } }, { label: '取消', eventType: 'dblclick', //该事件在手机模式下没法响应呢,只能在PC模式下调试 disabled: false, callBack(vm) { console.log('双击,取消'); } } ]
src/views/layout/TabBar.vue
的模板:
<template> <footer class="tab-bar" v-if="isRender"> <div class="tab-button" v-for="tab in tabBars" :key="tab.label"> <template> <tab-button :el="$parent" :disabled="tab.disabled" :event="tab.eventType" :callBack="tab.callBack"> <span>{{tab.label}}</span> </tab-button> </template> </div> </footer> </template>
v-for="tab in tabBars"
是Vue
中的循环结构,搭配:key
使用,优化Vue
的渲染机制;
tabBars
进行遍历,tab
为数组中的元素。key
值,在更新时,会复用组件,而不是销毁后,再建立一个新的组件。<tab-button :el="$parent" :disabled="tab.disabled" :event="tab.eventType" :callBack="tab.callBack">
这是是一个新组件的引用。
$parent
是组件实例#TabBar
的父实例(#App
)。src/views/layout/TabBar.vue
组件配置对象:
<script> const tabButton = { render(createElement) { return createElement( 'button', { "class": this.className, on: { [this.event]: this.tabClick, }, }, this.$slots.default, //指代<span>{{tab.label}}</span> ) }, props: ['event', 'callBack', 'disabled','el'], computed: { className() { return this.disabled ? 'tab-label disabled' : 'tab-label'; } }, methods: { tabClick() { if (this.disabled) return; this.callBack && this.callBack(this.el) } } } export default { components: { 'tab-button': tabButton }, props: ['tabBars'], computed: { isRender() { const isRender = _.isArray(this.tabBars) && this.tabBars.length !== 0; return isRender; }, }, } </script>
这里使用了另外一种方式定义组件tabButton
,其与 单文件组件 的区别仅仅在于使用render
方法定义模板。
优点:定义出来的组件更具备灵活性,在这里on
属性能够动态绑定事件类型。
[this.event]
是做为参数传进来的呢!Vue
规定的成员属性构建,区别只在于Template
的写做模式。underscore.js
(_.isArray
),须要在build/webpack.base.conf.js
中配置:const webpack = require('webpack'); ... module.exports = { ... module:{ ... }, plugins:[ new webpack.ProvidePlugin({ _: 'underscore', }), ], ...
而后,underscore
在全局可用。
由于这里的配置对dev
和prod
环境是一致的,因此,直接在build/webpack.base.conf.js
中配置了。
最终,咱们要作一个顶天立地的内滚动结构(使用flexBox布局便可):
src/App.vue
样式中:
<style lang="scss" scoped> @import "@/styles/mixins.scss"; #app { @include flex($direction: column); } .main-context { flex-grow: 1; overflow: hidden; overflow-y: auto; } </style>
其中src/styles/mixins.scss
:
@mixin flex($direction:row, $alignItems: stretch, $justifyContent: space-between, $basis: auto,$wrap:nowrap) { display: flex; flex-direction: $direction; align-items: $alignItems; justify-content: $justifyContent; flex-basis: $basis; flex-wrap: $wrap; }
#NavigationBar
、#TabBar
替换原临时搭建的对应组件,我相信你能处理好,对吧?!#App
小节中,是怎样注册局部组件的,若是想要在项目全部模板中能够直接使用标签名来应用组件,该怎么处理呢?#App
小节中,如何定义局部样式的,若是想让app.vue
中header的样式全局可用,该怎么处理呢?render
函数如何渲染组件模板,使用该方法如何定义组件?slot
用于什么状况下呢,有什么好处?