一步一步学Vue(十二)

时间:2023-03-08 15:34:45

为了提升代码的逼格,之后代码改为Vue文件组件,之前代码虽然读起来容易理解,而且适合在小的项目中使用,但是有如下缺点:

  • 全局定义(Global definitions) 强制要求每个 component 中的命名不得重复
  • 字符串模板(String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \
  • 不支持CSS(No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
  • 没有构建步骤(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug (formerly Jade) 和 Babel

文件扩展名为 .vue 的 single-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用 Webpack 或 Browserify 等构建工具。

所以基于这种考虑,我们以后会使用单文件模式去编写代码,并且为了看上去更洋气一些,我们的代码会以IView作为基础组件,不熟悉的同学正好可以简单了解一些,本节主要基于单文件组件重构上节的代码。

vue官方提供了很好的命令行工具,vue-cli,可通过npm直接安装 npm install -g vue-cli;对此我不做过多介绍,google到的内容你看都看不过来。

我们基于webpack-simple 脚手架搭建我们的项目,运行 vue init webpack-simple demo,接着一步一步走就ok了,然后进入demo 文件夹,执行npm install 安装依赖即可,安装完毕后执行npm run dev 即可启动程序:

一步一步学Vue(十二)

看到上述结果表示已经运行成功,从package.json的script节可以看到,开发模式下启动了热加载模式,无需手动刷新浏览器即可完成代码重载。

既然使用IView,那么我们先安装IView ,npm install --save iview; 并在webpack 入口页面引入并启用

一步一步学Vue(十二)

修改我们的webpack.config.js,保证支持css引入以及字体文件导入(npm install --save-dev css-loader style-loader url-loader):

var path = require('path')
var webpack = require('webpack') module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader','css-loader' ]
},
{
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: 'url-loader?limit=1024'
},
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
} if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}

此时我们的iview已经可用了,我们这里引入了iview全部组件,如果按需引入,则需要对每一个组件进行分别引入。首先搭建我们布局页(直接简单修改iview layout代码):

<template>
<div class="layout" :class="{'layout-hide-text': spanLeft < 5}">
<Row type="flex">
<i-col :span="spanLeft" class="layout-menu-left">
<Menu active-name="1" theme="dark" width="auto">
<div class="layout-logo-left">
Demo Project
</div>
<Menu-item name="1">
<Icon type="ios-navigate" :size="iconSize"></Icon>
<span class="layout-text">TODOList</span>
</Menu-item> </Menu>
</i-col>
<i-col :span="spanRight">
<div class="layout-header">
<i-button type="text" @click="toggleClick">
<Icon type="navicon" size="32"></Icon>
</i-button>
</div> <div class="layout-content">
<div class="layout-content-main">内容区域</div>
</div>
<div class="layout-copy">
2011-2016 &copy; demo
</div>
</i-col>
</Row>
</div>
</template>
<script>
export default {
data () {
return {
spanLeft: 5,
spanRight: 19
}
},
computed: {
iconSize () {
return this.spanLeft === 5 ? 14 : 24;
}
},
methods: {
toggleClick () {
if (this.spanLeft === 5) {
this.spanLeft = 2;
this.spanRight = 22;
} else {
this.spanLeft = 5;
this.spanRight = 19;
}
}
}
}
</script> <style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-breadcrumb{
padding: 10px 15px 0;
}
.layout-content{
min-height: 200px;
margin: 15px;
overflow: hidden;
background: #fff;
border-radius: 4px;
}
.layout-content-main{
padding: 10px;
min-height:768px;
}
.layout-copy{
text-align: center;
padding: 10px 0 20px;
color: #9ea7b4;
}
.layout-menu-left{
background: #464c5b;
}
.layout-header{
height: 60px;
background: #fff;
box-shadow: 0 1px 1px rgba(0,0,0,.1);
}
.layout-logo-left{
width: 90%;
height: 30px;
background: #5b6270;
border-radius: 3px;
margin: 15px auto;
text-align:center;
line-height:30px;
color:#fff;
}
.layout-ceiling-main a{
color: #9ba7b5;
}
.layout-hide-text .layout-text{
display: none;
}
.ivu-col{
transition: width .2s ease-in-out;
}
</style>

运行npm run dev:可看到如下效果:

一步一步学Vue(十二)

接下来引入我们的vuex,使用npm install --save vuex ,并对main.js做如下修改:

import Vue from 'vue'
import IView from 'iview';
import Vuex from 'vuex';
import App from './App.vue'
import 'iview/dist/styles/iview.css'; import store from './store'; Vue.use(IView);
Vue.use(Vuex); new Vue({
el: '#app',
store,
render: h => h(App)
})

创建store.js,并添加如下代码(代码来源于上一篇博文中代码):

var list=[];

export default {
state: {
items: [], // todoContainer中items,
//初始化表单所用
initItem: {
title: '',
desc: '',
id: ''
}
}, mutations: {
search (state, payload) {
state.items = list.filter(v => v.title.indexOf(payload.title) !== -1);
},
save (state, payload) {
if (state.initItem.id) {
var o = list.filter(v => v.id === payload.id);
o.title = payload.title;
o.desc = payload.desc;
state.items = state.items.map(v => {
if (v.id == payload.id) {
return payload;
}
return v;
}); } else {
var id=state.items.length+1;
state.items.push({id:id,title:payload.title, desc:payload.desc});
} list = state.items;
},
remove (state, payload) {
state.items = state.items.filter(v => v.id !== payload.id);
},
edit (state, payload) {
state.initItem = state.items.filter(v => v.id === payload.id)[0];
}
}
};

创建components文件夹,并按照单文件组件的规范创建组件:

SearchBar.vue

<template>
<div class="row toolbar">
keyword:
<Input type="text" v-model="keyword" ></Input>
<Button type="primary" @click="search()">search</Button>
</div>
</template> <script>
export default {
data: function () {
return {
keyword: ''
}
},
methods: {
search() {
this.$store.commit("search", {
title: this.keyword
});
}
}
}
</script>

TodoList.vue:

<template>
<Table border :columns="columns" :data="items"></Table>
</template>
<script>
export default{
data(){
return {
columns:[
{
title:'Id',
key:'id'
},
{
title:'title',
key:'title',
},
{
title:'desc',
key:'desc'
},
{
title:'actions',
//TODO:操作 }
]
}
},
props:[
'items'
],
methods:{
edit: function () {
this.$store.commit('edit',this.todo);
},
remove: function () {
this.$store.commit('remove',{id:this.todo.id});
}
}
}
</script>

TodoForm.vue:

<template>
<div class="col-md-6">
<div>
<label for="title">title:</label>
<input type="hidden" v-bind:value="todo.id" />
<Input v-model="todo.title" ></Input>
</div>
<div>
<label for="desc">desc</label>
<Input v-model="todo.desc" ></Input>
</div>
<div>
<Button type="primary" v-on:click="ok()">Ok</Button>
</div>
</div>
</template>
<script>
export default{
props: ['initItem'], computed: {
todo: function () {
return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc };
}
}, methods: {
ok: function () {
this.$store.commit("save",this.todo);
}
}
}
</script>

修改app.vue 完成组件注册和初始化:

<template>
<div class="layout" :class="{'layout-hide-text': spanLeft < 5}">
<Row type="flex">
<i-col :span="spanLeft" class="layout-menu-left">
<Menu active-name="1" theme="dark" width="auto">
<div class="layout-logo-left">
Demo Project
</div>
<Menu-item name="1">
<Icon type="ios-navigate" :size="iconSize"></Icon>
<span class="layout-text">TODOList</span>
</Menu-item> </Menu>
</i-col>
<i-col :span="spanRight">
<div class="layout-header">
<i-button type="text" @click="toggleClick">
<Icon type="navicon" size="32"></Icon>
</i-button>
</div> <div class="layout-content">
<div class="layout-content-main">
<search-bar></search-bar>
<todo-list :items="items"></todo-list>
<todo-form :init-item="initItem"></todo-form>
</div>
</div>
<div class="layout-copy">
2011-2016 &copy; demo
</div>
</i-col>
</Row>
</div>
</template>
<script>
import SearchBar from './components/SearchBar.vue';
import TodoForm from './components/TodoForm.vue';
import TodoList from './components/TodoList.vue'; export default {
data () {
return {
spanLeft: 5,
spanRight: 19
}
},
components:{
'search-bar':SearchBar,
'todo-form':TodoForm,
'todo-list':TodoList
},
computed: {
iconSize () {
return this.spanLeft === 5 ? 14 : 24;
},
initItem: function () {
return this.$store.state.initItem;
},
items: function () {
return this.$store.state.items;
}
},
methods: {
toggleClick () {
if (this.spanLeft === 5) {
this.spanLeft = 2;
this.spanRight = 22;
} else {
this.spanLeft = 5;
this.spanRight = 19;
}
}
}
}
</script> <style scoped>
.....
</style>

此时保存,直接在浏览器可以看到如下效果:

一步一步学Vue(十二)

今天时间不充足,重构就到这里,第一次使用单文件组件还是手生,代码调试比较费时间,一步一步的来吧。下一篇继续改造,里面包含了很多bug,大家可以试着修复或者完善一下。

good night。