vue-cli 脚手架工具的使用
vue-cli 的依赖
vue-cli 的使用流程
node 的安装
node 是通过js 操作系统接口的语言; npm 是node的包管理工具;所以,安装了node 就可以直接使用npm 下载我们需要的包;
node 可以直接去node官网下载对应系统的安装包,安装提示完成;
然后在 命令行中 通过 npm -v 查看npm 版本,如果有出来代表安装成功;
npm 代理 到cnpm
你直接通过添加 npm 参数 alias 一个新命令:
alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"
然后运行
cnpm install xx -g //全局安装xx
使用 vue-cli
vue init webpack demoName //生成 webpack 为模板的vue项目
运行热更新页面
npm run dev
生成项目可以直接通过 http://localhost:8080/?#/ 访问
如果你是拉别人的项目运行的,要先运行下面的命令,安装依赖包
npm install
//或者
npm i
生成一个 静态项目文件(直接打开的静态页面)
npm run build
大多数情况 ,生成的页面是不可以直接打开的;提示只能部署到 服务器上才能正常访问;
如果需要直接本地打开;到项目目录下的config文件夹里的index.js文件中,将build对象下的assetsPublicPath中的“/”,改为“./”即可
生成的文件都是经过压缩的,带md5的文件
es6 的引入与输出
export default{} , module.exports = {}, exports { }
在node 的模块中,exports 是 module.exports 的引用;但是在vue 中 不能写成
exports = {
porps:['text']
}
// 提示 text 未定义;
exports default {} 定义的输出;可以通过 import xx 直接使用(标准ES6);写法是:
exports default {a:123}
import a;
module.exports = {} 与 exports default 是等价的
但是如果 同一个js 同时出现 module.exports ={} 和 import 就会报错
module.exports = {a:123} //定义和导出
import a; //引入
//上面两个关键词 不能同时出现在同一个js 中
报错原因如下:
webpack可以使用require和export ,但是不能混合使用import 和module.exports ,不然会报错Cannot
assign to read only property 'exports' of object '#<Object>'
ES6 统一的写法 是 import 和 exports default
如果你要import 组件 ;建议 统一使用 exports default {} 编写组件
vue-router 路由插件安装
安装
cnpm install vue-router
引入为全局插件
配置路由表
可以通过router-link 组件 链接到 对应组件;它会解析成类似a 标签
<router-link :to="{path:'forms'}">forms</router-link> //注意书写格式一定要正确
<router-link :to="{path:'father'}">father</router-link>
注意到 生成的url 中前面自动加了个 “#”;可以通过添加 mode:'history', 去掉
原理:https://router.vuejs.org/zh-c...
路由配置中,path 可以设置 url 带参;
带参path定义
表示 apple 后面跟的是参数,不是路径;
带参路径
获取参数
使用路由要注意的地方
- 所有的路由都是定义在 routes 生成的页面中的;不能在子组件中重新定义;例子如下:
var router = new VueRouter()
import browseMode from './components/browse-mode.vue'
import blogList from './components/blog-list.vue'
import blogArticle from './components/blog-article.vue'
import writePanel from './components/write-panel.vue'
router.map({
'/': {
component: browseMode,
subRoutes: {
'/': {
component: blogList
},
'/details/:artId': {
component: blogArticle
}
}
},
'/edit/:mode': {
component: writePanel
},
'/search/tag/:tag': {
component: browseMode,
subRoutes: {
'/': {
component: blogList
}
}
},
'/search/time/:time': {
component: browseMode,
subRoutes: {
'/': {
component: blogList
}
}
},
'/search/title/:title': {
component: browseMode,
subRoutes: {
'/': {
component: blogList
}
}
},
})
router.start(App, 'app')
- 路由不是最适合做tab切换的;最适合做tab的是 components 内置组件 ;通过is 控制切换;
<component v-bind:is="currentView">
var vm = new Vue({
el: '#example',
data: {
currentView: 'home' //改变这个值就会切换内容;
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
- 定义带参路由 的时候 注意格式是 path:'/box1/:text', 不是path:'/box1:text',(可以理解为/: 后面是参数 ,/后面是路径;)
- 定义好路由后, router-link 是可以放在任何地方的;它就是一个a标签;点击会切换第一个父级的router-view 中的页面;还要注意格式和传参
<li class="tab1"><router-link :to="{path:'box1/666'}">box1</router-link></li>
// to的 值是一个对象 ,key 固定是path ,value 是 一个字符串;
// 后面的666 是参数;它的前面不是 “ ?或者 & ” 而是 “ / ”;
- 获取参数的时候,直接去到参数的值是 this.$route.params.text (text是你定义路由时的 :text )
- 因为$route 是一个全局变量,你还可以直接在html 中使用它的url参数
<div>{{$route.params.text}}</div>
定义一个多参数 路由的 url
使用时的url可能是这样:
http://localhost:8080/#/apple/red/detail/fool
//后面的detail 是固定的。
获取的方法是一样的;
var a = $route.params;
//结果
a={
color:red,
type:fool
}
子路由的定义
{
path:'/box1',
component:box1,
children:[
{
// 当 /box1/box1a匹配成功,
// box1a会被渲染在 box1的 <router-view> 中
path:'box1a', //这里不加 “ / ”
component:box1a
},
{
path:'box1b',
component:box1b
}
]
},
// 要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
//在box1 中定义一行
<router-view/>
//子组件 box1a 会自动渲染 到页面中;
//如果添加两个 router-link 组件 就可以做tab切换(不建议用路由做tab)
//router-link 只会切换 第一个父级的 router-view 中的内容,所以不会刷新整个 box1 ;
<ul>
<li class="tab1"><router-link :to="{path:'/box1/box1a'}">box1a</router-link></li>
<li class="tab2"><router-link :to="{path:'/box1/box1b'}">box1b</router-link></li>
</ul>
//path 写成 /box1/box1a 是用了根路径(绝对路径)的形式;
//如果写成 {path:'box1a'} 则是相对路径;这样写点击 浏览器的url会写成 http://localhost:8080/box1a
//因为顶级路由中是没有定义 box1a 组件的;所以页面空白;
带参子路由
//路由定义
{
path:'/box1/:text',
component:box1,
children:[
{
path:'box1a',
component:box1a
},
{
path:'box1b',
component:box1b
}
]
},
//跳转到 box1 的写法
<li class="tab1"><router-link :to="{path:'box1/789'}">box1</router-link></li>
//box1 中渲染 box1a/b 的写法
<ul>
<li class="tab1"><router-link :to="{path:url1}">box1a</router-link></li>
<li class="tab2"><router-link :to="{path:url2}">box1b</router-link></li>
</ul>
//初始化的时候生成url
mounted: function () {
this.parms = this.$route.params.text;
this.url1 = '/box1/'+this.parms+'/box1a'
this.url2 = '/box1/'+this.parms+'/box1b'
},
- router-link 中的to 属性,如果只是简单的跳转可以直接写成 to=“box1”;
- 如果加上:绑定属性;后面的属性可以是简短的 表达式,因此上面的例子可以简化为:
//路由定义
{
path:'/box1/:text',
component:box1,
children:[
{
path:'box1a',
component:box1a
},
{
path:'box1b',
component:box1b
}
]
},
//跳转到 box1 的写法
<li class="tab1"><router-link :to="{path:'box1/789'}">box1</router-link></li>
//box1 中渲染 box1a/ b 的写法
//to 属性会自动 运算出来;
<ul>
<li class="tab1"><router-link :to="/box1/'+parms+'/box1a'">box1a</router-link></li>
<li class="tab2"><router-link :to="'/box1/'+parms+'/box1b'">box1b</router-link></li>
</ul>
//初始化的时候生成url
mounted: function () {
this.parms = this.$route.params.text;
},
具名路由
router-link 还可以通过 name 指定跳转(具名路由);通过例如 tag="li" 指定包裹标签为a以为的tag;
children:[
{
path:'box1a',
name:"nameBox",
component:box1a
}
]
<router-link :to="{name:'nameBox'}" tag="li">nameBox</router-link>
//结果:会生成li, 点击router-view 会渲染出 box1a ;注意,path是不可缺少的
用具名路由布局(代替iframe的方式)
export default new Router({
mode:'history',
routes: [
{
path:'/index',
components:{ //注意这里是有 “ s ” 的
default:vuexIndex, //对应 <router-view/>
left:vuexLeft, // 对应<router-view name="left"></router-view>
right:vuexRight //对应<router-view name="right"></router-view>
}
}
]
})
<template>
<div id="app">
<router-view/>
<div class="content">
<router-view name="left"></router-view>
<router-view name="right"></router-view>
</div>
</div>
</template>
//注意访问的路径 是在components 中定义的path ;
在某个页面中定义具名路由
{
path:'/layout',
name:'layout',
component:layout,
children: [
{
path: '/',
components:{
default:main,
leftView:leftView,
topBar:topBar
},
}
]
}
路由的重定向
routes: [
{ path:'/', redirect:'/box' //路径不存在就跳转到box
},
{ path: '/a', redirect: '/b' } //访问a的路径,都跳转到b
]
vuex 状态管理插件安装
当vue项目中 组件过多并设计到功能某些数据的时候,管理数据变得复杂,VUEX 就是通过自身的一套数据管理流程帮助你管理 项目数据(状态);
安装
cnpm install vuex --save
实例化
//在main.js 中
import Vuex from 'vuex'
Vue.use(Vuex);
let store = new Vuex.Store({ //Store 是vuex 方法
state:{ //存放变量的地方
total:0,
appleTotal:0,
bananaTotal:0,
},
mutations:{ //定义同步变量的方法
aSetValue(state,value){
state.appleTotal = value
state.total = state.appleTotal + state.bananaTotal;
},
bSetValue(state,value){
state.bananaTotal = value
state.total = state.appleTotal + state.bananaTotal;
}
}
})
new Vue({
el: '#app',
store, //在实例中 引入
router,
components: { App },
template: '<App/>'
})
获取值
vuex 的变量store 全局以后可以通过以下语句获取
this.$Store.state.total
//组件中
<h3>总价:<span>{{$store.state.total}}</span></h3>
还可以通过设置getter函数 暴露对应的值
getters:{
getTotal(state){
return state.total
}
},
//组件中
<h3>总价:<span>{{$store.getters.getTotal}}</span></h3>
改变vuex变量的值
通过在组件中调用以下特定语句改变
methods:{
add(){
this.cnt += 1;
//bSetValue 是在vuex实例的mutations中定义的方法
//通过 bSetValue方法 去改变实例的值
this.$store.commit('bSetValue',this.cnt*5)
},
minus(){
this.cnt-=1;
this.$store.commit('bSetValue',this.cnt*5)
}
},
// 在vuex 中只有 commit 方法可以改变数据;这里是不建议直接在组件中调用 cmmit 的
// 因为cmmit中只能是同步操作;但是交互往往需要同步数据库
// 更加好的方法是在 actions的方法 中触发 commit;actions 方法支持异步操作;
// 所有涉及 后端API 的接口都是放在actions 中进行
actions 属性
mutations:{
aSetValue(state,value){
state.appleTotal = value
state.total = state.appleTotal + state.bananaTotal;
},
bSetValue(state,value){
state.bananaTotal = value
state.total = state.appleTotal + state.bananaTotal;
}
},
actions:{
// 定义doAfn 方法,间接触发 aSetValue;
// 在doAfn 方法是可以用ajax 的
doAfn(context,price){
context.commit('aSetValue',price)
},
doBfn(context,price){
context.commit('bSetValue',price)
}
}
组件中的触发方式 通过 dispatch 触发doAfn;
this.$store.dispatch('doAfn',val*10)
vuex 的 modules
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
官方文档代码
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
官方的文档组织建议:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
https://vuex.vuejs.org/zh-cn/...
一个 vuex 小例子
父组件
<template>
<div class="left">
<h1>我是left</h1>
<div class="tips">(我是{{text?text:'路由的方式'}}来的)</div>
<div class="left-content">
<div class="item-box">
<apple/>
<banana/>
</div>
<div class="price">
<h3>总价:<span>{{$store.getters.getTotal}}</span></h3>
</div>
</div>
</div>
</template>
<script>
import apple from '@/components/vuex/apple.vue'
import banana from '@/components/vuex/banana.vue'
export default {
data: function () {
return {
}
},
components:{
apple,
banana
},
props:['text']
}
</script>
<style scoped>
h1{
text-align: center;
}
.left-content{
margin-top: 20px;
}
.item-box{
width: 48%;
display: inline-block;
}
.price{
width: 50%;
display: inline-block;
vertical-align: bottom;
}
.item{
font-size: 15px;
margin-top: 20px;
margin-left: 20px;
}
</style>
apple 组件
<template>
<div class="item">
<span>苹果</span>
<button @click="add">add</button>
<button @click="minus">minus</button>
<input v-model.number.lazy="cnt">
<span v-if="noEnough" class="red">库存不够</span>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
cnt:0,
noEnough:false
}
},
methods:{
add(){
this.cnt += 1;
},
minus(){
this.cnt-=1;
}
},
watch:{
cnt(val){
if(val>10){
this.noEnough = true;
}
if(val<0){
this.cnt=0
}
if(val<10){
this.noEnough = false;
}
this.$store.dispatch('doAfn',val*10)
}
}
}
</script>
<style scoped>
.item input{
width: 40px;
display: inline-block;
}
.red{
color:red;
font-size: 12px;
}
</style>
banana 组件
<template>
<div class="item">
<span>香蕉</span>
<button @click="add">add</button>
<button @click="minus">minus</button>
<input v-model.number.lazy="cnt">
<span v-if="noEnough" class="red">库存不够</span>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
cnt:0,
noEnough:false
}
},
methods:{
add(){
this.cnt += 1;
},
minus(){
this.cnt-=1;
}
},
watch:{
cnt(val){
if(val>10){
this.noEnough = true;
}
if(val<10){
this.noEnough = false;
}
if(val<0){
this.cnt=0
}
this.$store.dispatch('doBfn',val*5)
}
}
}
</script>
<style scoped>
.item input{
width: 40px;
display: inline-block;
}
.red{
color:red;
font-size: 12px;
}
</style>
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import Vuex from 'vuex'
import App from './App'
import router from './router'
Vue.config.productionTip = false
Vue.use(Vuex);
let store = new Vuex.Store({
state:{
total:0,
appleTotal:0,
bananaTotal:0,
},
getters:{
getTotal(state){
return state.total
}
},
mutations:{
aSetValue(state,value){
state.appleTotal = value
state.total = state.appleTotal + state.bananaTotal;
},
bSetValue(state,value){
state.bananaTotal = value
state.total = state.appleTotal + state.bananaTotal;
}
},
actions:{
doAfn(context,price){
context.commit('aSetValue',price)
},
doBfn(context,price){
context.commit('bSetValue',price)
}
}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
router,
components: { App },
template: '<App/>'
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。