技巧 vue还能这样写

极简安装&启动

安装 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

  1. vue 内置抽象组件(父子关系会跳过该组件)
  2. 根据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 TreePatchFlag 的概念

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,与其做静态提升,不如预字符串化。

相对于静态提升:

  1. 节省内存
  2. 减少代码

示例

<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)
    })
}

开启prefixIdentifierscacheHandlers

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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值