快速构建项目
当下潮流的做法一般采用前后端分离的方式进行Web架构,但同时也对前端开发环境的搭建提出了更高的要求。
一个完整的前端开发环境应该具备预编译模板、注入依赖、合并压缩资源、分离开发和生产环境及提供一个模拟的服务端环境等功能
项目快速构建工具–Vue CLI
Vue CLI简介
可用于快速搭建带有热重载(在代码修改后不必刷新页面即可呈现修改后的效果)、lint代码语法检测及构建生产版本等功能的单页面应用
使用Vue CLI构建项目
1、打开控制台,输入:
cnpm install vue-cli -g
安装Vue CLI,尚未安装cnpm的同学可以输入
npm install cnpm -g --registry=https://registry.npm.taobao.org
安装国内淘宝镜像源的cnpm
命令执行结束后,输入
vue --version
输出版本号,安装成功
2、在项目所要放置的文件目录下打开控制台,输入:
vue init webpack my-project
初始化项目(my-project为项目名称)
3、在模板下载完成后,Vue CLI将引导我们进行项目配置,笔者的配置如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O8WONfkj-1657769007435)(C:\Users\Toreme\AppData\Roaming\Typora\typora-user-images\image-20220322001231673.png)]
其中, “Set up unit tests” 和“Setup e2e tests with Nightwatch”选择“no”,这部分内容与Vue没有直接关系,这里
不予探讨。最后一项也选择“no”是因为npm的镜像源在国外,安装依赖的速度缓慢且容易出错,笔者建议使用cnpm安装依赖。
4、输入
cnpm install
安装项目依赖
5、输入
npm start
构建项目的开发版本,并启动webpack-dev-server
此时,在浏览器地址栏输入http://localhost:8080即可访问项目,项目页面为大V
6、之后,另开一个控制台,输入
npm run build
构建项目的生产版本
项目目录介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omIQiZlP-1657769007437)(C:\Users\Toreme\AppData\Roaming\Typora\typora-user-images\image-20220322001826167.png)]
main.js是webpack的入口文件
App.vue是Vue CLI为我们默认创建的项目的根组件
- VueCLI采用关注点分离的开发方式,这种开发方式使得组件的内聚性更强,也更适合于组件化的开发
- script标签中的内容为Vue组件:template标签中的内容为组件的DOM结构;style标签中的内容为CSS样式表(在被赋予scoped属性之后,样式表的作用域仅限在当前组件中)
- export和import是ECMAScript6语法中用于模块化管理的两个关键字,这里使用export导出Vue组件以供外部调用
Vue CLI会将所有编译整理好的资源路径注入到以index.html为模板的镜像中,被注入后的镜像即生产版本中项目的入口文件,也就是dist文件目录下的index.html,这里的元素才是实例最终被挂载的地方。
在src文件目录下,还有一个重要的文件–使用Vue Router配置的router/index.js 有关Vue Router的内容,笔者将放到下一节中进行讲述
前端路由
路由这个概念首先出现在后台。传统MVC架构的web开发,由后台设置路由规则,当用户发送请求时,后台根据设定的路由规则将数据渲染到模板中,并将模板返回给用户。每一次请求就要刷新一次页面,十分影响交互体验
AJAX(Asynchronous Javascript And Xml),异步加载数据的方式实现页面局部刷新。异步交互体验
---->SPA——单页面应用,不仅页面交互无刷新,甚至页面跳转也可以无刷新,前端路由随之应运而生
前端路由的简单实现
广义:根据URL来分发视图,核心操作
- 监听浏览器地址的变化
- 动态加载视图
const http=require('http')
const fs=require('fs')
const hostName='127.0.0.1'
const port=3000
const server=http.createServer(function(req,res){
let content=fs.readFileSync('index.html')
res.writeHead(200,{
'content-type':'text/html;charset="utf-8"'
})
res.write(content)
res.end()
})
server.listen(port,hostName,function(){
console.log('Server is running here: http://${hostName}:${port}')
})
node app.js //node + filename
when console show Server is runing here:http://127.0.0.1:3000"
Vue模拟实现
index.html
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<div id="app">
<ul>
<li><router-link to="/">Home</router-link></li>
<li><router-link to="/about">About</router-link></li>
</ul>
<router-view></router-view>
</div>
<script type="text/javascript">
let Home={
template:'<h1>This is Hime!</h1>'
}
let About={
template:'<h1>This is About!</h1>'
}
let routes=[
{
path:'/',
component:Home
},
{
path:'/about',
component:About
}]
let RouterLink={
props:['to'],
template:'<a :href="to"><slot name="default"></slot></a>'
}
let RouterView={
data(){
return {
url:window.location.pathname//浏览器地址
}
},
computed:{
ViewComponent(){
return routes.find(route=>route.path===this.url).component
}
},
render(h){
return h(this.ViewComponent)
}
}
/*eslint-disable*/
let vm=new Vue({
el:'#app',
components:{RouterLink,RouterView}
})
</script>
JS实现前端路由的代码
<div>
<ul>
<li><a href="#/">Home</a></li>
<li><a href="#/about">About</a></li>
</ul>
<!-- 动态视图被挂载的元素 -->
<div id="view"></div>
</div>
<script type="text/javascript">
let Home = '<h1>This is Home!</h1>' // 视图模板Home
let About = '<h1>This is About!</h1>' // 视图模板About
let Router = function (el) { // 定义路由类
let view = document.getElementById(el)
let routes = [] // 路由规则列表
let load = function (route) { // 加载视图
route && (view.innerHTML = route.template)
}
let redirect = function () { // 分发视图
let url = window.location.hash.slice(1) || '/'
for (let route of routes) {
url === route.url && load(route)
}
}
this.push = function (route) { // 添加路由规则
routes.push(route)
}
window.addEventListener('load', redirect, false) //
页面加载时
window.addEventListener('hashchange', redirect, false)
// URL变化时
}
let router = new Router('view') // 实例化路由
router.push({ // 添加路由规则
url: '/',
template: Home
})
router.push({
url: '/about',
template: About
})
</script>
a标签的href中写入"#"符号,可以阻止页面刷新,(实现了单页面应用),单也会在URL中加入该符号。
在redirect函数中并没有直接取window.location.hash的值,而是先用slice(1)将"#"去掉
Vue中的前端路由
Vue Router是Vue.js官方提供的路由管理器,它与Vue.js的核心深度集成
1、基本路由
RouterLink和RouterView是Vue Router提供的两个内置组件。
RouterLink默认会被渲染成一个标签,它的to属性用于指定跳转链接;
RouterView将负责挂载路由匹配到的视图组件。
2、动态路由
路径参数应用英文冒号“:”标记,但是在使用时应注意设计的规则是否合理
routes:[
{path:'/:any',component:Home}//可以匹配路径为/about的路由,“about”将作为any的值
]
当动态路径被匹配时,我们可以在组件中使用this.$route.params来获取参数的值
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vuerouter.
js"></script>
<div id="app">
<ul>
<li><router-link to="/">Home</router-link></li>
<li @click="add">
<!-- 2. 参数num由实例传入路由 -->
<router-link :to="'/about/' + num">About</routerlink>
</li>
</ul>
<router-view></router-view>
</div>
<script type="text/javascript">
let Home = { template: '<h1>This is Home!</h1>' } //
Home组件
let About = { // About组件
template: '<div>' +
'<h1>This is About!</h1>' +
'<p>num: {{ $route.params.num }}</p>' + // 3. 在组件中
显示参数 num
'</div>'
}
let routes = [ // 定义路由规则,每一个路由规则应该映射一个视图
组件
{ path: '/', component: Home },
{ path: '/about/:num', component: About } // 1. 定义了
参数 num, 格式如:
/:num
]
let router = new VueRouter({ // 创建Vue Router实例,并传入
routes配置
routes
})
let app = new Vue({
data () {
return { num: 0 }
},
methods: { // 当点击About时,num值自增1
add () { this.num++ }
},
router
}).$mount('#app')
</script>
- 首先在html部分使用标签并设定to属性(路由规则)
- 定义组件
- 设定路由规则,格式
{path:'/xxx',component:Home}
- 创建Vue Router实例,并传入routes配置
new VueRouter({ routes})
3、嵌套路由
- 嵌套路由可以实现在动态视图中嵌套动态视图
- 多层的动态视图可以使用Vue的内置组件component来实现
- 使用component切换的视图会在页面刷新后回到初始状态,而使用路由分发的视图会在页面刷新后保持当前路径对应的视图,并在浏览器的history中留下记录
let routes = [ // 定义路由规则,每一个路由规则应该映射一个视图
组件
{ path: '/', component: Home },
{
path: '/about',
component: About,
children: [ // 2. 嵌套子路由
{ path: 'author', component: Author },
{ path: 'email', component: Email }
]
}
]
4、编程式路由
- 可以不使用RouterLink组件,而是在JS中使用router.push方法跳转视图
- @click=“redirectByPath(‘/xxxx’)”
- 通过路由的path跳转视图,还可以赋予路由name属性,然后通过name跳转视图
- 动态参数应该放在params中,当使用path时,param参数不生效,此时应将参数值直接写进path中
<div id="app">
<ul>
<!-- 默认字符串为路径参数 -->
<li @click="redirectByPath('/')">Home</li>
<li>
<!-- 指定参数为路径 -->
<div @click="redirectByPath('/about')">About</div>
<ul>
<!-- 嵌套路由-->
<li @click="redirectByPath('/about/author')">About - Author</li>
<!-- 嵌套路由,动态路由,当使用path时,params参数不生效
-->
<li @click="redirectByPath('/about/email', {email: lonelydawn@sina.com' })">About - Email</li>
<!-- 嵌套路由,动态路由,可以直接将参数写入path -->
<li @click="redirectByPath('/about/email/lonelydawn@sina.com')">About - </li>
<!-- 嵌套路由,动态路由,使用命名路由跳转视图 -->
<li @click="redirectByName('Email', { email:'singledawn@sina.com' })">About - Email</li>
</ul>
</li>
</ul>
<router-view></router-view>
</div>
5、使用Vue CLI快速构建的router/index.js
- 使用Vue.use安装Vue Router插件
- 使用export返回路由规则。默认只有当路径为“/”时。渲染HelloWorld组件
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
状态管理
小型应用来说,完全没有必要引入状态管理,这会带来更多的开发成本。然而当应用的复杂度逐渐提高,状态管理也越发重要起来
对于组件化开发来说,大型应用的状态往往跨越多个组件。
多层嵌套的父子组件之间传递状态十分复杂,而Vue没有为兄弟组件提供直接共享数据的方法。
基于这个问题,许多框架提供了解决方案——使用全局的状态管理器,将所有分散的共享数据交由状态管理器保存
Vue官网提供的状态管理器名为Vuex
对象引用
js中,对象的赋值,是某一时刻对对象的引用
核心是地址传递
状态管理器Vuex
管理分散在Vue各个组件中的数据
每一个Vuex应用的核心都是一个store(仓库)
store–非凡的全局变量
基于Vue数据与视图绑定的特点,当store中的状态发生变化时,与之绑定的视图也会被重新渲染
单向的过程,store中的状态不允许被直接修改。
唯一途径是 显示地提交(commit)mutation,这可以让我们方便地追踪每一个状态的变化
5个重要的概念
- State
- Getter
- Mutation
- Action
- Module
State
用于维护所有应用层的状态,并确保应用只有唯一的数据源
(SSOT,Single Source of Truth)
new Vuex.Store({
state:{
count:1
}
})
组件中,我们可以直接使用$store.state.count
也可以先用mapState辅助函数将其映射下来
import {mapState} from 'vuex'
export default{
computed:{
mapState(['count'])
}
}
Getter
维护由State派生的一些状态,这些状态随着State状态的变化而变化。
与计算属性一样,Getter中的派生状态在被计算之后会被缓存起来,当重复调用时,如果被依赖的状态没有变化,那么Vuex不会重新计算派生状态的值,而是直接采用缓存值
new Vuex.Store({
state:{
count:1
},
getters:{
tenTimesCount(state){
return state.count*10
}
}
})
组件中可以直接使用$store.getters.tenTimesCount
mapGetters辅助函数将其映射
import {mapGetters} from 'vuex'
export default{
computed:{
mapGetters(['tenTimesCount'])
}
}
Mutation
提供修改State状态的方法
new Vuex.Store({ // 创建仓库
state: {
count: 0
},
mutations: {
addCount (state, num) {
state.count += num || 1
}
}
})
组件中,可以直接使用store.commit来提交mutation
methods: {
addCount () {
this.$store.commit('addCount') // store被注入到Vue实例中后可使用this.$store
}
}
也可以先用mapMutation辅助函数将其映射下来,代码如下:
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['count']) // ...是ES 6中的对象展开运算符
},
methods: {
...mapMutations(['addCount']),
...mapMutations({ // 为mutation赋别名,注意冲突,此方法不常用
increaseCount: 'addCount'
})
}
}
Action
类似Mutation,不同在于
- Action不能直接修改状态,只能通过提交mutation来修改
- Action可以包含异步操作
Module
由于使用单一状态树,当项目的状态非常多时,store对象就会变得十分臃肿
因此,Vuex允许我们将store分割成模块(Module),每个模块拥有独立的State、Getter、Mutation和Action,模块之中还可以嵌套模块,每一级都有着相同的结构
在项目中使用Vuex
打开项目地址cmd
cnpm install vuex --save-dev
安装插件
在src 目录下创建store 、store/index.js 、store/modules、store/modules/counter.js
// …是ES 6中的对象展开运算符
},
methods: {
…mapMutations([‘addCount’]),
…mapMutations({ // 为mutation赋别名,注意冲突,此方法不常用
increaseCount: ‘addCount’
})
}
}
### Action
类似Mutation,不同在于
- Action不能直接修改状态,只能通过提交mutation来修改
- Action可以包含异步操作
### Module
由于使用单一状态树,当项目的状态非常多时,store对象就会变得十分臃肿
因此,Vuex允许我们将store分割成模块(Module),每个模块拥有独立的State、Getter、Mutation和Action,模块之中还可以嵌套模块,每一级都有着相同的结构
## 在项目中使用Vuex
打开项目地址cmd
cnpm install vuex --save-dev
安装插件
在src 目录下创建store 、store/index.js 、store/modules、store/modules/counter.js