后端小白的VUE入门笔记, 进阶篇
使用 vue-cli(脚手架) 搭建项目#
基于vue-cli 创建一个模板项目
通过 npm root -g 可以查看vue全局安装目录,进而知道自己有没有安装vue-cli
如果没有安装的话,使用如下命令全局安装
cnpm install -g vue-cli
创建一个基于webpack的新项目,在这过程中, 会安装依赖
vue init webpack 项目名
启动
cd vue-router-demo
npm start
常用的目录结构#
如果我们的项目是通过脚手架搭建的, 这已经是一个比较完善的种子项目了
|-- build : webpack 相关的配置文件夹(基本不需要修改)
|-- config: webpack 相关的配置文件夹(基本不需要修改)
|-- index.js: 指定的后台服务的端口号和静态资源文件夹
|-- node_modules: 在上面安装的依赖,都存放在这个文件夹下
|-- src : 源码文件夹,我们后续开发的组件和js分门别类的放在这里面
|-- main.js: 应用入口 js
|-- static: 静态资源文件夹
|-- .babelrc: babel 的配置文件
|-- .editorconfig: 通过编辑器的编码/格式进行一定的配置
|-- .eslintignore: eslint 检查忽略的配置
|-- .eslintrc.js: eslint 检查的配置
|-- .gitignore: git 版本管制忽略的配置
|-- index.html: 主页面文件
|-- package.json: 他就相当于maven的pom.xml, 里面存放着相关的依赖信息和项目的版本信息
|-- README.md: 应用描述说明的 readme 文件
配置 config/index.js#
可以在 config/index.js 中做一下的常用配置
- 添加跨域的配置
- 配置项目的主机名, 端口号
- 配置是否打开浏览器
- 代码检查工具 eslint
在开发的时候我们主要还是关注 src 文件, 后来需要的路由,store,ajaxApi, 以及其他组件全部在创建在这个文件夹下
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {}, // 添加跨域的配置
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 9528, // 配置是否打开浏览器
autoOpenBrowser: true, // 配置是否打开浏览器
errorOverlay: true,
notifyOnErrors: false,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: false,
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false,
入口 js 文件 main.js 的主要作用#
- 创建 vue 实例, 关联 index.html 中 id 为 app 的 div 代码块
- 添加项目中的路由模块 router
- 添加 store 模块 ---vuex
一般做好这不配置之后 main 就不用再改动了
import App from './App'
import router from './router'
import store from './store'
Vue.use(ElementUI, { locale })
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
根组件 App.vue#
其实每一个组件都可以完整的拥有下面三部分, 当然如果哪个组件中不需要添加 css 样式, 可以把最后一个 style 或者 script 标签去掉
<template>
<div>
<!-- 这里存放 -->
</div>
</template>
<//script>
export default {
name: 'app'
}
<///script>
<style>
</style>
组件间的相互调用#
比如根组件想使用 hello.vue 组件, 怎么做呢?
像下面这样, 三步走
- 第一步: 引入组件
- 第二步: 将组件映射成 target 标签
- 第三步使用标签
<template>
<div>
<!--第三步使用标签-->
<hello/>
<div/>
<template/>
<script>
import hello form './XXX/hello.vue'
export default{
// 将组件映射成标签
components:{
hello
}
}
<style>
</style>
第二步中引入标签时也可以去掉.vue 后缀
或者直接这样写, 是从 @/ 代表的是 src/
import hello form '@/XXX/hello'
打包与发布#
打包#
- 打包的命令:
npm run build
项目经过打包, 产出是一个 dist 文件, 里面分别是 index.html 和 静态资源文件夹, 这也是前后端分离开发的特色, 后端想控制 view 层, 也难了, 只有一张 index.html
发布方法 1- 静态服务器工具包#
命令:
npm install -g serve // 安装工具
serve dist
发布方法 2- 使用 tomcat 服务器#
注意点, 使用 tomcat 当服务器,要求文件夹的名字和项目的名字要一样, 修改的步骤如下:
- 修改
/build/webpack_prod.conf.js
文件
output:{
...
pathPath:'项目名称'
}
- 编译重新打包
npm run build
- 把打包得到的 dist 文件夹改名, 改成项目名
- 将改名完事后的文件拷贝到 tomcat 的 webapps 目录下, 运行 tomcat
eslint 的编码规范检查#
好的习惯就是使用它, 规范自己的代码风格, 但是也得说一下怎么禁用 eslint'
- 方法一: 通过如果是 webstorm 编译器的话, 点击 file/settings/ , 搜索 eslint, 可以去掉 enable
- 方法二: 编辑.eslintignore 文件, 添加自己想被忽略的文件
*.js
*.vue
一般我们就写这两部分,这一下子全忽略了
因为 eslint 有个莫名其妙的要求, 代码最后一行要求是空行, 可以通过下面的方法三取消掉
- 方法三: 编辑.eslintrc.js
rules:{
...
// 添加
'indent':0
}
父子组件之间数据交互#
在差分组件的时候, 本着多个组件共享的数据放在根组件的原则, 于是我们把共用的数据放在根组件, 于此同时操作这些数据的方法也被我们定义在根组件, 子组件想要使用这些数据, 想要操作这些数组怎么办呢? 像下面那样, 进行组件之间的数据传递
- 在父组件中给子组件传递方法或数据
使用: 强制数据绑定的方法, ChildTarget 是我们在 components 模块将子组件映射得来的子组件标签, name 可以是 vue 中 data 的方法, 也可以是属性
<template>
<ChildTarget :name="name"/>
</template>
- 子组件取出父组件传递过来的值
export default{
props:['name','name2']
}
数据的交互@click
#
最常用的就是使用@click="方法名"
, 或者@click="value = !value"
或者@click="value = true
如果我们向上面那样, 把公共的数据放在父组件中, 那么事件的触发一定是发生在子组件中, 子组件一般通过@click
给模板中的元素绑定上指定的动作,进而调用父组件的传递进来的方法, 操作父组件传递进来的值
此外, 在所有的组件中,vue 的 data 部分都向下面这样写,是规定
data(){
return{
name:''
}
}
- 常用的监视 watch 模块
watch:{
监视的data中的对象
name:{
deep:true, // 深度监视
handler: function(value){ // value 就是变化后的新的值
// todo
}
}
}
- 缓存模块
从缓存中把去出来的字符串转换成 json 串
JSON.parse(window.localStorage.getItem('')||'默认值');
把对象, 存储进浏览器的缓存
window.localStorage.setItem('自定义的 key',JSON.stringfy(value))
消息订阅, 打破父子组件信息传递的约束#
像上面那样, 如果不存在父子组件的关系, 父组件不引入子组件, 也就没办法把他映射成标签, 既然映射不成标签也就没法像上面那样, 通过 : 冒号 强制进行数据的绑定达到传递值的效果, 于是有了消息订阅
组件之间的通信方式: 发布 / 订阅
绑定监听: 订阅事件
触发事件: 发布事件
借助插件 -pubsub.js
安装命令:
npm install --save pubsub-js
场景: 我们给模板上的按钮绑定点击事件, 一旦被点解他就发布事件
- 在使用前需要导入 PubSub 对象
import PubSub from 'pubsub-js'
使用: 消息的发布
<button @click="search">Search</botton>
export default{
methods:{
search(name){
// search 是方法名
// name 是携带的参数, 没参数就不用写
Publish.publish('search',name)
}
}
}
消息的订阅:
- 依然是第一步: 引入 PubSub 对象
- 编码实现:
mounted: {
PubSub.subscribe("search",(name)=>{
// todo with name
});
异步请求#
安装插件 axios
npm install axios --save
- 在使用之间同样是需要引入:
import axios from 'axios'
发送一个 get 请求
axios.get(url)
.then(res=>{
// todo with res
})
.catch(error){
// todo
}
路由:#
vue 是如何做到使后端乖乖交出 view 层的控制权的?, 难道是直接使用 window.location.href = url 吗?
其实学过路由才知道, 使用的是 vue-router, 一个官方提供的路由框架, 可以使用我通过组合组件来组成应用程序, 仰仗它的路由插件 vue-router, 我们轻松控制页面的切换
我们要做的就是将组件 components 映射到 routers, 然后告诉 vue-router 到哪里去渲染他们
定义路由器#
安装插件
npm install vue-router --save
编码, 其实大家都会把关于路由的编码单独放到一个叫 router 的文件夹中, 而且, 它的编码基本上是机械化的编码, 分为如下几步
- 引入 Vue,VueRouter
- 声明 Vue.use(VueRouter)
- 引入路由组件
- 对外暴露路由器对象, 并且把路由组件配置进路由器对象
注意点 下面的配置部分, routes 不写乱写!!!
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Home from './Home.vue'
import About from './About.vue'
import Me from './Me.vue'
export default new VueRouter({
// 添加路由
routes:[
{
path:'/home',
component:Home,
meta:{
// 添加自定义的字段, 可以当成 flag, 也可以文本
}
},
{
path:'/about',
component:About,
meta:{
// 添加自定义的字段, 可以当成 flag, 也可以文本
},
childred:[ // 嵌套路由
{
path:'/about',
component:About,
meta:{
// 添加自定义的字段, 可以当成 flag, 也可以文本
}
}
]
}
},
{
path:'', // 默认访问空的话, 重定向到 /home
redirect:'/home'
}
]
})
使用路由进行页面的跳转#
原来进行页面的跳转我们通常使用 a 标签,(一般把 a 标签设计成按钮, 或者导航栏的样子, 点击发送请求, 进而跳转页面了), 而 vue 的路由其实和 a 标签差不多, 我们使用 vue 的 router-link 标签替换 a 标签
<router-link to:'/about' class="可以让我看起来像按钮的 css 样式"> </router-link>
<router-link to:'/home' class="可以让我看起来像按钮的 css 样式"> </router-link>
<router-view ></router-view>
这样用户点击 router-link, 就会把相应的子组件移植到标签块中
补充:
属性 | 类型 | 含义 |
---|---|---|
to | string | Location | 表示目标路由的链接。当被点击后,内部会立刻把 to 的值传到 router.push() ,所以这个值可以是一个字符串或者是描述目标位置的对象。 |
replace | boolean | 设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push() ,于是导航后不会留下 history 记录。 |
append | boolean | 设置 append 属性后,则在当前(相对)路径前添加基路径。例如,我们从 /a 导航到一个相对路径 b ,如果没有配置 append ,则路径为 /b ,如果配了,则为 /a/b |
回退到上一个路由#
我们可以在按钮上添加下面的动作, 是路由回退一级
<button @click="$router.back()"></button>
缓存路由组件#
使用如下标签包裹我们的 router-view, 这样当我们再回退到上一个路由时, 用户加进去的状态依然存在
<keep-alive>
<router-view ></router-view>
</keep-alive>
$router 与 $route#
$router 是路由器对象, 说白了就是用它去跳转页面, 美其名曰: 编程式路由导航
$route 是路由对象, 说白了就是某一个路由对象, 既然是某一个, 就不能进行页面的跳转, 相反是可以获取出当前路由组件的属性, 它的结构图如下:
$route 的组成图
向路由组件传递值 一#
需求: 我们想发送这样的请求 http:localhost:8080/home/1/ 羊肉串, 在路径上携带着参数 1
路由怎么接收参数呢?--> 使用: 占位
export default new VueRouter({
// 添加路由
routes:[
{
path:'/home/:id/:type', // 如果想在路径上传递值进来, 就使用: 占位
component:Home,
meta:{
// 添加自定义的字段, 可以当成 flag, 也可以文本
flag:true
}
},
当我们添加了 /: 之后, 它的组成结构就变成了这个样子
像下面这样传递值进去, 发起请求
<router-link to:`/home/${id}/${type}` class="可以让我看起来像按钮的 css 样式"> </router-link>
同时, 我们也可以向下面这样使用 $route. 在对应不同的路由组件中, 把里面的属性取出来, 注意啊, 这样取值, 前提是我们前面使用 /:id 占位, 并且也整整传递值进去了
<h1>id= {{$route.params.id}}</h1>
向路由组件传递值 二#
使用<router-view >
标签传递值
<router-view msg='abc'></router-view>
在路由组件中通过 props 取出值, 然后可以直接使用
export default{
props:[
msg:String
]
}
编程式的路由导航#
编程式的路由导航说白了就是, 不用 router-link 标签转而使用代码路由的跳转呗, 举个例子, 我们使用手机 qq, 最下面有几个导航栏, 点击不同的按钮转换到不同的页面去, 如果用编程式的路由导航就很好做
- 第一步就是将需要的路由组件配置进路由器
- 给按钮绑定上点击事件
- 点击事件触发我们所谓的编程式路由导航
vue 提供了两种编程式的路由导航实现
- 第一种:
这种常用的一种
this.$router.replace(`/home/${id}`)
- 第二种:
这种具有栈的特性, 也就是说, 用户点击返回键, 会返回到上一级路由
this.$router.push(`/home/${id}`)
slot 标签#
它是个和 rout-view 和像的标签, 都是用来占位的, 它可以接受父组件传递给他的一段 html
举个例子: 有四张路由组件, 他们共用一个叫 header 的组件当作自己的头部, 但是他们需要传递进去属于自己的不同的值, 下面使用 slot 实现
在 MyHeader.vue 中
<!--首页头部-->
<header class="header">
<!-- 这里使用插槽占位 -->
<slot name="left"></slot>
<span">
<span >我是 header</span>
</span>
<!-- 这里使用插槽占位 -->
<slot name="right"></slot>
</header>
在父组件中使用:注意啊,下面的组件想往 MyHeader.vue 中的插槽中,传递进去代码片段,前提是他要把 MyHeader.vue 映射成标签,成为他的父组件
<div>
<MyHeader>
<span class="header_search" slot="left">
<i class="iconfont icon-sousuo"></i> /* 在插槽的左边植入一个 icon*/
</span>
<!-- 给右边的插槽传递模板 -->
<span class="header_login" slot="right">
<a href="" >登录 | 注册</a> /* 在插槽的右边植入一个链接 */
</span>
</MyHeader>
</div>
Vuex#
官方的解释: vuex 是专门为 Vue.js 应用程序开发的状态管理模式, 它采用集中式的储存应用中所有组件的状态, 并以相应的规则保证状态以一种可预期的方式发生变化
说白了: 当我们划分组件之后, 每一个组件都有自己的属性, 但是不同的组件的数据是不能共享的, 于是我们可以使用从父组件往子组件传播数据的模式, 而且完全不相干的两个组件可能需要对方 data 里的数据, 又怎么传递呢? vuex 就应对 迎战这个问题
vuex 就是一个单独存储的区域, 用于存放公共的属性
安装命令:#
npm install --save vuex
创建 vuex 的四个组件对象, 如上图
vuex 的组件对象一: state.js#
状态对象, 存放一系列的状态, 其实就是把子组件中 data 里面的字段赋复制过来
state.js文件
export default {
arr: []
}
vuex 的组件对象二: actions.js#
超级重要的组件, 在这个组件中我们可以提交异步事件, 最常用的就是用户直接通过 $store.dispatch('action 中的方法名'), action 会触发 mutation 的调用, 间接更新状态
action.js
// add 方法的方法第一个参数是不变的 {commit}, 其实他就是 $store 对象
// 通过这个 commit 方法, 把数据包装成对象传递给 mutations
// 第二个参数的可选的, 可以是调用者传递进来的参数, 也可以是 state 对象
export default {
add({commit},item){
// 提交 mutation 请求
commit(ADD_TODO,{item}); // 把数据包装成对象传递给 mutations
},
vuex 的组件对象三: mutations.js#
真正的去执行 action 传进来, 更新 state 中数据的操作
mutations.js
export default {
add(state,{item}){
state.arr.unshift(item);
}
}
vuex 的组件对象四: getters.js#
包含了所有的基于 state 的 get 计算属性, 这一点也很好用, 他是一种双向的数据绑定
getters.js
export default {
// 计算属性
totalCount (state) {
return state.arr.length
},
}
把四个组件拼装成 store 对象#
- 在 src 下创建 store 文件夹, 在改文件夹下创建 store.js
- 导入 Vue , Vuex
- 声明 Vue 使用 Vuex
- 将上面的四个组件注册进来 store.js
state: 状态对象, 存放的是需要共享数据的字段
actions: 包含多个事件回调函数的对象
mutations: 包含真正去更新 state 中字段的函数
getter: 计算属性的方法 - 对外暴露匿名 store 对象
- 将 store 配置进 main.js vue 的入口 js 中
编码实现: store.js
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import actions from './actions'
import mutations from './mutations'
import getters from './getter2'
Vue.use(Vuex)
// 对外暴露你匿名 store 对象
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
把store对象,注册进main.js
更全面的数据处理流程图#
获取 state 中的值#
做好了上面的配置, 在任何地方都能用下面的方式获取出 store 里面的数据
this.$store.state.属性
使用 vuex, 改变状态值#
添加上前缀, 再使用
this.$store.commit('matations 中的方法名','可选的参数')
// 注意哦, action 中是可以提交异步函数的
this.$store.dispach('action 中的方法名','可选的参数')
也可以像下面这样, 先进行映射就可以不再添加任何前缀, 直接使用他们
// 从 vuex 中引入映射
import {mapState,mapGetters,mapActions} from 'vuex'
export default {
computed:{
...mapState(['state 中的属性值'])
...mapGetters(['getters.js 中的方法名'])
},
methods:{
...mapActions(['actions.js 中的方法名'])
}
}