百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)

cac55 2024-10-26 08:14 7 浏览 0 评论

作者:HcySunYang

转发链接:https://mp.weixin.qq.com/s/cbLm56UcoV6DQI2jBMM8YQ

目录

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)

教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)本篇

预字符串化

静态提升的 VNode 节点或节点树本身是静态的,那么能否将其预先字符串化呢?如下模板所示:

<div>
    <p></p>
    <p></p>
    ...20 个 p 标签
    <p></p></div>

假设如上模板中有大量连续的静态的 p 标签,当采用了 hoist 优化时,结果如下:

cosnt hoist1 = createVNode('p', null, null, PatchFlags.HOISTED)
cosnt hoist2 = createVNode('p', null, null, PatchFlags.HOISTED)... 20 个 hoistx 变量
cosnt hoist20 = createVNode('p', null, null, PatchFlags.HOISTED)

render() {
    return (openBlock(), createBlock('div', null, [
        hoist1, hoist2, ...20 个变量, hoist20
    ]))}

预字符串化会将这些静态节点序列化为字符串并生成一个 Static 类型的 VNode:

const hoistStatic = createStaticVNode('<p></p><p></p><p></p>...20个...<p></p>')

render() {
    return (openBlock(), createBlock('div', null, [
       hoistStatic
    ]))}

这有几个明显的优势:

  • 生成代码的体积减少
  • 减少创建 VNode 的开销
  • 减少内存占用

静态节点在运行时会通过 innerHTML 来创建真实节点,因此并非所有静态节点都是可以预字符串化的,可以预字符串化的静态节点需要满足以下条件:

  • 非表格类标签:caption 、thead、tr、th、tbody、td、tfoot、colgroup、col
  • 标签的属性必须是:
  • 标准 HTML attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
  • 或 data-/aria- 类属性

当一个节点满足这些条件时代表这个节点是可以预字符串化的,但是如果只有一个节点,那么并不会将其字符串化,可字符串化的节点必须连续且达到一定数量才行:

  • 如果节点没有属性,那么必须有连续 20 个及以上的静态节点存在才行,例如:<div><p></p><p></p>… 20 个 p 标签<p></p></div>

或者在这些连续的节点中有 5 个及以上的节点是有属性绑定的节点:

<div>
    <p id="a"></p>
    <p id="b"></p>
    <p id="c"></p>
    <p id="d"></p>
    <p id="e"></p></div>

这段节点的数量虽然没有达到 20 个,但是满足 5 个节点有属性绑定。

这些节点不一定是兄弟关系,父子关系也是可以的,只要阈值满足条件即可,例如:

<div>
    <p id="a">
        <p id="b">
            <p id="c">
                <p id="d">
                    <p id="e"></p>
                </p>
            </p>
        </p>
    </p></div>

预字符串化会在编译时计算属性的值,例如:

<div>
    <p :id="'id-' + 1">
        <p :id="'id-' + 2">
            <p :id="'id-' + 3">
                <p :id="'id-' + 4">
                    <p :id="'id-' + 5"></p>
                </p>
            </p>
        </p>
    </p></div>

在与字符串化之后:

const hoistStatic = createStaticVNode('<p id="id-1"></p><p id="id-2"></p>.....<p id="id-5"></p>')

可见 id 属性值是计算后的。

Cache Event handler

如下组件的模板所示:

<Comp @change="a + b" />

这段模板如果手写渲染函数的话相当于:

render(ctx) {
    return h(Comp, {
        onChange: () => (ctx.a + ctx.b)
    })}

很显然,每次 render 函数执行的时候,Comp 组件的 props 都是新的对象,onChange 也会是全新的函数。这会导致触发 Comp 组件的更新。

当 Vue3 Compiler 开启 prefixIdentifiers 以及 cacheHandlers 时,这段模板会被编译为:

render(ctx, cache) {
    return h(Comp, {
        onChange: cache[0] || (cache[0] = ($event) => (ctx.a + ctx.b))
    })}

这样即使多次调用渲染函数也不会触发 Comp 组件的更新,因为 Vue 在 patch 阶段比对 props 时就会发现 onChange 的引用没变。

如上代码中 render 函数的 cache 对象是 Vue 内部在调用渲染函数时注入的一个数组,像下面这种:

render.call(ctx, ctx, [])

实际上,我们即使不依赖编译也能手写出具备 cache 能力的代码:

const Comp = {
    setup() {
        // 在 setup 中定义 handler
        const handleChange = () => {/* ... */}
        return () => {
            return h(AnthorComp, {
                onChange: handleChange  // 引用不变
            })
        }
    }}

因此我们最好不要写出如下这样的代码:

const Comp = {
    setup() {
        return () => {
            return h(AnthorComp, {
                onChang(){/*...*/}  // 每次渲染函数执行,都是全新的函数
            })
        }
    }}

v-once

这是 Vue2 就支持的功能,v-once 是一个“很指令”的指令,因为它就是给编译器看的,当编译器遇到 v-once 时,会利用我们刚刚讲过的 cache 来缓存全部或者一部分渲染函数的执行结果,例如如下模板:

<div>    <div v-once>{{ foo }}</div></div>

会被编译为:

render(ctx, cache) {
    return (openBlock(), createBlock('div', null, [
        cache[1] || (cache[1] = h("div", null, ctx.foo, 1 /* TEXT */))
    ]))}

这样就缓存了这段 vnode。既然 vnode 已经被缓存了,后续的更新就都会读取缓存的内容,而不会重新创建 vnode 对象了,同时在 Diff 的过程中也就不需要这段 vnode 参与了,因此你通常会看到编译后的代码更接近如下内容:

render(ctx, cache) {
    return (openBlock(), createBlock('div', null, [
        cache[1] || (
            setBlockTracking(-1), // 阻止这段 VNode 被 Block 收集
            cache[1] = h("div", null, ctx.foo, 1 /* TEXT */),
            setBlockTracking(1), // 恢复
            cache[1] // 整个表达式的值
        )
    ]))}

稍微解释一下这段代码,我们已经讲解过何为 “Block Tree”,而 openBlock() 和 createBlock() 函数用来创建一个 Block。而 setBlockTracking(-1) 则用来暂停收集的动作,所以在 v-once 编译生成的代码中你会看到它,这样使用 v-once 包裹的内容就不会被收集到父 Block 中,也就不参与 Diff 了。

所以,v-once 带来的性能提升来自两方面:

  • 1、VNode 的创建开销
  • 2、无用的 Diff 开销

但其实我们不通过模板编译,一样可以通过缓存 VNode 来减少 VNode 的创建开销:

const Comp = {
    setup() {
        // 缓存 content
        const content = h('div', 'xxxx')
        return () => {
            return h('section', content)
        }
    }}

但这样避免不了无用的 Diff 开销,因为我们没有使用 Block Tree 优化模式。

这里有必要提及的一点是:在 Vue2.5.18+ 以及 Vue3 中 VNode 是可重用的,例如我们可以在不同的地方多次使用同一个 VNode 节点:

const Comp = {
    setup() {
        const content = h('div', 'xxxx')
        return () => {
            // 多次渲染 content
            return h('section', [content, content, content])
        }
    }}

手写高性能渲染函数

接下来我们将进入重头戏环节,我们尝试手写优化模式的渲染函数。

几个需要记住的小点:

  1. 一个 Block 就是一个特殊的 VNode,可以理解为它只是比普通 VNode 多了一个 dynamicChildren 属性
  2. createBlock() 函数和 createVNode() 函数的调用签名几乎相同,实际上 createBlock() 函数内部就是封装了 createVNode(),这再次证明 Block 就是 VNode。
  3. 在调用 createBlock() 创建 Block 前要先调用 openBlock() 函数,通常这两个函数配合逗号运算符一同出现:render() {return (openBlock(), createBlock('div'))}

Block Tree 是灵活的:

在之前的介绍中根节点以 Block 的角色存在的,但是根节点并不必须是 Block,我们可以在任意节点开启 Block:

setup() {
    return () => {
        return h('div', [
            (openBlock(), createBlock('p', null, [/*...*/]))
        ])
    }}

这也是可以的,因为渲染器在 Diff 的过程中如果 VNode 带有 dynamicChildren 属性,会自动进入优化模式。但是我们通常会让根节点充当 Block 角色。

正确地使用 PatchFlags:

PatchFlags 用来标记一个元素需要更新的内容,例如当元素有动态的 class绑定时,我们需要使用 PatchFlags.CLASS 标记:

const App = {
  setup() {
    const refOk = ref(true)

    return () => {
      return (openBlock(), createBlock('div', null, [
        createVNode('p', { class: { foo: refOk.value } }, 'hello', PatchFlags.CLASS) // 使用 CLASS 标记
      ]))
    }
  }}

如果使用了错误的标记则可能导致更新失败,下面列出详细的标记使用方式:

  • PatchFlags.CLASS - 当有动态的 class 绑定时使用
  • PatchFlags.STYLE - 当有动态的 style 绑定时使用,例如:createVNode(‘p’, { style: { color: refColor.value } }, ‘hello’, PatchFlags.STYLE)
  • PatchFlags.TEXT - 当有动态的文本节点是使用,例如:createVNode(‘p’, null, refText.value, PatchFlags.TEXT)
  • PatchFlags.PROPS - 当有除了 class 和 style 之外的其他动态绑定属性时,例如:createVNode(‘p’, { foo: refVal.value }, ‘hello’, PatchFlags.PROPS, [‘foo’])

这里需要注意的是,除了要使用 PatchFlags.PROPS 之外,还要提供第五个参数,一个数组,包含了动态属性的名字。

  • PatchFlags.FULL_PROPS - 当有动态 name 的 props 时使用,例如:createVNode(‘p’, { [refKey.value]: ‘val’ }, ‘hello’, PatchFlags.FULL_PROPS)

实际上使用 FULL_PROPS 等价于对 props 的 Diff 与传统 Diff 一样。其实,如果觉得心智负担大,我们大可以全部使用 FULL_PROPS,这么做的好处是:

  • 避免误用 PatchFlags 导致的 bug
  • 减少心智负担的同时,虽然失去了 props diff 的性能优势,但是仍然可以享受 Block Tree 的优势。

当同时存在多种更新,需要将 PatchFlags 进行按位或运算,例如:PatchFlags.CLASS | PatchFlags.STYLE 。

NEED_PATCH 标识

为什么单独把这个标志拿出来讲呢,它比较特殊,需要我们额外注意。当我们使用 ref 或 onVNodeXXX 等 hook 时(包括自定义指令),需要使用该标志,以至于它可以被父级 Block 收集,详细原因我们在静态提升一节里面讲解过了:

const App = {
  setup() {
    const refDom = ref(null)
    return () => {
      return (openBlock(), createBlock('div', null,[
        createVNode('p',
          {
            ref: refDom,
            onVnodeBeforeMount() {/* ... */}
          },
          null,
          PatchFlags.NEED_PATCH
        )
      ]))
    }
  }}

该使用 Block 的地方必须用

在最开始的时候,我们讲解了有些指令会导致 DOM 结构不稳定,从而必须使用 Block 来解决问题。手写渲染函数也是一样:

  • 分支判断使用 Block:
const App = {
setup() {

const refOk = ref(true)return () => {
  return (openBlock(), createBlock('div', null, [
    refOk.value
      // 这里使用 Block
      ? (openBlock(), createBlock('div', { key: 0 }, [/* ... */]))
      : (openBlock(), createBlock('div', { key: 1 }, [/* ... */]))
  ]))}
}
}

这里使用 Block 的原因我们在前文已经讲解过了,但这里需要强调的是,除了分支判断要使用 Block 之外,还需要为 Block 指定不同的 key 才行。

  • 列表使用 Block:

当我们渲染列表时,我们常常写出如下代码:

const App = {
  setup() {
    const obj = reactive({ list: [ { val: 1 }, { val: 2 } ] })

    return () => {
      return (openBlock(), createBlock('div', null,
        // 渲染列表
        obj.list.map(item => {
          return createVNode('p', null, item.val, PatchFlags.TEXT)
        })
      ))
    }
  }}

这么写在非优化模式下是没问题的,但我们现在使用了 Block,前文已经讲过为什么 v-for 需要使用 Block 的原因,试想当我们执行如下语句修改数据:

obj.list.splice(0, 1)

这就会导致 Block 中收集的动态节点不一致,最终 Diff 出现问题。解决方案就是让整个列表作为一个 Block,这时我们需要使用 Fragment:

const App = {
  setup() {
    const obj = reactive({ list: [ { val: 1 }, { val: 2 } ] })

    return () => {
      return (openBlock(), createBlock('div', null, [
        // 创建一个 Fragment,并作为 Block 角色
        (openBlock(true), createBlock(Fragment, null,
          // 在这里渲染列表
          obj.list.map(item => {
            return createVNode('p', null, item.val, PatchFlags.TEXT)
          }),
          // 记得要指定正确的 PatchFlags
          PatchFlags.UNKEYED_FRAGMENT
        ))
      ]))
    }
  }}

总结一下:

  • 对于列表我们应该始终使用 Fragment,并作为 Block 的角色
  • 如果 Fragment 的 children 没有指定 key,那么应该为 Fragment 打上 PatchFlags.UNKEYED_FRAGMENT。相应的,如果指定了 key 就应该打上 PatchFlags.KEYED_FRAGMENT
  • 注意到在调用 openBlock(true) 时,传递了参数 true,这代表这个 Block 不会收集 dynamicChildren,因为无论是 KEYED 还是 UNKEYED的 Fragment,在 Diff 它的 children 时都会回归传统 Diff 模式,因此不需要收集 dynamicChildren。

这里还有一点需要注意,在 Diff Fragment 时,由于回归了传统 Diff,我们希望尽快恢复优化模式,同时保证后续收集的可控性,因此通常会让 Fragment 的每一个子节点都作为 Block 的角色:

const App = {
  setup() {
    const obj = reactive({ list: [ { val: 1 }, { val: 2 } ] })

    return () => {
      return (openBlock(), createBlock('div', null, [
        (openBlock(true), createBlock(Fragment, null,
          obj.list.map(item => {
            // 修改了这里
            return (openBlock(), createBlock('p', null, item.val, PatchFlags.TEXT))
          }),
          PatchFlags.UNKEYED_FRAGMENT
        ))
      ]))
    }
  }}

最后再说一下稳定的 Fragment,如果你能确定列表永远不会变化,例如你能确定 obj.list 是不会变化的,那么你应该使用:PatchFlags.STABLE_FRAGMENT标志,并且调用 openBlcok() 去掉参数,代表收集 dynamicChildren:

const App = {
  setup() {
    const obj = reactive({ list: [ { val: 1 }, { val: 2 } ] })

    return () => {
      return (openBlock(), createBlock('div', null, [
        // 调用 openBlock() 不要传参
        (openBlock(), createBlock(Fragment, null,
          obj.list.map(item => {
            // 列表中的任何节点都不需要是 Block 角色
            return createVNode('p', null, item.val, PatchFlags.TEXT)
          }),
          // 稳定的片段
          PatchFlags.STABLE_FRAGMENT
        ))
      ]))
    }
  }}

如上注释所述。

  • 使用动态 key 的元素应该是 Block

正如在静态提升一节中所讲的,当元素使用动态 key 的时候,即使两个元素的其他方面完全一样,那也是两个不同的元素,需要做替换处理,在 Block Tree 中应该以 Block 的角色存在,因此如果一个元素使用了动态 key,它应该是一个 Block:

const App = {
  setup() {
    const refKey = ref('foo')

    return () => {
      return (openBlock(), createBlock('div', null,[
        // 这里应该是 Block
        (openBlock(), createBlock('p', { key: refKey.value }))
      ]))
    }
  }}

这实际上是必须的,详情查看 https://github.com/vuejs/vue-next/issues/938 。

使用 Slot hint

我们在“稳定的 Fragment”一节中提到了 slot hint,当我们为组件编写插槽内容时,为了告诉 runtime:“我们已经能够保证插槽内容的结构稳定”,则需要使用 slot hint:

render() {
    return (openBlock(), createBlock(Comp, null, {
        default: () => [
            refVal.value
               ? (openBlock(), createBlock('p', ...)) 
               ? (openBlock(), createBlock('div', ...)) 
        ],
        // slot hint
        _: 1
    }))}

当然如果你不能保证这一点,或者觉得心智负担大,那么就不要写 hint 了。

使用 $stable hint

$stable hint 和之前讲的优化策略不同,前文中的策略都是假设渲染器在优化模式下工作的,而 $stable 用于非优化模式,也就是我们平时写的渲染函数。那么它有什么用呢?如下代码所示(使用 tsx 演示):

export const App = defineComponent({
  name: 'App',
  setup() {
    const refVal = ref(true)

    return () => {
      refVal.value

      return (
        <Hello>
          {
            { default: () => [<p>hello</p>] }
          }
        </Hello>
      )
    }
  }})

如上代码所示,渲染函数中读取了 refVal.value 的值,建立了依赖收集关系,当修改 refVal 的值时,会触发 <Hello> 组件的更新,但是我们发现 Hello 组件从来没有 props 变化,二来它的插槽内容是静态的,因此不应该更新才对,这时我们可以使用 $stable hint:

export const App = defineComponent({
  name: 'App',
  setup() {
    const refVal = ref(true)

    return () => {
      refVal.value

      return (
        <Hello>
          {
            { default: () => [<p>hello</p>], $stable: true } // 修改了这里
          }
        </Hello>
      )
    }
  }})

为组件正确地使用 DYNAMIC_SLOTS

当我们动态构建 slots 时,需要为组件的 VNode 指定 PatchFlags.DYNAMIC_SLOTS,否则将导致更新失败。什么是动态构建 slots呢?通常情况下是指:依赖当前 scope 变量构建的 slots,例如:

render() {
    // 使用当前组件作用域的变量
    const slots ={}
    // 常见的场景
    // 情况一:条件判断
    if (refVal.value) {
        slots.header = () => [h('p', 'hello')]
    }
    // 情况二:循环
    refList.value.forEach(item => {
        slots[item.name] = () => [...]
    })
    // 情况三:动态 slot 名称,情况二包含情况三
    slots[refName.value] = () => [...]

    return (openBlock(), createBlock('div', null, [
        // 这里要使用 PatchFlags.DYNAMIC_SLOTS
        createVNode(Comp, null, slots, PatchFlags.DYNAMIC_SLOTS)
    ]))}

如上注释所述。

以上,不知道到达这里的同学有多少,Don’t stop learning…

本篇已完结

推荐Vue学习资料文章:

vue实现一个6个输入框的验证码输入组件

一用惊人的Vue实践技巧「值得推荐」

Vue常见的面试知识点汇总(上)「附答案」

Vue常见的面试知识点汇总(下)「附答案」

Kbone原理详解与小程序技术选型

为什么我不再用Vue,改用React?

让Jenkins自动部署你的Vue项目「实践」

20个免费的设计资源 UI套件背景图标CSS框架

Deno将停止使用TypeScript,并公布五项具体理由

前端骨架屏都是如何生成的

Vue原来可以这样写开发效率杠杠的

用vue简单写一个音乐播放组件「附源码」

为什么Vue3.0不再使用defineProperty实现数据监听?

「干货」学会这些Vue小技巧,可以早点下班和女神约会

探索 Vue-Multiselect

细品30张脑图带你从零开始学Vue

Vue后台项目中遇到的技术难点以及解决方案

手把手教你Electron + Vue实战教程(五)

手把手教你Electron + Vue实战教程(四)

手把手教你Electron + Vue实战教程(三)

手把手教你Electron + Vue实战教程(二)

手把手教你Electron + Vue实战教程(一)

收集22种开源Vue模板和主题框架「干货」

如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」

手把手教你实现一个Vue自定义指令懒加载

基于 Vue 和高德地图实现地图组件「实践」

一个由 Vue 作者尤雨溪开发的 web 开发工具—vite

是什么让我爱上了Vue.js

1.1万字深入细品Vue3.0源码响应式系统笔记「上」

1.1万字深入细品Vue3.0源码响应式系统笔记「下」

「实践」Vue 数据更新7 种情况汇总及延伸解决总结

尤大大细说Vue3 的诞生之路「译」

提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器

大厂Code Review总结Vue开发规范经验「值得学习」

Vue3 插件开发详解尝鲜版「值得收藏」

带你五步学会Vue SSR

记一次Vue3.0技术干货分享会

Vue 3.x 如何有惊无险地快速入门「进阶篇」

「干货」微信支付前后端流程整理(Vue+Node)

带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」

「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分

「干货」Vue+Element前端导入导出Excel

「实践」Deno bytes 模块全解析

细品pdf.js实践解决含水印、电子签章问题「Vue篇」

基于vue + element的后台管理系统解决方案

Vue仿蘑菇街商城项目(vue+koa+mongodb)

基于 electron-vue 开发的音乐播放器「实践」

「实践」Vue项目中标配编辑器插件Vue-Quill-Editor

基于 Vue 技术栈的微前端方案实践

消息队列助你成为高薪 Node.js 工程师

Node.js 中的 stream 模块详解

「干货」Deno TCP Echo Server 是怎么运行的?

「干货」了不起的 Deno 实战教程

「干货」通俗易懂的Deno 入门教程

Deno 正式发布,彻底弄明白和 node 的区别

「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台

「实践」深入对比 Vue 3.0 Composition API 和 React Hooks

前端网红框架的插件机制全梳理(axios、koa、redux、vuex)

深入Vue 必学高阶组件 HOC「进阶篇」

深入学习Vue的data、computed、watch来实现最精简响应式系统

10个实例小练习,快速入门熟练 Vue3 核心新特性(一)

10个实例小练习,快速入门熟练 Vue3 核心新特性(二)

教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」

2020前端就业Vue框架篇「实践」

详解Vue3中 router 带来了哪些变化?

Vue项目部署及性能优化指导篇「实践」

Vue高性能渲染大数据Tree组件「实践」

尤大大细品VuePress搭建技术网站与个人博客「实践」

10个Vue开发技巧「实践」

是什么导致尤大大选择放弃Webpack?【vite 原理解析】

带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】

带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】

实践Vue 3.0做JSX(TSX)风格的组件开发

一篇文章教你并列比较React.js和Vue.js的语法【实践】

手拉手带你开启Vue3世界的鬼斧神工【实践】

深入浅出通过vue-cli3构建一个SSR应用程序【实践】

怎样为你的 Vue.js 单页应用提速

聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总

【新消息】Vue 3.0 Beta 版本发布,你还学的动么?

Vue真是太好了 壹万多字的Vue知识点 超详细!

Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5

深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

手把手教你深入浅出vue-cli3升级vue-cli4的方法

Vue 3.0 Beta 和React 开发者分别杠上了

手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件

Vue3 尝鲜

总结Vue组件的通信

Vue 开源项目 TOP45

2020 年,Vue 受欢迎程度是否会超过 React?

尤雨溪:Vue 3.0的设计原则

使用vue实现HTML页面生成图片

实现全栈收银系统(Node+Vue)(上)

实现全栈收银系统(Node+Vue)(下)

vue引入原生高德地图

Vue合理配置WebSocket并实现群聊

多年vue项目实战经验汇总

vue之将echart封装为组件

基于 Vue 的两层吸顶踩坑总结

Vue插件总结【前端开发必备】

Vue 开发必须知道的 36 个技巧【近1W字】

构建大型 Vue.js 项目的10条建议

深入理解vue中的slot与slot-scope

手把手教你Vue解析pdf(base64)转图片【实践】

使用vue+node搭建前端异常监控系统

推荐 8 个漂亮的 vue.js 进度条组件

基于Vue实现拖拽升级(九宫格拖拽)

手摸手,带你用vue撸后台 系列二(登录权限篇)

手摸手,带你用vue撸后台 系列三(实战篇)

前端框架用vue还是react?清晰对比两者差异

Vue组件间通信几种方式,你用哪种?【实践】

浅析 React / Vue 跨端渲染原理与实现

10个Vue开发技巧助力成为更好的工程师

手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

1W字长文+多图,带你了解vue的双向数据绑定源码实现

深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】

干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现

手把手教你D3.js 实现数据可视化极速上手到Vue应用

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】

吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】

Vue3.0权限管理实现流程【实践】

后台管理系统,前端Vue根据角色动态设置菜单栏和路由

作者:HcySunYang

转发链接:https://mp.weixin.qq.com/s/cbLm56UcoV6DQI2jBMM8YQ

相关推荐

让组策略保护Windows XP的安全

默认安装完WindowsXP之后,我们的WindowsXP并不很安全。因此,我们有必要对系统进行一些修修补补,一般情况下我们都要动用到注册表。诚然,修改注册表是一种非常有效的方法,但是它需要一定的...

你造吗?十种方式保护你免受&quot;零日攻击&quot;

|责编:王迪WindowsXP的寿终正寝,数据安全问题又再一次成为人们关注的焦点。近日,微软透漏,一个基于InternetExplorer的“零日攻击”给用户带来了严重破坏。“零日攻击”一种利用...

特立独行——打造游戏专用独立系统

大部分人的电脑是为了学习和工作用的,所以,如果你是一个游戏迷,那么推荐你安装一个独立系统专用于游戏,做到工作娱乐两不相扰。方案1:游戏专用移动WindowsXP目的:解决游戏兼容性问题喜欢玩游戏的都...

驰为VX8 3G Win8入门教程篇

距离Win8.1的正式发布也将近1年了,凭借着Win8.1在移动便携以及娱乐办公上的优势,现在的Win8平板越来越受到消费者的追捧,而驰为VX83G就是其中一款,搭载了卓越的英特尔Z3735G四核芯,...

易淘收银软件说明

易淘收银系统,简称易淘收银,专为小型及连锁零售、餐饮行业打造。基于SaaS模式,智能便捷,无需维护,轻量级设计却功能强大,简约而不失专业,助力门店高效管理收银。1、前台系统:收银客户端;2、后台系...

CAD打不开怎么办?原因可能是电脑中毒了,6步就能完美解决问题

一、问题描述我的CAD安装后无法打开,安装过程中没有出现任何问题,但是安装后打开就出现一个对话框“DBXCAS0”点击后又出现“FATALERROR:UnhandledAccessViola...

腾讯QQ6.1正式版发布更新

2014-07-2405:12:00作者:张林【中关村在线软件资讯】7月24日消息:腾讯QQ官网小幅更新了QQ6.1正式版,最新版本号升级至11905,继续主打扁平化、炫酷登录窗口、支持同步最近一...

Win10等网页版OneDrive无法登陆怎么办?

IT之家(www.ithome.com):Win10等网页版OneDrive无法登陆怎么办?Win10之家报道,微软OneDrive云网盘是跨平台的数据同步和存储服务,支持WindowsPC(如Wi...

经典回顾:折戟沉沙的Windows Longhorn有着惊艳的登录屏幕

尽管微软原先计划让WindowsLonghorn继承WindowsXP操作系统的衣钵,但这个充满雄心壮志的操作系统项目最终还是未能迎来曙光,而是被微软用WindowsVista取而代...

电脑怎么优化

电脑配置和宽带流量也是硬件,但这些要求其实并不需要很高,关键还是怎么去安全使用电脑并进行有效的优化。电脑的应用和优化处理一、电脑的应用和优化处理二、目前,大家使用的个人电脑,配置方面均没多大问题,比如...

怎么安装usb驱动

USB驱动主要是针对WIN98时代的说法,如今WINXP已集成大部分USB驱动,通常都能识别。只有极少数情况下,例如手机、打印机或扫描仪等办公设备的USB驱动可能无法自动识别。1、USB驱动偶尔无法...

普通话考试多名考生信息被泄露,接投诉后涉事网站被限制访问

“陕西普通话成绩查询网(sxpth.cn)”泄露个人信息网站截图网传图片显示,407名普通话考试考生的姓名、身份证号码等个人信息疑似被泄露。9月26日下午,涉事网站sxpth.cn的域名注册商——成...

电脑伪技巧——个人电脑无需设置登录密码

默认情况下,我们每次登录系统都要输入登录账户对应的密码才能进入桌面。有些朋友觉得这样很麻烦,由于电脑只是自己使用,还不如不要设置密码,这样每次可以自动登录。大家知道,账户密码是系统验证用户合法性的唯一...

Windows 10/11 自带远程桌面:实用技巧与操作指南

Windows10/11自带远程桌面:实用技巧与操作指南在当今快节奏的数字时代,远程访问和控制计算机的需求日益增长。微软在Windows10和Windows11中内置了远程桌面功能,为用户提供...

不升级系统的5大原因造吗?

2015-01-2405:54:00作者:陈占伟Windows10系统的发布,让人们重新将目光聚焦到生命力长久的Windows系统之上。如今操作系统越来越多,似乎Windows升级的获得的关注度...

取消回复欢迎 发表评论: