oliver1204 / randomNotes

用来记录平时的日常总结
1 stars 0 forks source link

VueConf 3.0 尤雨溪 演讲 #93

Open oliver1204 opened 5 years ago

oliver1204 commented 5 years ago

chrome DevTools:
 vue: 周活跃 90万
 react: 周活跃 160万

vue 3.0 目标: 更快、更小、支持TypeScript

更快

1. Object.defineProperty —> Proxy

Object.defineProperty劫持 转换 Object 属性是一个昂贵的过程,浏览器的Javascript引擎 喜欢比较稳定的 Object 结构,而Proxy 真正做到了对原始 
Object 的代理,初始化的性能得到了很好的提升。

2. Virtual DOM 重构

Vue 3.0 的整个 Virtual DOM 用 TypeScript 重写了。完美的支持了TypeScript。很多人认为我们使用 vdom 目的是让节目变化的更快,但其实Virtual DOM 核心价值就是用 纯Javascript 描述界面渲染结构。于此同时其付出的代价是:每一次更新的时候,都需要从头开始遍历整个结构,一次比对,找到变化的部分。

虽然 Vue 能保证触发更新到组件,但是组件的颗粒度还是比较大的,在组件内部也需要遍历整个 vdom 树。例如下面的例子:

<template>
  <div id="content">
     <p class="text">Lorem ipsum</p>
     <p class="text">Lorem ipsum</p>
     <p class="text">{{ message }}</p>
     <p class="text">Lorem ipsum</p>
     <p class="text">Lorem ipsum</p>
  </div>
</template>

由上面的例子我们可以看出,整个组件中变化的部门只有{{ message }} 这一部分,但是我们却需要遍历整个 vdom

传统的 vdom 的性能跟模版大小正相关,跟动态节点的数量无关。在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费。

3. 传统的 vdom 的性能瓶颈

虽然在大多数的情况下,很多更新是可以在16毫秒内完成的, 但是当项目足够的大时候,16毫秒并不是不够的。

那么传统的 vdom 为什么要用这种不效率的算法呢?究其原因就是:最初的vdom 并不是由模版编译而来的。比如 react 它是由 JSX 编译而来的,JSXJavascript 的一种语法延伸,它具备 Javascript 的一切动态性。

JSX 和 手写的 render function 由于是完全动态的,这种过度的灵活性导致运行时可以用于优化的信息缺失。

function render() {
   const children = []

   for(let i = 0; i < 5; i++) {
      children.push(h('p', {
         class: 'text'
      }, i === 2 ? this.message : 'Lorum ipsum'))
   }

   return h('div', { id: 'content' }, children)
}

比如,上面的 render function 我们很难单独的从这段代码中获取到唯有当 i === 2 时动态渲染 this.message 否则渲染 Lorum ipsum

反之,从上一个模版例子中,我们可以清楚的发现只有 this.message 是动态变化的。

所以,这就是包含的可以用于优化的信息的不同。react 的本质就是,只要你用 JSX 来写,我们就没有办法向用模版一样的来推测出可以优化的信息。

react 的优化方法是: 时间分片。将更新划分为一帧一帧的。react 秉承的观念是,既然伤害已经造成了,那么我们能做的就是将伤害减少到最底。

vue 的优化方法是: 认为整个 dom树都是静态的,然后从中间提炼出只变化的部分,Block tree(区块树),对比只变化的部分进行更新。

<template>
   <div>
      <p class="text">Lorem ipsum</p>
         <p  v-if="ok">
            <span>{{ message }}</span>
            <span>Lorem ipsum</span>
         </p>
   </div>
</template>

v-if 外部,只有 v-if 是动态节点 v-if 内部,只有 {{ message }} 是动态节点

<template>
   <div>
      <p class="text">Lorem ipsum</p>
         <p  v-for="item in list">
            <span>{{ item.message }}</span>
            <span>Lorem ipsum</span>
         </p>
   </div>
</template>

v-for 外部,只有 v-for 是动态节点 v-for 内部,只有 {{ item.message }} 是动态节点

4. Block tree(区块树)

新策略的 vdom 更新性能由与模版整体大小相关提升为与动态内容的数量相关。

5. 为什么不能抛弃 Virtual DOM

render function 可以实现很多高级场景,但是使用 render function 不可避免的就要用到 Virtual DOM。 当然兼容 2.x 也是必须的。

TypeScript

1. 取消 Class API 取而代之的是 Function-based API

引入 Class API 原本的目的是更好的支持 TS,但是 发现props 和其他插件的注入依然会导致类型问题,而且 Class API 除了支持类型外,似乎没有带来任何优势。

2. Function-based API
import { value, computed, watch, onMounted } from 'vue'

const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }

    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })

    onMounted(() => {
      console.log(`mounted`)
    })
    // 暴露给模版使用的值
    return {
      count,
      plusOne,
      increment
    }
  }
}

优势:

  1. 更好的 TypeScript 的类型推导支持
  2. 更灵活的逻辑复用能力

逻辑复用

在 3.0 之前逻辑复用的方式需要有 Mixins、Higher-order Components、Renderless Components 几种。

1. Mixins

当使用大量的 Mixins 时会造成命名冲突以及模版数据来源不清楚等问题

const mousePositionMixin = {
    data() {
        return {
            x: 0,
            y: 0
        }
    },
    mounted() {
        window.addEventListener('mousemove', this.update)
    },
    destroyed() {
        window.addEventListener('mousemove', this. update)
    },
    methods: {
        update(e) {
            this.x = e.pageX
            this.y = e.pageY
        }
    }
}
2. Higher-order Components 高阶组件

高阶组件就是用一个父组件来承载逻辑内容,让父组件把最终的数据以Props 的形式传给里面的组件,当使用该组件的时候,就用此高阶组件把真正要写的组件包一下,真正要写的组件就以Props的形式接收外面传进来的数据。

const demo = withMousePosition({
      props: ['x', 'y'],
      template: `<div> Mouse position: x {{x}} / y {{y}}</div>`
})

大量使用时问题:

  1. props 命名空间冲突
  2. props 来源不清楚
  3. 额外的组件实例性能消耗
3. Renderless Components 作用域插槽
<mouse v-slot="{ x, y }">
   Mouse position: x {{x}} / y {{y}}
</mouse>

slot 没有命名空间冲突和数据来源不清楚的问题,但是依旧会造成额外的组件实例性能消耗。

Function-based API

组件部分:

function useMousePosition () {
  const x = value(0)
  const y = value(0)

  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', this. update)
  })

  onUnmounted(() => {
    window.addEventListener('mousemove', this. update)
  })

  return { x, y }
}

模版部分:


new Vue({
  https://github.com/olifer655/randomNotes/issues/93template: `
    <div>
      Mouse position: x {{x}} / y {{y}}
    </div>
  `,
  data() {
    const { x, y } = useMousePosition()
    return {
      x,
      y
    }
  }
})

对比 React Hooks

React Hooks 存在的问题:

  1. 在每次组件更新的时候,每次渲染会将所有声明的 Hooks 全部更新一遍。(vue 只在初始时初始化一次)
  2. 存在闭包变量问题
  3. 内联回调导致子组件永远更新

vue 完美的规避了上面的问题。