1.项目地址
2.环境搭建
git clone [resposibility]
(resposibility为目录名称,记得要替换)cd [resposibility]
(resposibility为目录名称,记得要替换)npm install
(安装依赖,有的安装不了,记得切换npm源 不会🤔点我)npm run dev
(进入开发者模式)npm run build
(打包静态资源)
3.基础搭建
1. 真的有点坑
基础架构搭建使用的是开源库 开源地址🤔, 当时看到star很多就选择了后面自己实际写业务模块的时候,发现坑真的多啊。最多的是因为该项目模板长期未更新,导致依赖模块的版本过低,依赖之间各个不兼容,甚至当时
webpack
是3.0的。通过大量的github issue
查找,谷歌英文搜索,逐渐的完善了下来。
2. 踩坑中学习
2.1 项目架构分析
2.2 MOCK
-
(1)在
main.js
中引入mock
:
import '../mock';
-
(2)配置
mock
信息
import Mock from 'mockjs';
// 通过Mock.mock()模拟api接口
Mock.mock('/api/goodslist', 'get', {
status: 200,
message: '获取数据列表成功',
'data|5-10': [
{
// 'id|+1': 0, //模拟自增长的id
id: '@increment(1)',
name: '@cword(2,5)',
price: '@natural(2,10)',
count: '@natural(100,999)',
img: '@dataImage(25x25)'
}
]
});
-
(3) 使用
axios
本地请求mock
数据
axios
的baserURl
需要为/
,然后正常的请求为
this.$ajax({
url: '/api/goodslist',
method: 'get',
})
.then(data => {
console.log(data, 'from mock')
})
结果
2.3 webpack
-
(1)
Node.js
核心模块之path
path.__dirname
指当前文件所在目录的绝对地址。path.resolve()
和path.join()
的区别 -
(2)
HtmlWebpackPlugin
插件webpack
入口文件entry
为main.js
,打包资源路径设置为和webpack.config.js同级下的dist文件,**将publicPath设置为''**能够保证静态资源部署在服务器的时候,已合适的方式找到资源地址。否则会出现地址路径不匹配的情况。
通过HtmlWebpackPlugin
插件可以自动的将打包后的静态资源注入到已经写好的index.html
文件中。这样就成为了最初的index.html
引入head
、link
、body
、script
的方式。通过file的方式是打开是不完整的,需要使用node.js
的静态资源加载的方式,获取静态资源。即app.use('/', express.static(path.resolve(__dirname, './dist')))
的方式了。 -
(3)
BundleAnalyzerPlugin
插件该插件主要用于分析打包完成后静态资源
size
情况。
配置如下
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html')
}),
new BundleAnalyzerPlugin({
analyzerHost: '127.0.0.1',
analyzerPort: '7000'
})
],
目前的感觉就是,让各个模块的size
均衡分布,比如大的模块要需要尽量拆分出来,比如之前的element-ui
和vue
模块,引入之后的打包资源非常大,我的1G2核的服务器在初次加载的时候,耗时很长才能加载页面出来。于是我查阅资料,阅读webpack文档后,引入了externals
;
-
(4)
externals
配置(打包资源优化)
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。这样打包后的资源就缩小了很多,在优化方面,尽量不要因为某个功能就去引入一整个包,如果需要这个功能的话,就去研究这个npm
包的代码出来,然后扣出你所需要的功能,直接使用在项目中。当然很多huawei员工也这样干的,不过需要 MIT协议了。这也就是为啥需要理解prototype
原型链、this
指向、执行上下文这些的原因了,理解了,帮助你更好的吸收源代码,然后魔改一番到自己的项目中啦
所以index.html
静态资源就出现了cdn的引入链接:
<body>
<!-- import Vue before Element -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
let metaEl = document.querySelector('meta[name="viewport"]');
let dpr = window.devicePixelRatio;
let scale = 1 / dpr;
metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=yes');
</script>
<div id="app"></div>
</body>
exernals
配置如下
externals: {
vue: 'Vue',
'element-ui':'ELEMENT'
},
-
(5)
Loaders
管理资源
当我们混入一些资源如图片、字体、样式等等,可借助webpack通过 loader 或内置的 Asset Modules 引入任何其他类型的文件。webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
},
{
test: /\.sass$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader?indentedSyntax'
],
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
'scss': [
'vue-style-loader',
'css-loader',
'sass-loader'
],
'sass': [
'vue-style-loader',
'css-loader',
'sass-loader?indentedSyntax'
]
}
}
},
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre",
include: [path.resolve(__dirname, 'src')],//指定检查的目录
options: {
formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
},
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.(ttf|eot|svg|woff|woff2)$/,
loader: 'url-loader'
},
]
},
-
(6) 配置
webpack-dev-server
进行热更新
配置如下:
devServer: {
contentBase: './dist',
},
package.json
脚本为
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
配置告知
webpack-dev-server
,将dist
目录下的文件serve
到localhost:8080
下。(serve,将资源作为 server 的可访问文件)
webpack-dev-server
会从output.path
中定义的目录为服务提供bundle
文件,即,文件将可以通过http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename]
进行访问。
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过 dev server 配置中的 publicPath 选项进行修改。
3. 向优秀学习
优秀的架构采用的是vue-cli3
,然后在vue.config.js
中使用chainWebpack
,然后引入webpack.dev.js
和 webpack.pro.js
两种环境模块。当然了,两者开发模块间还有公共部分,于是乎又出现了webpack.common.js
模块供dev
和pro
环境使用。然后针对前端本地代理的服务器proxy
继续抽象,让代理服务器地址、端口等配置信息单独抽离,作为json
。json
作为webpack原生支持的模块,根本不用使用module.exports
,直接导入导出。然后就是在针对webpack的一通操作了:loader
、plugins
、performance
等等。
4. 模块构建
5. 业务方向
6. 性能优化
7. 打包部署
8. 工具方法
npm源切换
- nrm 提供下包的地址,使用nrm可以切换下载源
- nrm ls 显示的是地址.
- npm 是一个包管理工具,可以用来装包。
- npm i cnpm -g 下载了cnpm包管理器
如果你公司有自己的npm项目源,记得找你同事索取,具体修改记得是.nrmp地址。
9. 功能相关(只是写项目的时候的记录,语言还未阻止清晰👀)
1.用户提交答案
用户提交答案的id由登录时存储并在提交答案页面获取。用户点击下一题的时候,需要将用户信息本地缓存,防止用户信息丢失;当用户不小心将页面刷新时,可以通过调用回显接口。
2.动态路由跳转
提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。复用组件为Summary
你可以简单地 watch (监测变化) $route 对象
const User = {
template: '...',
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
}
}
或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:
@/components/summary/index.vue
beforeRouteUpdate(to, from, next) {
this.renderData(to.params.id, 2)
next();
},
3. @include @mixin 等引入
@/common/z-input/index.vue
$success: #586AEA;
$warning: #ffcc00;
$error: #cc3300;
@mixin map-radio($color) {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid $color;
.pitch {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: $color;
text-align: center;
}
}
.success.radio {
@include map-radio($success)
}
.warning.radio {
@include map-radio( $warning)
}
.error.radio {
@include map-radio($error)
}
10. 项目设计思路分析
main.js
:
- 该脚本
-
- 通过原型拓展Vue构造函数的功能:
axios
封装./utils/request
,
- 通过原型拓展Vue构造函数的功能:
-
- 引入vue全家桶vue-router、vuex作为new Vue()的参数,vue实例化(▶实例化内部vue做了什么事情)后将编译成render函数将vue-router匹配的首页组件
home/index.vue
编译成render函数,然后挂载在引入的App.vue中的id为app的element中。
- 引入vue全家桶vue-router、vuex作为new Vue()的参数,vue实例化(▶实例化内部vue做了什么事情)后将编译成render函数将vue-router匹配的首页组件
-
import
引入element
,Vue.use(element)❓使用element-ui组件, import 引入公共scss样式,import 引入./permission.js
(▶vue组件全局路由钩子)❓引入的机制和在vue内部的实现如何拓展vue实例功能。
render内部渲染
permission.js
permission.js
定义了全局路由钩子。通过工具函数获取存储在本地cookie中的token;当存在token后,访问登录页,直接重定向path:/
,访问别的页面直接放行。当不存在token时,如果要访问的路径不在白名单中,重定向到登录页;存在白名单中时,直接放行。
import router from './router/index.js';
// import store from './store/index.js';
import tokenInstance from './utils/auth.js'; // 从cookie获取token
const whiteList = ['/login', '/', '/sign', '/register']; // 路由白名单
router.beforeEach((to, from, next) => {
const hasToken = tokenInstance.getToken();
if(hasToken) {
if(to.path == '/login') {
next({ path: '/' })
} else {
next();
}
} else {
whiteList.indexOf(to.path) !== -1 ? next() : next({ path: '/login'})
}
})
项目中涉及到技术分析
试图修改子组件中计算属性的值
父组件
/**
* 清空选中效果
*/
clearSelect() {
this.render.choose.forEach((item,index) => {
let target = this.$refs['xInput' + index][0];
target.innerState = ""
})
}
子组件
computed: {
innerState() {
console.log(this.value, this.reply, 'hi')
if(this.value === this.reply) {
return 'success'
}
}
}
报错:
解决方案
因为本人的computed不依赖data属性,所以❓即便设置set估计也不会响应式触发所以暂时弃用computed,使用methods。嗯~🤔有些牵强,毕竟computed也是依赖props属性的,props也是响应式属性呢。但是props规范并不支持被修改呢。
连续组件传值
组件传值的时候,父组件定义的初始值和经过ajax或者别的方式更改的值都会传入到子组件中。如果子组件将接收到的props值非直接渲染,而是通过将props值赋值给data中的属性值的话,那么将会出现无法渲染数据的问题。❓原因在于经过data、props的属性初始化一次,并不会监听传入的值的变化你会问了,为什么直接在
<template></template>
模板能够动态监听到props的变化,并渲染呢。原因嘛:😂等后面需要的时候在去研究。言归正传:解决方案就是使用watch监听props值的变化喽。
将props值保存到本地
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个 prop 的情形:
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
对题目选项做出选择 && 刷新或者跳转路由切换时,选择项回显 && 题目答案判断
效果展示
选中效果
题目判断
逻辑说明
抽离为三个参数:solutionsArray replys(用户选择) "string collection" value: "string"(x-input组件的value值)。当选中时,将用户选择项和value对比,符合就在computed返回"success"class类;当用户点击交卷时,请求带有答案的接口,然后replys和solutions比较:1.完全相等2. 不相等 2.1 部分匹配 2.2 部分不匹配 2.3 完全不匹配;然后匹配出的数组matchList和value对比即可。