极简安装&启动
安装 vue-cli
npm install -g @vue/cli // 安装cli3.x
vue --version // 查询版本是否为3.x
新建
vue create hello-cli3
零配置启动/打包
npm install -g @vue/cli-service-global
vue serve App.vue // 启动服务
vue build App.vue // 打包出生产环境的包并用来部署
启动图形化界面
vue ui
技巧
1. slot
vue.runtime
搜索renderSlot
<!-- 组件 -->
<template>
<!-- 新建了一个slot,叫header -->
<div
v-if="$slots.header"
class="c-base-popup-header"
>
<slot name="header" />
</div>
</template>
...
<!-- 使用 -->
<template>
<comp>
<!-- 用法1 -->
<template v-slot:header>
...
</template>
<!-- 用法2 -->
<div slot="header">多行信息<br/>第二行信息</div>
<!-- 用法3 -->
<!-- #代替v-slot -->
<template #header>
...
</template>
</comp>
</template>
2. slot-scope
<!-- 组件 -->
<template>
<!-- 新建了一个slot,叫header -->
<div
v-if="$slots.header"
class="c-base-popup-header"
>
<!-- slot用v-bind绑定插槽数据,被替换后,可以直接读取这个值 -->
<slot v-bind="aa" />
<slot name="value" v-bind="{ value: 123 }" />
</div>
</template>
...
<!-- 使用 -->
<template>
<CustomTable :data="tableData">
<!-- 替换父组件中的默认<slot /> -->
<template v-slot="slotProp">
{{ slotProp.user.firstName }}
</template>
<!-- 替换父组件中的<slot name="value"/>,并且获取当前数据,命名为row -->
<template v-slot:value="row">
<!-- 由于slot绑定了{ value: 123},所以这里可以直接操作属性value -->
{{ row.value.toFixed(3) }}
</template>
</CustomTable>
</template>
3. keep-alive
- vue 内置抽象组件(父子关系会跳过该组件)
- 根据LRU策略缓存第一个子组件实例,下次render时,根据组件 id 从缓存中拿实例(如果存在的话)
注:如果没处理好,比如之前的组件实例没销毁,将会是内存泄漏的源头
<keep-alive>
<div>...其他组件</div>
</keep-alive>
4. 优雅绑定hook
{
mounted() {
window.addEventListener('resize', this.$_handleResizeChart)
// 通过hook监听组件销毁钩子函数,并取消监听事件
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', this.$_handleResizeChart)
})
},
}
5. 覆盖外部组件scoped
样式
<style lang="less" scoped>
.aa {
/deep/ .el-alert__title {
}
}
</style>
6. 解耦操作组件生命周期
<template>
<v-chart
@hook:mounted="handleChartMounted"
@hook:beforeUpdated="loading = true"
@hook:updated="loading = false"
:data="data"
/>
</template>
<script>
export default {
data() {
return {
loading: false,
};
},
handleChartMounted() {
// do sth
},
}
</script>
7. $props
、$attrs
和$listeners
<template>
<!-- 传递父组件所有动态属性给子组件 -->
<input v-bind="$props" />
<!-- 传递父组件所有静态属性给子组件 -->
<input v-bind="$attrs" />
<!-- 将父组件 (不含 .native修饰器)的 v-on 事件监听器给子组件 -->
<childComponent v-on="$listeners" />
</template>
8. 动态添加子组件
<template>
<!-- 利用内置component组件,记得绑定 is 属性 -->
<component v-for="(item,index) in componentList" :key="index" :is="item"></component>
</template>
<script>
import ColorIn from '@/components/Magic/ColorIn.vue'
import LineIn from "@/components/Magic/LineIn.vue";
import Header from "@/components/Magic/Header.vue";
import Footer from "@/components/Magic/Footer.vue";
export default{
data() {
return {
componentList: ['ColorIn', 'LineIn', 'Header', 'Footer'],
};
},
components:{
ColorIn,
LineIn,
Header,
Footer
}
}
</script>
9. Vue.filter
注册
// 方式一:全局注册
Vue.filter('stampToYYMMDD', (value) =>{
// 处理逻辑
});
// 方式二:全局注册多个filter
// ./common/filters/custom
const dateServer = value => value.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3')
export { dateServer }
import * as custom from './common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]));
// 方式三:局部注册
export default {
filters: {
stampToYYMMDD: (value)=> {
// 处理逻辑
}
}
}
使用
<template>
<!-- 在双花括号中 -->
{{ message | stampToYYMMDD }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | dateServer"></div>
</template>
10. .sync
- 子组件可修改props
父组件
<template>
<!-- 用sync定义属性 -->
<child :foo.sync="foo" />
</template>
<script>
export default {
data() {
return {
foo: 'aaa',
};
},
}
</script>
子组件
<script>
//...
{
// ...
// 通过事件更新,类似v-model
this.$emit('update:foo', newValue);
}
</script>
11. Object.freeze
- 性能提升
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
vue3
compiler
1. 提取动态 vnode
vue3 有
Block Tree
和PatchFlag
的概念
Block
相比普通VNode,多了dynamicChildren
// Block仅包含最近子代的vnode
const block = {
tag: Fragment,
dynamicChildren: [
{
tag: 'p',
children: item,
// patchFlag用于标志静态节点还是动态节点,
// 动态节点又分:1. 稳定动态节点 2. 不稳定动态节点
// 稳定动态节点用优化后的算法,即提取dynamicChildren做更新
// 不稳定动态节点还是使用之前的diff算法
patchFlag: 1,
}
],
}
PatchFlag 有多种:
- STABLE_FRAGMENT
- PROPS(还能指定 props 中的具体 key)
- CLASS
- STYLE
- TEXT
- FULL_PROPS(当props的key为动态值)
- NEED_PATCH(仅需更新ref属性)
- UNKEYED_FRAGMENT(没key的fragment)
- KEYED_FRAGMENT(有key的fragment)
- DYNAMIC_SLOTS(动态构建
slots
时用到)
2. 静态提升
包含 vnode 提升和 props 提升
好处是可以避免多次 diff,重复创建静态 vnode(props)
// 静态vnode提升
const hoist1 = createVNode('p', null, 'text')
function render() {
return (openBlock(), createBlock('div', null, [
hoist1
]))
}
// 静态props提升
const hoistProp = { foo: 'bar', a: 'b' }
render(ctx) {
return (openBlock(), createBlock('div', null, [
createVNode('p', hoistProp, ctx.text)
]))
}
哪些情况不会被静态提升?
- 元素带有动态的 key 绑定(key 不同即使别的属性相同,做的也是完全替换,用不到 diff)
- 使用 ref 的元素(创建新元素当然要更新 ref,总不能指向老元素吧)
- 使用自定义指令的元素
- 动态属性,且非常量(也就是说仅包含常量的动态属性也会被静态提升)
3. 预字符串化
大量连续的类似的vnode,与其做静态提升,不如预字符串化。
相对于静态提升:
- 节省内存
- 减少代码
示例
<div>
<p></p>
<p></p>
...20 个 p 标签
<p></p>
</div>
如果用静态提升
const hoist1 = createVNode('p', null, 'text')
const hoist2 = createVNode('p', null, 'text')
// ...
const hoist20 = createVNode('p', null, 'text')
如果用预字符串化
const hoistStatic = createStaticVNode('<p></p>...20个');
哪些不会预字符串化?
- 格类标签:caption 、thead、tr、th、tbody、td、tfoot、colgroup、col
- 含非标准html属性,不包括aria-
- 连续静态节点(含子节点)少于20个
- 连续含属性的静态节点(含子节点)少于5个
4. Cache Event handler
绑定的事件做缓存处理
示例
<Comp @change="a + b" />
默认编译为
render(ctx) {
return h(Comp, {
onChange: () => (ctx.a + ctx.b)
})
}
开启prefixIdentifiers
和cacheHandlers
后
render(ctx, cache) {
return h(Comp, {
onChange: cache[0] || (cache[0] = ($event) => ctx.a + ctx.b)
})
}
一些尝试
组件
vue-code-diff
类git风格代码对比,参考
使用到的库
<div v-html="html" v-highlight></div>
// ..
import { createPatch } from 'diff';
import { Diff2Html } from 'diff2html';
import hljs from 'highlight.js';
import 'highlight.js/styles/googlecode.css';
import 'diff2html/dist/diff2html.css';
{
// ...
computed: {
html() {
const oldString = 'hello world';
const newString = 'hello world2';
const args = ['', oldString, newString, '', ''];
// 这里获取了git diff 的结果
const dd = createPatch(...args);
// git diff 结果转 json
const outStr = Diff2Html.getJsonFromDiff(dd, {
inputFormat: 'diff',
outputFormat: 'side-by-side',
showFiles: false,
matching: 'lines',
});
// json 转 html
const html = Diff2Html.getPrettyHtml(outStr, {
inputFormat: 'json',
outputFormat: 'side-by-side',
showFiles: false,
matching: 'lines',
});
return html;
},
},
// ...
}
tiptap
轻量级页面编辑器
<template>
<div>
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
<button :class="{ 'is-active': isActive.bold() }" @click="commands.bold">
Bold
</button>
</editor-menu-bar>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
} from 'tiptap-extensions'
export default {
components: {
EditorMenuBar,
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
// ...按需注入扩展
],
content: `
<h1>Yay Headlines!</h1>
<p>All these <strong>cool tags</strong> are working now.</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>