《前端会客厅》对话winter和尤雨溪,深度探寻Vue3设计思想(中)

前端会客厅第一期(中)B站地址

前端会客厅是我和winter设计的一档技术节目,每期会邀请一个嘉宾,畅聊前端技术,废话不多说,尤大上篇很受欢迎,中篇也上线拉,配套代码演示 相当于一个买家秀, 代码实操我最近会录制视频放B站,欢迎关注

TOC

  1. Composition API
    1. reactive
    2. watchEffect
    3. ref
  2. 响应式 && LocalStorage
  3. 响应式 && 数据获取
  4. Option Vs Composition

Composition

其实Vue2现在就有一个全局的方法,方法叫做Vue.observable。 这个其实就是跟Vue3的这个reactive,是一样的,

  it('should observe basic properties', () => {
    let dummy
    const counter = reactive({ num: 0 })
    effect(() => (dummy = counter.num))

    expect(dummy).toBe(0)
    counter.num = 7
    expect(dummy).toBe(7)
  })

reactive负责对象等负责数据,ref负责基本数据变成响应式 比如数字和字符串,effect负责副作用,这三个概念就是响应式的核心,而且ref和reactive还有一点点小区别

reactive直接遍历对象+Proxy, ref其实也可以用reactive实现,不过ref只用到了valute属性,所以完全可以使用get和set来实现依赖收集和通知,有更好的性能

function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  let value = shallow ? rawValue : convert(rawValue)
  const r = {
    __v_isRef: true,
    get value() {
      track(r, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newVal) {
      if (hasChanged(toRaw(newVal), rawValue)) {
        rawValue = newVal
        value = shallow ? newVal : convert(newVal)
        trigger(
          r,
          TriggerOpTypes.SET,
          'value',
          __DEV__ ? { newValue: newVal } : void 0
        )
      }
    }
  }
  return r
}

Proxy

effect其实并不会在我们每次所有的reactive对象发生变化的时候都执行。 他是会只有在这个counter.num有变化的时候才会执行一次。 所以这样其实是用了一个比较巧妙的办法去监听了变化 然后这个背后就是用proxy去做,而且如果数据是深层嵌套的,Proxy只会在获取数据的时候,才回去递归的用proxy,而不是vue2的首次渲染就全部定义好,这也是Vue3性能提升的一个原因

    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

响应式 && LocalStorage

我们设计一个drag的小方块,逻辑很简单,就是可以把div拖着乱跑,但是每次刷新, 小方块会回到初始状态,我们如果想加入一个数据同步到localStorage的例子,用reactive和effect就可以实现一个基本无注入的同步函数

import {reactive, ref, effect} from "@vue/reactivity";

export default function LocalStorage(key, defaultValue){
    let data = reactive({});

    Object.assign(data, localStorage[key] && JSON.parse(localStorage[key]) || defaultValue);

    effect(() => localStorage[key] = JSON.stringify(data));

    return data;
}



export default {
    data: () => ({
        pos: LocalStorage("pos", {x:20, y:20})
    }),
}

只在data这里加了一个函数设置了key,就可以实现data数据和localStorage的同步了,有了这个脑洞,我们其实可以做很多事

  1. 响应式 && 数据获取 (ajax)
  2. console.log && info && error实现日志文件写入
  3. 响应式系统独立,可以用在任意框架离,比如react中

Option Vs Composition

这个没啥多说得了,社区对比的文章也很多了,主要就是可以把生命周期,响应式数据,操作函数,全部抽离再一个内部函数,所以vue3的组件就可以无限的拆分,并且数据来源还可以异常清晰

比如我们的todoMvc加一个功能,滚动一段距离后,上面的输入框就fixed再顶部,用composition api就可以把这个功能完整的抽离

import { ref, onMounted, onUnmounted } from 'vue'
export default function useScroll() {
  const top = ref(0)

  function update(e) {
    top.value = window.scrollY
  }

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

  onUnmounted(() => {
    window.removeEventListener('scroll', update)
  })

  return { top }
}

模板中直接使用

const {x,y} = useMouse()

<header class="header" :class="{fixed:top>130}">

可维护性直线提升,并且还可以有效的把一个功能相关的数据都放在一起,避免了vue2代码维护的上下反复横条,官方经典的图如下

一个颜色是一个功能,可以看出vue3的代码组织更为合理

但是vue2的option api也不是完全放弃,因为option的配置,更符合人的心智模型,啥玩意都是this.x 所以小程序option很合适,大型程序composition更利于组织和调试

还可以直接看下官方吐槽的代码

吐槽的官方Option组件

优化之后的composition版本 优雅程度直线上升

export default {
  setup () {
    // Network
    const { networkState } = useNetworkState()
    // Folder
    const { folders, currentFolderData } = useCurrentFolderData(networkState)
    const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
    const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
    const { showHiddenFolders } = useHiddenFolders()
    const createFolder = useCreateFolder(folderNavigation.openFolder)
    // Current working directory
    resetCwdOnLeave()
    const { updateOnCwdChanged } = useCwdUtils()
    // Utils
    const { slicePath } = usePathUtils()
    return {
      networkState,
      folders,
      currentFolderData,
      folderNavigation,
      favoriteFolders,
      toggleFavorite,
      showHiddenFolders,
      createFolder,
      updateOnCwdChanged,
      slicePath
    }
  }
}

代码和总结

代码

语雀连载《Vue3生态技术内幕》

vue3的composition和React Hooks只是长的一样,内部实现机制完全不同,可以算是雷锋和雷峰塔的区别,Hooks每次render都会执行一次,composition 是只调用一次, reactivity独立后,让vue3插上了想象力的翅膀,你对新的composition有什么疑问,欢迎写在留言区一起讨论

预告

  • 用户答疑+八卦
  • reactivity和vuex的关系
  • class based api被干掉的心路历程
  • vue3设计的过程
  • 小右如何学习的

第二期嘉宾是周爱民老师,会深入聊js,欢迎关注,持续更新