慕ke 前端工程师2023版2024版「完JIE」

88 阅读4分钟

前端工程师2023版2024版

核心代码,注释必读

// download:3w ukoou com

问题一:compostion api 根本没有解决任何问题,只是追逐新玩意的东西

Vue 最开始很小,但是现在被广泛应用到不同级别复杂度的业务领域,有些可以基于 option API 很轻松处理,但是有些不可以。例如下面的场景:

有很多逻辑的大型组件(数百行) 在多个组件可复用的逻辑

对于问题 1,你需要把每个逻辑拆分到不同选项,例如,一段逻辑需要一些响应数据,一个计算属性,一些监听属性还有方法。你去了解这段逻辑时,需要不断上下移动阅读,虽然你知道一些属性是什么类型,但是你并不知道他具体的作用。当一个组件包含多个逻辑,情况就更糟糕了。如果用新的 API,可以将数据和逻辑组合在一起,最重要的是,你可以干净的把这些逻辑提取到一个函数,甚至一个单独的文件中。

问题二:使用新 API 导致逻辑分散到不同地方,违背"关注点分离" 这个问题和项目文件组织方式问题类似。我们很多人都同意按文件类型组织(布局放 HTML,样式 CSS,逻辑 JS)并不是正确的方式,因为强制把相关代码分割到三个文件,只是给人一种“关注点分离”的错觉。这里的关键是“关注点”不是由文件类型定义。相反,我们大多数选择以功能或者职责来组织文件,这正是人们喜欢 Vue 单文件组件的原因。SFC 就是按功能组织代码的方法,但讽刺的是当首次引入 SFC 时,许多人也是拒绝的,认为它违反了关注点分离。

问题三:新的语法让 Vue 失去简单性,导致"意大利面条式代码"的出现,降低项目维护性。 正好相反,新的 API 就是为了提高项目长期维护性的。如果我们查看任何 javascript 项目,都会从入口文件开始阅读,该文件的本质是你的应用启动时被隐式调用的"main"函数。如果只有一个函数入口,会导致意大利面条代码,那所有的 js 项目都是意大利面条代码。显然不是的,因为开发人员通过代码模块化或者较小的函数来组织代码。另外,我同意新的 API 理论上会降低代码质量的最低门槛。但是我们可以使用以往防止代码变成意大利面条的手段缓解这种情况。另一方面,新的 API 可以提升代码质量的最高上限,相比 option api,你可以重构为质量更高的代码。而且,基于 Option api 你还得解决类似 mixins 的问题。很多人认为"Vue 失去简单性",实际上只是失去组件内代码类型检查能力(就是你不知道一个变量时 data、method、还是 computed)。但是用新的 API,实现一个类型检测器也是非常容易实现以前的特性的。也就是说,你不应该被 option api 限制思维,而更多关注逻辑内聚问题。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 测试set的引用类型
        // 经过测试,之所以反向塞到肚子里是由于为了在更新的时候给去删除多余无用的effect 
        const set = new Set()
        set.add(1)
        console.log(set)
        const a = {
            deps: [set]
        }
        console.log(a)
        const { deps } = a
        console.log(deps)
        deps[0].remover(1)
        console.log(set)
    </script>
</body>

</html>

前端工程师 2023版 前端工程师2024版 项目实战

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>
        依赖追踪和注销逻辑梳理
    </h1>
    <div>

        依赖追踪的api <a href="https://v3.cn.vuejs.org/api/effect-scope.html#effectscope">effectScope</a> 其实已经被暴露出来供用户使用
    </div>
    <div>
        这个api的本质就是为了更方便的管理副作用的收集和销毁。在源码内部使用的也是当前api
    </div>
    <div>
        为什么要有effectScope?
    </div>
    <div>
        具体请看vuerfc 的 <a
            href="https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md"></a> rc
    </div>
    <script src="../packages/vue/dist/vue.global.js"></script>
    <script>
        //具体实现逻辑实例:
        let activeEffectScope
        const effectScopeStack = []

        class EffectScope {
            // active 的作用是为了防止执行stop销毁之再次执行当前实例的方法依然生效
            active = true
            effects = []
            cleanups = []
            parent
            scopes
            index
            constructor(detached = false) {
                if (!detached && activeEffectScope) {
                    this.parent = activeEffectScope
                    this.index =
                        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
                            this
                        ) - 1
                }
            }

            run(fn) {
                if (this.active) {
                    try {
                        this.on()
                        return fn()
                    } finally {
                        this.off()
                    }
                }
            }
            // 依赖追踪使用
            on() {
                if (this.active) {
                    effectScopeStack.push(this)
                    activeEffectScope = this
                }
            }

            off() {
                if (this.active) {
                    effectScopeStack.pop()
                    activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
                }
            }

            stop(fromParent) {
                if (this.active) {
                    this.effects.forEach(e => e.stop())
                    this.cleanups.forEach(cleanup => cleanup())
                    if (this.scopes) {
                        this.scopes.forEach(e => e.stop(true))
                    }
                    this.active = false
                }
            }
        }
        // 建立关联的函数,当前函数在computed、watch、watchEffect等函数中使用
        function recordEffectScope(
            effect,
            scope
        ) {
            scope = scope || activeEffectScope
            if (scope && scope.active) {
                scope.effects.push(effect)
            }
        }

        //1、 在外部使用
        // 由于源码中recordEffectScope 被保存在vue 的对象中无法获取
        //为了能模拟源码中调用recordEffectScope 方法重写 computed 
        // 2、挡在内部使用时,其实就是收集了组件的依赖,从而能达到在卸载时,统一执行scope.stop统一卸载当前组件的的依赖的所有effect
        const computed = Vue.computed
        Vue.computed = function (fn) {
            const val = computed(fn)
            // 模拟拿到effect
            recordEffectScope(val.effect)
            return val
        }
        const ref = Vue.ref
        Vue.ref = function (val) {
            const newRef = computed(val)
            // 模拟拿到effect
            recordEffectScope(val.effect)
            return newRef
        }
        function effectScope() {
            return new EffectScope()
        }

        const scope = effectScope()
        const num = Vue.ref(0)

        const val = scope.run(() => {
            //这个情况下是没有依赖收集的
            const doubled = Vue.computed(() => {
                console.log(1111)
                return num.value * 2
            })
            return '他还能有返回值'
        })
        num.value++
        // 卸载了所有的依赖
        scope.stop()
    </script>
</body>

</html>