视图
包含内容#NavigationBar、#TabBar、#MainContext;
为什么#NavigationBar、#TabBar分在Layout中,而不是components中?
代码上实际上是没有差别的,只是认为#NavigationBar、#TabBar是加载一次的,而非复用,且属于页面布局内容。
App.vue
Vue实例化的根组件,我们在这里进行布局:
src/App.vue文件:
import TabBar from "@/views/layout/TabBar";
import NavigationBar from "@/views/layout/NavigationBar";
export default {
name: 'App',
components: {
'navigation-bar': NavigationBar,
'tab-bar':TabBar,
}
}
...//未动原有样式;
在这里,我们使用标识 其内部的HTML为Vue Template。
内部必有一个且唯一的节点(这里是div#app)包裹内容(即使只是一串字符)-->若存在同级节点,则会报错(这是因为VNode会通过createElement('div')来创建真实节点,只能是单个元素);
通过components属性以键值对的形式引入组件,模板(HTML)中使用的标签名为键名(自定义元素VNode),值为导入的组件模块;
通过components定义组件使用的方式,限制了组件应用的范围。即:如果你在其它文件直接使用,控制台会报错:组件未注册-->这就是组件的局部注册。
局部注册的组件要求:如果在某一文件中应用该组件,必须要使用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文件:
NavigationBar
header{
border-bottom: 4px solid #821910;
}
在这里,我们通过style[scoped]定义一份样式,其作用范围仅限于当前文件(又可称模块)模板中的元素。
像下边,在TabBar.vue中的header元素就没有使用到该文件中的对应样式,这就是局部作用域的样式。
局部作用域的样式只对当前文件中的元素起作用,想改变body的样式,不好意思,请全局导入或不使用局部作用域。
src/views/layout/TabBar.vue文件:
测试是否和NavigationBar一样的效果
TabBar
footer{
border-bottom: 4px solid #07776e;
}
显示效果:
NavigationBar
#NavigationBar中分左右结构,左边按钮后退,右边按钮更新页面。
更新页面只是更新数据,而不是整个页面的刷新,每个页面更新数据的接口不同,所以,要作为组件属性传入。
在src/views/layout/navigationBar.vue中:
关闭
{{title}}
刷新
该部分为单文件组件#NavigationBar的Template部分。
@click是v-on:click的简写,用于绑定点击事件。
v-if是Vue中的条件指令,根据返回的布尔值动态添加或移除DOM元素。
/**
* @title:头部标题;
* @refresh:刷新处理函数;
*/
export default {
props: ['title', 'refresh'],
computed: {
hasTitle() {
return this.title && this.title.trim();
},
},
methods: {
goBack() {
this.$router.back()
},
onRefresh() {
this.refresh();
},
},
}
该部分为单文件组件#NavigationBar的组件配置对象。
props为父级(调用该组件的组件)传过来的属性。
传值方式(需要在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的模板:
v-for="tab in tabBars"是Vue中的循环结构,搭配:key使用,优化Vue的渲染机制;
对tabBars进行遍历,tab为数组中的元素。
同样key值,在更新时,会复用组件,而不是销毁后,再创建一个新的组件。
这是是一个新组件的引用。
$parent是组件实例#TabBar的父实例(#App)。
src/views/layout/TabBar.vue组件配置对象:
const tabButton = {
render(createElement) {
return createElement(
'button',
{
"class": this.className,
on: {
[this.event]: this.tabClick,
},
},
this.$slots.default, //指代{{tab.label}}
)
},
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;
},
},
}
这里使用了另一种方式定义组件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样式中:
@import "@/styles/mixins.scss";
#app {
@include flex($direction: column);
}
.main-context {
flex-grow: 1;
overflow: hidden;
overflow-y: auto;
}
其中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用于什么情况下呢,有什么好处?
思考
接下来要实现列表了呢,怎么做列表数据呢?
番外