简介
- 参考文章
- vue异常代码开发环境正常报错,编译之后不报错且页面卡死问题。参考: https://github.com/PanJiaChen/vue-element-admin/issues/2212
Vue3
- 选项式和组合式:选项时为类似Vue2的JSON模式,组合式为类似React的函数式模式(使我们可以使用函数而不是声明选项的方式书写 Vue 组件)
- Vue3中没有了
$parent
和$children
- 可通过
getCurrentInstance()
获取当前组件,findComponentUpward()
向上查找父组件,和findComponentsDownward()
向下查找子组件。参考:https://juejin.cn/post/7117808675716071460
- 可通过
- 文章
语法
- v-model
- 单个属性建议使用
modelValue
(固定props值),此时直接v-model="data"
即可 - 多个属性可定时如
myModel
(取值props.myModel),此时需使用v-model:myModel="data"
(只有modelValue才能进行省略)
- 单个属性建议使用
watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51<script setup>
import { watch, ref, reactive } from 'vue'
// ==> 侦听一个 getter
const person = reactive({name: '小松菜奈'})
watch(
() => person.name,
(n, o) => {
console.log(n, o)
},
{immediate:true}
)
person.name = '有村架纯'
// ==> 直接侦听ref,及停止侦听
const ageRef = ref(16)
const stopAgeWatcher = watch(ageRef, (n, o) => {
console.log(n, o)
if (value > 18) {
stopAgeWatcher() // 当ageRef大于18,停止侦听
}
})
const changeAge = () => {
ageRef.value += 1
}
// ==> 监听多个数据源
// 如果你在同一个函数里同时改变这些被侦听的来源,侦听器只会执行一次
// 如果用 nextTick 将两次修改隔开,侦听器会执行两次
const name = ref('小松菜奈')
const age = ref(25)
watch([name, age], ([name, age], [prevName, prevAge]) => {
console.log('newName', name, 'oldName', prevName)
console.log('newAge', age, 'oldAge', prevAge)
})
// ==> 侦听引用对象(数组Array或对象Object)
const arrayRef = ref([1, 2, 3, 4])
const objReactive = reactive({name: 'test'})
// ref deep, getter形式,新旧值不一样
const arrayRefDeepGetterWatch = watch(() => [...arrayRef.value], (n, o) => {
console.log(n, o)
}, {deep: true})
// reactive,deep,(修改name)新旧值不一样
const arrayReactiveGetterWatch = watch(() => objReactive, (n, o) => {
console.log(n, o)
}, {deep: true})
</script>
约定俗成
- 习惯
- 项目url固定链接不以
/
结尾,使用地方手动加/
方便全局搜索
- 项目url固定链接不以
- 注意
- vue单文件组件,每个文件里面只能含有一个script标签;如果含有多个,默认只解析最后一个
文件引入
1 | <!-- css --> |
vue组件
- 文件名建议大写开头驼峰或者小写下划线分割。windows下组件文件名对大小写不敏感,linux下组件文件名对大小写敏感
1 | <template> |
vue生命周期(含路由)
- 生命周期钩子
- 根组件实例:8个 (beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed)
- 组件实例:8个 (beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed)
- 先执行created同步代码, 执行mounted同步代码, 无法控制created中的异步代码执行完后再执行mounted
- 全局路由钩子:2个 (beforeEach、afterEach)
- 组件路由钩子:3个 (beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave)
- 指令的周期: 5个 (bind、inserted、update、componentUpdated、unbind)
- beforeRouteEnter的next所对应的周期
- nextTick所对应的周期
<keep-alive>
组件activated
和deactivated
- 钩子执行顺序
- 路由勾子 (beforeEach、beforeRouteEnter、afterEach)
- App根组件 (beforeCreate、created、beforeMount)
- 父组件-mixins (beforeCreate、created、beforeMount): 父组件 beforeCreate -> mixins beforeCreate -> 父组件 created -> …
- 组件 (beforeCreate、created、beforeMount)
- 指令 (bind、inserted)
- 组件 mounted
- 父组件-mixins (mounted)
- App根组件 (mounted)
- beforeRouteEnter的next的回调
- nextTick
- 销毁顺序
- mixins (beforeDestroy)
- 父组件 (beforeDestroy)
- 组件 (beforeDestroy)
- 组件 (destroyed)
- mixins (destroyed)
- 父组件 (destroyed)
- 浏览器地址栏刷新/回车/F5
- 所有页面组件重新创建,重头调用
beforeCreate
;且在某页面刷新时,该页面的beforeDestroy
等钩子不会被执行
- 所有页面组件重新创建,重头调用
后端模板中使用VUE
- 主页面
1 | <head> |
- 组件页面
1 | <!-- 模板的一部分 my-widget.ftl --> |
页面渲染
父子组件加载
- 加载顺序:先创建父组件,先挂载子组件
- 创建父组件
beforeCreate
-created
- 挂载父组件之前
beforeMount
- 创建子组件
beforeCreate
-created
- 挂载子组件之前
beforeMount
- 挂载子组件
mounted
- 挂载父组件
mounted
- 创建父组件
- 父子组件分别请求各自的数据,且子组件请求中的某些参数需要等父组件获取后台数据后才可初始化,此时最好
watch
对应的参数
1 | watch: { |
数组/对象改变数据不刷新问题
- 由于 JavaScript 的限制,Vue 不能检测以下变动的数组
- 当你利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
。如v-for循环想动态给item增加属性,此时只能先定义一个List,动态在方法中设置此List值,并通过JSON进行转换赋值 - 当你修改数组的长度时,例如:
vm.items.length = newLength
- 下列方法可触发视图更新
- push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- 当你利用索引直接设置一个项时,例如:
由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var vm = new Vue({
data:{
a: 1,
user: {
name: null
},
list: []
}
})
// `vm.a` 是响应的,`vm.b` 是非响应的(添加新属性)
vm.b = 2
vm.user.name = 'smalle' // 是响应的
vm.user.password = '123456' // 是非响应的(添加新属性)
vm.list = [{name: 'hello'}] // 响应的
vm.list[0].name = 'smalle' // 响应的
// 假设有一个按钮需要动态添加loading,可写成<Button :loading="myLoading[i]"></Button>,然后created之后对myLoading[i]赋初始值并重新设置myLoading(两次JSON转换),之后myLoading[i]就可以动态监听了Vue.set和Vue.delete的作用
Vue.set
向响应式对象中添加一个 property,并可确保这个新 property 同样是响应式的. eg: Vue.set(this.dictMap, key, list)- 也可使用如
this.$set(row, '_editLoading', true)
,常用于表格单行按钮loading效果
- Vue3.X新版本开始将采用ES6的Proxy来进行双向绑定。可解决上述问题(可以直接监听对象而非属性,可以直接监听数组下标的变化)
- 由于 JavaScript 的限制,Vue 不能检测以下变动的数组
- vue无法检测数组的元素变化(包括元素的添加或删除);可以检测子对象的属性值变化,但是无法检测未在data中定义的属性或子属性的变化
- 解决上述数组和未定义属性不响应的方法:
this.user = JSON.parse(JSON.stringify(this.user));
(部分场景可使用this.user = Object.assign({}, this.user);
) - 对于v-for,最好定义key值(且不能使用index作为key),否则容易出现无法选择/无法修改该select的值
- 如结合select的option循环时,只需要当前select的option的key值唯一,无需整个页面的key值保证唯一性
- 大多数情况下不建议使用index作为key。当第一条记录被删除后,第二条记录的key的索引号会从1变为0,这样导致oldVNode和newNNode两者的key相同。而key相同时,Virtual DOM diff算法会认为它们是相同的VNode,那么旧的VNode指向的Vue实例(如果VNode是一个组件)会被复用,导致显示出错 ^3
- 解决上述数组和未定义属性不响应的方法:
- provide和inject无法实时响应解决办法
- https://www.jianshu.com/p/2f210939cc4e
- provide 和 inject 绑定并不是可响应的,这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的
provide: {obj, name}
name如果是字符串类型(或者通过computed返回的字符串类型),则无法进行响应传递,可考虑放到obj对象中
- 扩展说明
1 | <!-- 示例使用iveiw库 --> |
页面渲染优化(性能优化)
- 参考文章 ^9
- vue渲染流程
打包优化
采用懒加载 ^13
- 将
import Hello from '@/components/Hello'
改成const Hello = () => import('@/components/Hello')
。本质上,它是利用了Promise 如使用动态加载的路由配置方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 未使用:打包后代码在一个 chunk-vendors 文件中
import Hello from '@/pages/Hello'
{
path: 'hello1',
name: 'hello1',
component: Hello
}
// 使用动态加载的路由配置方式:打包后代码被分散到多个 chunk-* 文件中,之后客户端可按需加载
{
path: 'hello2',
name: 'hello2',
component: () => import('@/pages/Hello')
}
- 将
启用 Gzip 压缩(以下两种方式建议同时进行)
- 开启nginx等服务器gzip。参考nginx.md#配置示例
使用 compression-webpack-plugin 将静态资源提前压缩成 gz 格式,且nginx开启
gzip_static on;
,就不用在服务器进行压缩。参考nginx.md#配置示例。速度比服务器压缩要更快,且传输资源更少1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16npm install compression-webpack-plugin --save-dev
// vue-cli为例。开发环境不进行压缩,否则页面无法显示
chainWebpack: config => {
if(process.env.NODE_ENV === 'production') {
const CompressionPlugin = require('compression-webpack-plugin');
config.plugin('compressionPlugin')
.use(new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i,
threshold: 10240,
minRatio: 0.8
// ,deleteOriginalAssets: true // 是否删除原资源,即只保留 .gz 文件。Electron打包需要保留原资源,否则安装包找不到相应js文件
}));
}
}
将依赖库挂到 CDN 上。可以提高首屏响应速度
- 常用CDN服务商
在
public/index.html
中引入库,部分代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- 方式一:引入相应版本的库文件 -->
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.7/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.1/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script>
<!-- 方式二: -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>" defer></script>
<% } %>
</body>配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 然后在 webpack(vue-cli为例) 中配置额外依赖(两种方式都需要)
configureWebpack: {
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'element-ui': 'elementUI',
'axios': 'axios',
}
},
// 方式二:注入cdn参数到htmlWebpackPlugin
chainWebpack: config => {
let cdn = {
js: ['https://cdn.bootcss.com/vue/2.6.10/vue.min.js']
}
cdn = process.env.NODE_ENV === 'production' ? cdn : {css:[],js:[]};
config.plugin('html').tap(args => {
args[0].cdn = cdn
return args
})
}
// 删除 package.json 的 dependencies 中此插件的依赖
// vue 页面可正常使用,无需修改
import Vue from 'vue'
减少不必要的库依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 方式一(不推荐):手动增加webpack-bundle-analyzer分析插件(vue-cli自带无需安装,参考方式二)
// 包依赖分析工具
npm install webpack-bundle-analyzer --save-dev
// vue-cli为例。执行 npm run build 后会自动打开 http://127.0.0.1:8888/ 显示包依赖信息。**使用完之后可注释此行代码,否则命令行一直不会退出,给人一种没打包完成的错觉**
chainWebpack: config => {
if(process.env.NODE_ENV === 'production') {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
config.plugin('bundleAnalyzerPlugin').use(new BundleAnalyzerPlugin({}))
}
}
// 方式二(推荐):vue-cli 自带分析插件
"scripts": {
"report": "vue-cli-service build --report --mode pord"
}
// 打包之后在dist文件夹里面会生成report.html文件
npm run report去掉 .map 文件:vue-cli设置
productionSourceMap: false
,参考下文vue-cli- 使用 UglifyJsPlugin 丑化js,去掉debugger/console等信息
- 使用
cnpm i -save-dev image-webpack-loader
对图片进行压缩 测试环境和生产环境出来的包不一致问题
- 测试环境 js 文件在 dist 根目录下,且存在很大的 main.js 和 app.js;build 打包之后 js 文件在 dist/js 目录下,main和app也被分割成几个小文件了
NODE_ENV
为webpack内置参数,可通过process.env.NODE_ENV
获取,当其值是production
(固定值)时,会进行压缩混淆等操作。所以测试环境和生产环境打包都应该设置NODE_ENV=production
,然后通过.env.test
和.env.prod
环境变量文件(vue-cli功能)区分不同环境的API地址,如created1
2
3
4
5
6
7// .env.test
NODE_ENV = production
VUE_APP_ENV = testenv
// .env.prod
NODE_ENV = production
VUE_APP_ENV = production
公共代码抽离
指令等语法优化
v-if
&v-show
^8- 生命周期
- v-if 控制着绑定元素或者子组件实例 重新挂载(条件为真)/销毁(条件为假) 到DOM上,并且包含其元素绑定的事件监听器,会重新开启监听。
- v-show 控制CSS的切换,元素永远挂载在DOM上
- 权限问题
- 涉及到权限相关的UI展示无疑用的是v-if
- UI操作
- 初始化渲染,如果要加快首屏渲染,建议用v-if
- 频次选择,如果是频繁切换使用,建议使用v-show
- 生命周期
v-key
使用- v-for需要配合v-key使用,且不能使用index作为key
- computed 和 watch 区分使用场景
- 需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算
- 需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率
- 动态增加Watch和Compute:https://juejin.cn/post/6911647306659938317
- 在v-for中使用计算属性:https://qastack.cn/programming/40322404/vuejs-how-can-i-use-computed-property-with-v-for
Vuex使用及优化
- 参考Vuex的使用场景
- 扁平化 Store 数据结构
- 避免持久化 Store 数据带来的性能问题:对必要数据进行写入;多次写入操作合并为一次
- 避免持久化存储的容量持续增长
使用Object.freeze()进行优化
Object.freeze()
是ES5新增的特性,可以冻结一个对象,防止对象被修改 ^10- const定义的对象不能被重新赋值引用,但是对象属性可进行修改
- Object.freeze()定义的对象可以重新赋值引用,但是对象属性不能修改
- 当把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,以便 Vue 追踪依赖,在属性被访问和修改时通知变化,进行页面重新渲染。但 Vue 在遇到像 Object.freeze() 这样被设置为不可配置之后的对象属性时,不会为对象加上 setter getter 等数据劫持的方法
- 示例
1 | new Vue({ |
优化无限列表性能
- 如果应用存在非常长或者无限滚动的列表,那么采用窗口化的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间
- 开源工具如:vue-virtual-scroller 和 vue-virtual-scroll-list
图片资源懒加载
- vue-lazyload 插件使用
组件懒加载
- 使用组件懒加载在不可见时只需要渲染一个骨架屏,不需要渲染一些隐藏组件
- 插件vue-lazy-component
第三方插件的按需引入
- 基于babel-plugin-component插件
服务端渲染/预渲染
组件
v-model使用
- 官方说明
- 双向数据绑定主要需要解决表单元素值改变后对应的变量值同时变化(变量值变化表单元素的值变化是肯定的)
- 在原生表单元素中
<input v-model="inputValue">
相当于<input v-bind:value="inputValue" v-on:input="inputValue = $event.target.value">
- 简单弹框案例
1 | <script> |
- 扩展案例说明
1 | <!-- 示例一 --> |
父子组件通信
Prop、自定义事件https://cn.vuejs.org/v2/guide/components.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6
通信方式
- 通过
props
从父向子组件传递数据,父组件对应属性改变,子组件也会改变。子组件中不建议修改props
中的属性- 在Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribute传递props给组件内,组件内只能被动接收组件外传递过来的数据,并且在组件内,不能修改由外层传来的props数据
- 如果在子组件中修改定义的
props
参数,则会报错:vue.esm.js:591 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "customerId"
- 自定义事件
$emit
,子组件可以向父组件传递数据(参考以下示例) - 通过
$refs
属性,父组件可直接取得、修改、调用子组件的数据- 场景还原:父组件点击按钮,控制显示子组件的弹框(
iview
弹框),此时当iview
弹框关闭时会修改v-model
的值,如果用props
则违反了props
单向数据流的原则 ref
可以用于标记一个普通元素或组件$refs
只有mounted
了之后才能获取到数据
- 场景还原:父组件点击按钮,控制显示子组件的弹框(
- 在子组件中可以通过
$parent
调用父组件属性和方法,修改父组件的属性也不会报错。注意:像被iview的TabPane包裹的组件,其父组件就是TabPane - 在父组件中使用
sync
修饰符修饰props属性,则实现了父子组件中hello的双向绑定,但是违反了单项数据流,只适合特定业务场景 - 全量绑定props参数(v-bind/v-on)
1 | <!-- |
props定义说明
1 | props: ['count', 'name'] |
示例
1 | <!-- parent.vue --> |
子组件和子组件通信(Bus)
1 | // 在初始化web app的时候,main.js给data添加一个 名字为eventBus的空vue对象。就可以使用 this.$root.eventBus 获取对象 |
slot插槽
1 | <!-- (定义slot)组件comp.vue --> |
动态组件/异步组件
- 基本说明 ^1
v-bind:is="组件名"
:就是几个组件放在一个挂载点下,然后根据父组件的某个变量来决定显示哪个,或者都不显示keep-alive
:默认被切换掉(非当前显示)的组件,是直接被移除了。假如需要子组件在切换后,依然需要他保留在内存中,避免下次出现的时候重新渲染,那么就应该在component标签中添加keep-alive
属性activate
:钩子,延迟加载transition-mode
过渡模式
- 动态加载外部组件
- 简单案例
1 | <div id="app"> |
keep-alive
- 默认被切换掉(非当前显示)的组件,是直接被移除了。假如需要子组件在切换后,依然需要他保留在内存中,避免下次出现的时候重新渲染,可使用
keep-alive
^5 用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- 缓存动态组件 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 缓存路由组件,可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件。如果使用include属性则可有条件的缓存 -->
<keep-alive>
<router-view></router-view>
</keep-alive>生命周期钩子
- 在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子
activated
与deactivated
(写在匹配到的组件中) - 不使用keep-alive: beforeRouteEnter -> created -> mounted -> destroyed
- 使用keep-alive
- 初次进入页面: beforeRouteEnter -> created -> mounted -> activated -> deactivated
- 再次进入缓存的页面: beforeRouteEnter -> activated -> deactivated (created和mounted不会再执行)
- 在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子
include
和exclude
(Vue2.1.0新增,之前版本可通过其他方式代替)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- 缓存路由 -->
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
<!-- 缓存路由,仍然可以使用数组 -->
<keep-alive include='a'>
<router-view></router-view>
</keep-alive>- 匹配规则
- 首先匹配组件的name选项,如果name选项不可用
- 则匹配它的局部注册名称(父组件 components 选项的键值)
- 匿名组件,不可匹配(比如路由组件没有name选项时,并且没有注册的组件名)
- 只能匹配当前被包裹的组件,不能匹配更下面嵌套的子组件(比如用在路由上,只能匹配路由组件的name选项,不能匹配路由组件里面的嵌套组件的name选项)
<keep-alive>
不会在函数式组件中正常工作,因为它们没有缓存实例- include和exclude可同时使用,exclude的优先级大于include
- 匹配规则
路由和keep-alive
- 如果发现未缓存,可看看是否有子孙路由也用到了router-view,此时要将最近的router-view进行缓存
- https://www.lmlphp.com/user/16603/article/item/552232/
案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52<template>
<div>
<keep-alive :include="includedComponents">
<router-view v-if="keepAlive" :key="key" />
</keep-alive>
<router-view v-if="!keepAlive" />
</div>
</template>
<script>
import Vue from 'vue'
const CACHE_INCLUDED_ROUTES = 'cache_included_routes'
export default {
name: 'ParentView',
data() {
return {}
},
computed: {
key() {
return this.$route.path
},
includedComponents() {
const includedRouters = Vue.ls.get(CACHE_INCLUDED_ROUTES)
if (this.$route.name && !(this.$route.meta && this.$route.meta.notCache)) {
let cacheRouterArray = Vue.ls.get(CACHE_INCLUDED_ROUTES) || []
if (!cacheRouterArray.includes(this.$route.name)) {
cacheRouterArray.push(this.$route.name)
Vue.ls.set(CACHE_INCLUDED_ROUTES, cacheRouterArray)
return cacheRouterArray
}
}
return includedRouters
},
keepAlive() {
return !(this.$route.meta && this.$route.meta.notCache)
},
},
}
</script>
<!-- 删除标签时清除缓存 -->
<script>
handleCloseTag(remailTag) {
// 关闭缓存
const CACHE_INCLUDED_ROUTES = 'cache_included_routes'
const cacheRouterArray = remailTag.map(x => x.name)
Vue.ls.set(CACHE_INCLUDED_ROUTES, cacheRouterArray)
}
</script>
不同路由指向同一组件,需要全部缓存
- 方案一:避免路由直接指向同一组件,可将在外面报告一层页面,参考:https://blog.csdn.net/yanxiaomu/article/details/100753335
- 方案二:更改keep-alive源码使自定义key为path(默认为组件name),参考:https://blog.csdn.net/qq_44170108/article/details/133313641
- 参考路由页签组件:https://github.com/bhuh12/vue-router-tab
事件
示例
1 | <!-- stop阻止冒泡,prevent阻止原生事件(如表单提交页面刷新) --> |
鼠标点击其他区域事件
- 基于指令
- 简易版本参考:https://www.jianshu.com/p/9e1c241d8edb
- iview版本参考:iview/src/directives/v-click-outside-x.js
监控全局点击事件(不推荐)
1 | // 1. main.js |
点击按钮后下载
1 | <!-- click方法中不能直接使用window对象 --> |
插入字符串
1 | <Select v-model="editForm.alertField"> |
自动转大写
- 有时候会出现看着转大写了,但是传输到后台的最后一个字母还是小写
1 | <Input size="small" placeholder="编号" v-model="form.orderNo" clearable |
样式
lang 和 scoped
1 | <script> |
全局样式/自动化导入
https://cli.vuejs.org/zh/guide/css.html#%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AF%BC%E5%85%A5
- 以vue-cli自动导入stylus为例
1 | npm i style-resources-loader -D // 需要提前安装依赖 |
- less/sass为例(stylus亦可)
1 | # 更新vue cli到3.0以上 |
- main.js引入样式文件、全局样式自动化导入、vue文件中样式关系
- 优先级:vue文件样式 > 全局样式自动化导入 > main.js引入样式文件
- 全局样式自动化导入中使用
/deep/
有效;main.js引入样式文件中/deep/
无效,直接写即可修改第三方组件样式 - main.js引入文件作用域
- 引入的 css 都是全局生效的
- 引入的 js 文件只在 main.js 中生效。是因为 main.js 在webpack中是一个模块,引入的 js 文件也是一个模块,在其他地方是访问不到的,这就是ES6的模块化。所以如果想 main.js 引入的 js 全局可用,就需要绑定到全局对象上,比如绑定 Vue 上
样式使用举例
- 使用案例
1 | // 通过~导入node_module目录下模块样式(也可写成相对路径), 从而可进行变量覆盖 |
- vue组件中给body设置样式
1 | <!-- 法一:通过生命周期直接修改body样式 --> |
接受外部参数动态修改样式
1 | <!-- Vue2支持 --> |
transition 动画
1 | <!-- |
- 结合Velocity.js,Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画
mixins混入
- mixins
- 值为对象的选项,例如 data()、methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
- created 等方法,会先执行混入created方法,再执行组件created方法
- 全局混入
Vue.mixin({...})
- 案例
1 | <!-- 被导入通用功能组件 --> |
- 通用代码
1 | // create.js |
指令
自定义指令
- 文档
- 钩子函数
bind
只调用一次,指令第一次绑定到元素时调用inserted
被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)update
所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前- ·componentUpdated` 指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind
只调用一次,指令与元素解绑时调用
- 钩子函数参数
el
指令所绑定的元素,可以用来直接操作 DOMbinding
包含name, value, expression, arg, modifiers. 案例v-demo:foo.a.b="message"
(message=hello!)- name: demo
- arg: foo (也支持变量
v-pin:[direction]="200"
) - modifiers: {“a”: true, “b”: true}
- expression: message
- value: hello! (如果指令需要多个值,可以传入一个 JavaScript 对象字面量,
v-demo="{ color: 'white', text: 'hello!'}"
)
vnode
oldVnode
自定义指令案例
input输入自动转大写
1 | <Input v-uppercase v-model="form.orderNo" placeholder="编号" clearable></Input> |
多环境配置用户权限
- permission.js
1 | // v-permission="['admin','editor']" |
API说明
Vue.use(demo, opts)
注册插件- 自定义插件参考开发组件库
- 如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入
实际是调用了demo的install(Vue, opts)方法,相当于传入了Vue对象到demo中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// config.js
import Vue from 'vue'
let config = {
name: 'hello world'
}
export default {
install(Vue) {
// 之后在组件中可使用this.$config
Vue.prototype.$config = config
}
}
/*
// 等同于
const install = Vue => {
Vue.prototype.$config = config
}
export default { install }
*/
// main.js
import config from './config/index.js'
Vue.use(config)
Vue.extend(options)
使用基础 Vue 构造器,创建一个子类. Vue.component 是基于此函数的- 创建异步组件可参考:https://juejin.cn/post/6890072682864476168
- 参考report-table#report-modal
Vue.component(name, component)
通过js手动注册全局组件,此时无需在components属性中定义。如果在main.js执行了此函数,则全局.vue文件均可使用此主键。使用是可使用name或其下划线形式名称- Vue.component注册全局组件时,内部会调用
Vue.extend
方法,将定义挂载到Vue.options.components上 components: { Demo }
components属性是用于注册局部组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// {string} id
// {Function | Object} [definition]
Vue.component('demo', {
// render: function (createElement) {},
template: `
<div class="demo">
hello world
</div>
`
})
// 基于函数(动态组件)
Vue.component('demo',
() => ({
component: import('@/view/components/demo.vue'),
error: MyErrorComp
})
)
// 全局异步组件
Vue.component('demo', resolve => {
// require会告诉webpack将构建的代码分割成多个包,这些包通过Ajax请求
require(['./Demo'], resolve)
})
- Vue.component注册全局组件时,内部会调用
Vue.mixin({...})
全局混入Vue.directive
1
2
3
4
5
6
7
8
9
10
11
12// 注册
Vue.directive('my-directive', {
bind: function () {},
inserted: function () {},
update: function () {},
componentUpdated: function () {},
unbind: function () {}
})
// 或指令函数
Vue.directive('my-directive', function () {
// 这里将会被 `bind` 和 `update` 调用
})
开发组件库
说明
当基于element-ui等进行二次开发时
- 在案例入口文件中可以引入element-ui(案例不打包到库文件中),可将element-ui加到dependences和externals(对应public/index.html需引入element-ui的CDN文件)中,这样打包案例时体积会减少
- 在组件库模板中可以使用element-ui的标签,组件库无需在入口js中导入element-ui进行use(前提是主应用全局use安装了element-ui插件)
如果在库入口js文件及引申文件中引入了vue/element-ui,则不管包依赖是在dependences还是devDependences,都会将element-ui打包到组件库的输出文件中(如果只是使用element-ui的标签则不会)
- 在产物中搜索
ElInput
或el-input
如果有则说明插件库中包含了element-ui包 如果此时将vue/element-ui设置成了externals,则将此插件在另外一个vue项目中引用时会出现插件模块缺少Vue对象。可动态判断是否需要设置成externals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const IS_PROD = process.env.NODE_ENV === 'production'
// script中增加`"lib": "vue-cli-service build --target lib ......"`
// 此时通过 npm run lib 进行打包库文件,则 process.env.npm_config_argv(original) 的值为['run', 'lib']
const IS_BUILD = JSON.parse(process.env.npm_config_argv).original.filter(x => x === 'build').length > 0
module.exports = {
configureWebpack(config) {
if (IS_PROD && IS_BUILD) {
config.externals = {
vue: 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT'
}
}
},
}
- 在产物中搜索
如果一定需要使用vue/element-ui的方法,可在插件的install方法(Vue插件安装的入口方法)中接收外部应用(主应用)传入进来的vue/element-ui对象并缓存到全局属性中(如在插件内部调用Vue.component动态注册组件,此时必须使用同一个Vue对象,因此必须接收外部传入进来的,参考下文组件库中使用动态组件)
- 异步加载插件
- 使用 externals
- vue2异步加载插件/组件/指令等 vue2自带异步加载组件
- vue3支持异步加载插件
- 案例
- https://gitee.com/sqbiz/wplugin-variant-form 基于vue-cli打包
- https://github.com/sscfaith/avue-form-design 基于vue-cli打包(packages文件为实际包, src为案例)
- https://gitee.com/smallweigit/avue-plugin-ueditor 无需打包(直接将packages目录下文件暴露成包)
打包与导入
打包方式
通过vue-cli打包
参考: https://blog.csdn.net/qq_41887214/article/details/120619211
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30{
// 导入模块时的入口函数(如果需要调试可去掉min,这样主应用导入后可直接在浏览器打断点。修改package.json后需要重启主应用)
// 如果导出了多个模块,可以在引入的时候写全路径,如`import ReportTable from 'report-table'`引入默认模块,而通过类似`import ReportTableDemo from 'report-table/lib-demo/report-table-demo.umd.min.js`引入另外一个模块
"main": "./lib/report-table.umd.min.js",
// 如果是scope类型模块(name以@xxx/开头),则npm默认为发布私有包,如果需要发布成公开则需要定义下面配置
"publishConfig": {
"access": "public"
},
// 上传到npm仓库的文件夹
"files": [
"lib",
"types"
],
"typings": "types/index.d.ts",
"scripts": {
// 参考: https://gitee.com/gitee-frontend/
// 实时监控打包(修改代码编译较快,可实时反映到主应用,且调试时显示的是源码;可增加如`lib/*.hot-update.*`让git忽略热更新产生的文件)
// 其他如npm link引用本地模块的方法参考:https://blog.csdn.net/zhangxin09/article/details/119344515
// 配合 npm link(通过本地路径直接安装模块即可,偶尔还是需要npm link) 就可以做本地调试了。(1)现在模块目录执行<sudo> npm link将当前模块关联到全局 (2) 在到项目目录执行`npm link my-module`关联模块到项目中(执行后会将本地开发包关联到node_modules中;如果项目目录中配置的是远程包,当重新npm i就会重新下载远程包,即npm link失效)
// 注意:--watch模式下,打包的lib中不会出现.css文件(样式和图片等资源无法实时监控),因为css样式已经内联了,可通过在模块的 vue.config.js 中设置 css: { extract: true } 取消内联
// vue-cli-service build只会将组件中的样式进行打包到单独的文件或放在js内联中;无法将额外的less等样式进行打包,如需要可在主入口引入一下(如果时间开发需要额外的less,可将style目录放到打包产物中,主应用直接应用less文件也可以)
"start": "vue-cli-service build --target lib --name report-table --dest lib ./src/index.js --watch",
// 打包命令,打出来的包在lib文件夹中
// --formats umd-min # 产物包类型,默认包含common.js、.umd.js、.umd.min.js,此时表示只打包umd.min
"lib": "vue-cli-service build --target lib --name report-table --dest lib --formats umd-min ./src/index.js",
"lint": "vue-cli-service lint",
// 分析的是以 vue.config.js 中的 pages.index.entry 为入口
"report": "vue-cli-service build --report --mode prod"
}
}
通过rolljs打包
- 通过webpack打包
- 主应用导入本地环境组件库
- 参考:https://qastack.cn/programming/8088795/installing-a-local-module-using-npm
npm install /path/to/component/project/dir
此时会在package.json中创建对应的依赖,值为file:..
的相对路径- 模块更新不会直接热部署到应用,必须重新build
npm link
(或对应的yarn link
同理使用)- 在模块目录执行
npm link
会将当前模块链接到全局模块中 - 在应用目录执行
npm link package-name
引用该模块 - 也可直接使用相对/绝对路径,相当于上面两步
- 此方式不会在package.json中增加依赖
- 模块更新不会直接热部署到应用,必须重新build
- 在模块目录执行
- 解决使用 npm link 时, eslint 提示对应包存在错误的问题
- 在vue.coonfig.js的configureWebpack属性中加
resolve: { symlinks: false }
,参考: https://stackoverflow.com/questions/48410203/webpack-gives-eslint-errors-while-using-npm-link
- 在vue.coonfig.js的configureWebpack属性中加
- 主应用导入远程环境组件库(均需先上传包)
- 通过github安装,参考基于git仓库进行安装
- 通过npm镜像安装
组件库中使用动态组件
- 把主应用的组件通过在组件库中import/require动态引入,会出现找不到组件文件;且通过Vue.component注册时并没有注册到主应用Vue对象中,而是注册到组件的Vue对象中
- 解决方法:在通过Vue.use组件库的时候,将主应用的Vue构造函数传递到组件库中,并且把需要动态引入主应用组件添加到Vue原型中(当然也可以注册成全局Vue组件)
- 组件库主要代码
1 | // =============> 组件库入口函数 |
- 主应用主要代码
1 | // main.js |
相关问题
部分插件引用的模块用到了PostCSS,当
npm link
后,在主项目中启动后报错Error: No PostCSS Config found
在当前项目根目录下创建
postcss.config.js
,并加入配置1
2
3
4
5
6
7module.exports = {
plugins: {
autoprefixer: {
browsers: 'last 5 version'
}
}
}
JSX使用
- 类似的可参考render函数)
vue的jsx语法是基于babel-plugin-transform-vue-jsx插件实现的 ^7
- 使用vue-cli3则不需要手动安装下述babel插件即可使用(如果重复安装会报错Duplicate declaration “h”)。其他方式需要手动安装
1 | # 非vue-cli3需要手动安装 |
- 使用 ^6
- babel插件会通过正则匹配的方式在编译阶段将书写在组件上属性进行分类
- onXXX的均被认为是事件,nativeOnXXX是原生事件,domPropsXXX是Dom属性,class、staticClass、style、key、ref、refInFor、slot、scopedSlots这些被认为是顶级属性,至于组件声明的props,以及html属性attrs,不需要加前缀,插件会将其统一分类到attrs属性下,然后在运行阶段根据是否在props声明来决定属性归属
- 不建议声明onXXX的属性
- 对于原生指令,只有v-show是支持的。v-if可用(&& 或 ?:)代替;v-for可用array.map代替;v-model使用事件触发;自定义指令使用…解构
<el-input value={this.value} onInput={$event => this.value = $event} {...{ directives }}></el-input>
(其中let directives = [{ name: 'rt-permission', value: ['manager'] }]
)
- 对于事件
- 使用
on-[eventName]
格式, 比如 on-on-change, on-click, on-camelCaseEvent - 使用
on[eventName]
格式,比如 onClick, onCamelCaseEvent。click-two 需要这样写 onClick-two,onClickTwo 是不对的 - 使用 spread 语法,即
{...{on: {event: handlerFunction}}}
- 使用
- 对于样式
- 如果组件中使用了scoped,则对该组件的JSX标签中的css类进行样式编写时必须添加
/deep/
等作用于穿透标识
- 如果组件中使用了scoped,则对该组件的JSX标签中的css类进行样式编写时必须添加
- babel插件会通过正则匹配的方式在编译阶段将书写在组件上属性进行分类
- 示例
1 | // v-if使用 && 代替,v-if 和 v-else 使用 ?: 代替 |
render函数
1 | Vue.component('anchored-heading', { |
- render 语法(iview案例)
1 | // 语法 |
- vue的data对象对应属性
1 | { |
- iview示例:此时Poptip和Tag都是Vue对象,因此要设置参数props
1 | // 调用组件并监听事件 |
- 报错 You may have an infinite update loop in a component render function
- 参考:https://www.itread01.com/content/1541599683.html
render method is triggered whenever any state changes
vue组件中任何属性改变致使render函数重新执行。如果在模板中直接修改vue属性或调用的方法中修改了属性(如双括号中,而@click等事件中是可以修改vue属性的),就会导致重新render。从而产生render - 属性改变 - render无限循环
Vue对Typescript的支持
VueRouter路由
基本概念
- 路由生命周期见上文
query
和params
的区别- query参数会拼接到url上面,param不会
- params的参数值可以赋值给路由路径中的动态参数。因此使用params时路由跳转后再次刷新页面会导致参数丢失($router.go也会丢失params参数)
1 | this.$router.push({ |
- redirect
1 | { |
$route
和$router
区别$route
为当前路由,为vue内置$router
为路由管理器(全局的),一般在main.js中new Vue()时挂载
1 | export default { |
- push和replace的区别
- 编程式
router.push(...)
和声明式<router-link :to="...">
类似,都是往 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL router.replace(...)
和<router-link :to="..." replace>
类似,它们不会向 history 添加新记录,而是替换掉当前的 history 记录router.go(n)
类似window.history.go(n)
- 编程式
- 动态路由、嵌套路由、
<router-view />
- 实现嵌套路由有两个要点:在组件内部使用
<router-view />
标签、VueRouter 的参数中使用 children 配置 - 嵌套路由(https://router.vuejs.org/zh/guide/essentials/nested-routes.html)
- 实现嵌套路由有两个要点:在组件内部使用
1 | <div id="app"> |
- 路由懒加载(当打包构建应用时,Javascript 包会变得非常大,影响页面加载速度)
router.addRoute
动态添加路由,如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它
路由变化页面数据不刷新
- 参考上文【父子组件加载】中的示例,监控
$route
变化
- 当使用路由参数时,例如参数
/user/:username
,且从 /user/foo 导航到 /user/bar,原来的组件实例会被复用- 因为两个路由都渲染同个组件(User),比起销毁再创建,复用则显得更加高效
- 不过这也意味着组件的生命周期钩子(如created)不会再被调用
- 但是两个路径显示的data数据是缓存两份,不会覆盖
1 | const router = new VueRouter({ |
hash和history路由模式
- 为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于改变视图的同时不会向后端发出请求。为了达到这种目的,浏览器当前提供了hash和history两种支持模式,Vue-Router是基于此两者特性完成 ^4
hash
:即地址栏 URL 中的#
符号(此 hash 不是密码学里的散列运算)。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面history
:利用了 HTML5 History Interface 中新增的pushState()
和replaceState()
方法。支持的浏览器(IE=10)。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求
- Vue-Router使用对比
- hash模式
- 前进、后退、刷新均不会请求后端,仅刷新路由
- 缺点:Url中带有
#
号,nginx同域名多项目配置不支持 - router.beforeEach 中执行 next({..from}) 跳转后,不会执行afterEach();而history模式会在执行一次beforeEach后执行afterEach。参考 https://www.okcode.net/article/70038
- history模式
- 前进、后退不会请求后端,但是刷新、f5会请求后端(nginx),如果后端无浏览器地址栏中的路径则会404
- 缺点:需要浏览器支持(IE=10),刷新可能会出404
- 两种模式对于router.push、replace、go的效果一致
- hash模式
- history模式使用(https://router.vuejs.org/zh/guide/essentials/history-mode.html)
1 | // 开启history模式 |
刷新页面
1 | <!-- 添加组件src/views/redirect.vue --> |
打开新页面
1 | // 可结合下文vue-contextmenujs插件实现右键打开新标签 |
子模块路由案例(iview-admin为例)
- 隐藏菜单
1 | // 路由 |
- 子组件路由(嵌套路由,参考上文)
1 | // 路由 |
数据库动态路由
- 数据库以路由格式存储json
- 前端初始化时,默认路由设置成空,或者添加几个静态路由(如403/404等页面)
- 用户登录后,通过
addRoutes
动态将路由项添加到路由对象中,并存储路由数组到Vuex防止重复添加 - 用户退出登录后,重置路由对象(重新new一个路由对象)
Vuex
- Vuex
- 刷新浏览器地址会导致vuex的state状态丢失,一般是默认给state的相应属性赋值Cookie的值或者在使用的地方通过Cookie重新获
1 | import Cookies from 'js-cookie' |
Vuex的使用场景
- 采用单向数据流的模式,子组件修改数据必须通过事件触发回调。如果当组件的层级越来越深,会造成父组件可能会与很远的组件之间共享同份数据,如果此时很远的组件需要修改数据时,就会造成事件回调需要层层返回。因此可通过Vuex这个状态管理库来统一维护 ^8
- 实践:父组件负责渲染Store的数据状态(初始化)且通过computed监听状态变化,然后通过props传递数据到子组件中,子组件触发事件提交更改状态的action, Store可以在Dispatcher上监听到Action并做出相应的操作,当数据模型发生变化时,就触发刷新整个父组件界面
利用computed的特性,用get和set来获取和设值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19computed: {
message: {
get () {
return this.$store.state.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
},
// 对于一些需要动态转换的属性较为实用
curList: {
get () {
return this[`${this.searchForm.dataType}List`]
},
set (newValue) {
this[`${this.searchForm.dataType}List`] = newValue
}
},
}持久化工具[vuex-persistedstate]。可将store数据写入到localStorage中
扁平化 Store 数据结构
- 可基于JSON数据规范化(normalize),使用如 Normalizr 等开源的工具,可以将深层嵌套的 JSON 对象通过定义好的 schema 转变成使用 id 作为字典的实体表示的对象
分离渲染UI数据量大的属性,以免其他不必要的状态改变而影响它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 假设某组件A会通过商铺名获取商铺信息。如果A商铺loading状态变更,则state1.shops属性变化,这时Getter监听到变化后,会通知绑定的组件(商铺A,商铺B,...),然后UI响应变化。像fruits本没有变化也会触发水果组件重新渲染,而其数据量大会导致性能变差
const state1 = {
shops: {
// 对应商铺组件
商铺A: {
startDate: "2018-11-01",
endDate: "2018-11-30",
loading: false,
diplayMoreFruitsLink: true,
fruits: [{},{},{}...], // 水果,对应水果组件
},
商铺B:{...},
...
}
}
// 改进后。此时商铺信息变化并不会影响"商铺A_fruits"属性变化,水果组件不会重新渲染
const state2 = {
shops: {
商铺A: {
startDate: "2018-11-01",
endDate: "2018-11-30",
loading: false,
diplayMoreFruitsLink: true,
fruits: [{},{},{}...], // 水果
},
商铺B:{...},
...
},
商铺A_fruits: [{},{},{}...],
商铺B_fruits: [{},{},{}...],
...
}
vue-cli
- 官网
- 安装
1 | # Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上) |
vue-cli-service
- 命令说明
1 | # 查看帮助 |
- package.json 常用配置
1 | { |
.evn
.env.test-sq
(多)环境变量配置。具体参考:https://cli.vuejs.org/zh/guide/mode-and-env.html
1 | ## 获取的值都是字符串类型 |
vue.config.js
1 | // process.env 可以获取node下所有的环境变量 |
Vuepress文档框架
Vuepress 官方推出的文档框架
- demo
多版本部署
1
2
3
4
5
6
7// # .vuepress/config.js
var json = require('../../package.json')
module.exports = {
// base: '/',
base: '/dist.' + json.version + '/'
}nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15server {
listen 5200;
server_name localhost;
index index.html index.htm;
# http://localhost:5200/
location = / {
rewrite . http://$server_name/dist.0.5.2-beta.2/ break;
}
# http://localhost:5200/dist.0.5.2-beta.2/
location ~ ^/dist(.+?)/ {
root /www/demo/;
}
}
可结合vuese 先对vue组件进行解析成markdown
- 其他框架
- Docsify/Docute
- 这两个项目同样都是基于 Vue,然而它们都是完全的运行时驱动,因此对 SEO 不够友好
- Hexo
- Vue 的文档一直使用此框架,Hexo 最大的问题在于他的主题系统太过于静态以及过度地依赖纯字符串,二vuepress可以使用vue来处理布局和交互
- GitBook
- GitBook 最大的问题在于当文件很多时,每次编辑后的重新加载时间长得令人无法忍受
- docz
- docsite 阿里开源(React), 对SEO友好
- Docsify/Docute
- 静态站点
- GatsbyJS: 基于React
- Next.js: 基于React的
- Nuxt.js: 基于Vue.js, 相比Vuepress更偏Web应用
- Gridsome: 基于Vue.js, 支持GraphQL
- docusaurus
插件收集
- 自定义右键菜单 vue-contextmenujs 包大小130K
- 可以在指定元素上开启自定义右键菜单
- 栅格布局 Vue Grid Layout
- 如可用于首页自定义栅格
参考文章