视图
包含内容#NavigationBar
、#TabBar
、#MainContext
;
为什么#NavigationBar
、#TabBar
分在Layout
中,而不是components
中?
代码上实际上是没有差别的,只是认为#NavigationBar
、#TabBar
是加载一次的,而非复用,且属于页面布局内容。
App.vue
Vue实例化的根组件,我们在这里进行布局:
src/App.vue
文件:
<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'), //可以追加当前项目下,想快捷访问的文件目录
}
},
临时定义的组件文件:
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
文件:
<template>
<div>
<header>测试是否和NavigationBar一样的效果</header>
<footer>TabBar</footer>
</div>
</template>
<style scoped>
footer{
border-bottom: 4px solid #07776e;
}
</style>
显示效果:
NavigationBar
#NavigationBar
中分左右结构,左边按钮后退,右边按钮更新页面。
更新页面只是更新数据,而不是整个页面的刷新,每个页面更新数据的接口不同,所以,要作为组件属性传入。
在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
的值。 - 在模板中,作元素的innerHTML内容时,如
{{title}}
引用。
-
-
methods
为该组件内,元素绑定的事件处理函数。- 在程序中,如
this.refresh()
引用。 - 在模板中,如
@click="onRefresh"
调用,传入的是函数应用;若传参,如@click="onRefresh(param)"
调用。
- 在程序中,如
-
computed
本身写法和函数定义一致,然而,其本身是一个data
(数据源),字段名为函数名,值为函数的返回值。- 使用方式与
props
一致。
- 使用方式与
区别 | method | computed |
---|---|---|
类型 | 函数 | 数据变量 |
参数 | 可以带参 | 不带参(非函) |
触发 | 交互时触发 | 声明内部的this属性的值变化时执行 |
显示效果
这里样式请大家随意设定,我使用的是flexBox布局。
点击刷新,我定义了console.log('refresh success')
。
TabBar
#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]
是作为参数传进来的呢!
-
注意:这里的事件类型
- 组件本质上只是一个JavaScript对象(虚拟DOM),该对象按
Vue
规定的成员属性构建,区别只在于Template
的写作模式。
-
- 这里应用了Slot,指代该组件嵌套的子节点。
- 这里使用了
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
中配置了。
显示效果
整体Layout布局
最终,我们要做一个顶天立地的内滚动结构(使用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
用于什么情况下呢,有什么好处?
思考
- 接下来要实现列表了呢,怎么做列表数据呢?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。