2024年最新尤雨溪:Vue Function-based API RFC,2024年最新wework前端面试

总结
  • 对于框架原理只能说个大概,真的深入某一部分具体的代码和实现方式就只能写出一个框架,许多细节注意不到。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 算法方面还是很薄弱,好在面试官都很和蔼可亲,擅长发现人的美哈哈哈…(最好多刷一刷,不然影响你的工资和成功率???)

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

第一次跳槽十分忐忑不安,和没毕业的时候开始找工作是一样的感受,真的要相信自己,有条不紊的进行。如果有我能帮忙的地方欢迎随时找我,比如简历修改、内推、最起码,可以把烦心事说一说,人嘛都会有苦恼的~

祝大家都有美好的未来,拿下满意的 offer。

const writableComputed = computed(

// read

() => count.value + 1,

// write

val => {

count.value = val - 1

}

)

Watchers

watch() API 提供了基于观察状态的变化来执行副作用的能力。

watch() 接收的第一个参数被称作 “数据源”,它可以是:

  • 一个返回任意值的函数

  • 一个包装对象

  • 一个包含上述两种数据源的数组

第二个参数是回调函数。回调函数只有当数据源发生变动时才会被触发:

watch(

// getter

() => count.value + 1,

// callback

(value, oldValue) => {

console.log('count + 1 is: ', value)

}

)

// -> count + 1 is: 1

count.value++

// -> count + 1 is: 2

和 2.x 的 $watch 有所不同的是,watch() 的回调会在创建时就执行一次。这有点类似 2.x watcher 的 immediate: true 选项,但有一个重要的不同:默认情况下 watch() 的回调总是会在当前的 renderer flush 之后才被调用 —— 换句话说,watch()的回调在触发时,DOM 总是会在一个已经被更新过的状态下。 这个行为是可以通过选项来定制的。

在 2.x 的代码中,我们经常会遇到同一份逻辑需要在 mounted 和一个 watcher 的回调中执行(比如根据当前的 id 抓取数据),3.0 的 watch() 默认行为可以直接表达这样的需求。

观察 props

上面提到了 setup() 接收到的 props 对象是一个可观测的响应式对象:

const MyComponent = {

props: {

id: Number

},

setup(props) {

const data = value(null)

watch(() => props.id, async (id) => {

data.value = await fetchData(id)

})

return {

data

}

}

}

观察包装对象

watch()可以直接观察一个包装对象:

// double 是一个计算包装对象

const double = computed(() => count.value * 2)

watch(double, value => {

console.log('double the count is: ', value)

}) // -> double the count is: 0

count.value++ // -> double the count is: 2

观察多个数据源

watch() 也可以观察一个包含多个数据源的数组 - 这种情况下,任意一个数据源的变化都会触发回调,同时回调会接收到包含对应值的数组作为参数:

watch(

[valueA, () => valueB.value],

([a, b], [prevA, prevB]) => {

console.log(a is: ${a})

console.log(b is: ${b})

}

)

停止观察

watch() 返回一个停止观察的函数:

const stop = watch(…)

// stop watching

stop()

如果 watch() 是在一个组件的 setup() 或是生命周期函数中被调用的,那么该 watcher 会在当前组件被销毁时也一同被自动停止:

export default {

setup() {

// 组件销毁时也会被自动停止

watch(/* … */)

}

}

清理副作用

有时候当观察的数据源变化后,我们可能需要对之前所执行的副作用进行清理。举例来说,一个异步操作在完成之前数据就产生了变化,我们可能要撤销还在等待的前一个操作。为了处理这种情况,watcher 的回调会接收到的第三个参数是一个用来注册清理操作的函数。调用这个函数可以注册一个清理函数。清理函数会在下属情况下被调用:

  • 在回调被下一次调用前

  • 在 watcher 被停止前

watch(idValue, (id, oldId, onCleanup) => {

const token = performAsyncOperation(id)

onCleanup(() => {

// id 发生了变化,或是 watcher 即将被停止.

// 取消还未完成的异步操作。

token.cancel()

})

})

之所以要用传入的注册函数来注册清理函数,而不是像 React 的 useEffect 那样直接返回一个清理函数,是因为 watcher 回调的返回值在异步场景下有特殊作用。我们经常需要在 watcher 的回调中用 async function 来执行异步操作:

const data = value(null)

watch(getId, async (id) => {

data.value = await fetchData(id)

})

我们知道 async function 隐性地返回一个 Promise - 这样的情况下,我们是无法返回一个需要被立刻注册的清理函数的。除此之外,回调返回的 Promise 还会被 Vue 用于内部的异步错误处理。

Watcher 回调的调用时机

默认情况下,所有的 watcher 回调都会在当前的 renderer flush 之后被调用。这确保了在回调中 DOM 永远都已经被更新完毕。如果你想要让回调在 DOM 更新之前或是被同步触发,可以使用 flush 选项:

watch(

() => count.value + 1,

() => console.log(count changed),

{

flush: ‘post’, // default, fire after renderer flush

flush: ‘pre’, // fire right before renderer flush

flush: ‘sync’ // fire synchronously

}

)

全部的 watch 选项(TS 类型声明)

interface WatchOptions {

lazy?: boolean

deep?: boolean

flush?: ‘pre’ | ‘post’ | ‘sync’

onTrack?: (e: DebuggerEvent) => void

onTrigger?: (e: DebuggerEvent) => void

}

interface DebuggerEvent {

effect: ReactiveEffect

target: any

key: string | symbol | undefined

type: ‘set’ | ‘add’ | ‘delete’ | ‘clear’ | ‘get’ | ‘has’ | ‘iterate’

}

  • lazy与 2.x 的 immediate 正好相反

  • deep与 2.x 行为一致

  • onTrack 和 onTrigger 是两个用于 debug 的钩子,分别在 watcher 追踪到依赖和依赖发生变化的时候被调用,获得的参数是一个包含了依赖细节的 debugger event。

生命周期函数

所有现有的生命周期钩子都会有对应的 onXXX 函数(只能在 setup() 中使用):

import { onMounted, onUpdated, onUnmounted } from ‘vue’

const MyComponent = {

setup() {

onMounted(() => {

console.log(‘mounted!’)

})

onUpdated(() => {

console.log(‘updated!’)

})

// destroyed 调整为 unmounted

onUnmounted(() => {

console.log(‘unmounted!’)

})

}

}

依赖注入

import { provide, inject } from ‘vue’

const CountSymbol = Symbol()

const Ancestor = {

setup() {

// providing a value can make it reactive

const count = value(0)

provide({

})

}

}

const Descendent = {

setup() {

const count = inject(CountSymbol)

return {

count

}

}

}

如果注入的是一个包装对象,则该注入绑定会是响应式的(也就是说,如果 Ancestor 修改了 count,会触发 Descendent 的更新)。

类型推导

为了能够在 TypeScript 中提供正确的类型推导,我们需要通过一个函数来定义组件:

import { createComponent } from ‘vue’

const MyComponent = createComponent({

props: {

msg: String

},

setup(props) {

watch(() => props.msg, msg => { /* … */ })

return {

count: value(0)

}

},

render({ state, props }) {

// state typing inferred from value returned by setup()

console.log(state.count)

// props typing inferred from props declaration

console.log(props.msg)

// this exposes both state and props

console.log(this.count)

console.log(this.msg)

}

})

createComponent 从概念上来说和 2.x 的 Vue.extend 是一样的,但在 3.0 中它其实是单纯为了类型推导而存在的,内部实现是个 noop(直接返回参数本身)。它的返回类型可以用于 TSX 和 Vetur 的模版自动补全。如果你使用单文件组件,则 Vetur 可以自动隐式地帮你添加这个调用。

Required Props

Props 默认都是可选的,也就是说它们的类型都可能是 undefined。非可选的 props 需要声明 required: true :

import { createComponent } from ‘vue’

createComponent({

props: {

foo: {

type: String,

required: true

},

bar: {

type: String

}

} as const,

setup(props) {

props.foo // string

props.bar // string | undefined

}

})

这里需要注意我们在 props 选项后面加了一个 as const —— 这是 TS 3.4 提供的一个功能,可以避免 required: true 这样的字面量在推导时被拓宽为 boolean 类型,从而让 Vue 内部可以通过 extends true 来确定 props 是否可选。

注:我们可能应该把 props 改为默认 required,只有当声明 optional: true 时才是可选。

复杂 Props 类型

Vue 提供的 PropType 类型可以用来声明任意复杂度的 props 类型,但需要用 as any 进行一次强制类型转换:

import { createComponent, PropType } from ‘vue’

createComponent({

props: {

options: (null as any) as PropType<{ msg: string }>

},

setup(props) {

props.options // { msg: string } | undefined

}

})

依赖注入类型

依赖注入的 inject 方法是唯一必须手动声明类型的 API:

import { createComponent, inject, Value } from ‘vue’

createComponent({

setup() {

const count: Value = inject(CountSymbol)

return {

count

}

}

})

这里的 Value 类型即是包装对象的类型 ,通过泛型参数来声明其内部包装的值的类型。

缺点/潜在问题

新的 API 使得动态地检视/修改一个组件的选项变得更困难(原来是一个对象,现在是一段无法被检视的函数体)。

这可能是一件好事,因为通常在用户代码中动态地检视/修改组件是一类比较危险的操作,对于运行时也增加了许多潜在的边缘情况(特别是组件继承和使用 mixin 的情况下)。新 API 的灵活性应该在绝大部分情况下都可以用更显式的代码达成同样的结果。

缺乏经验的用户可能会写出 “面条代码”,因为新 API 不像旧 API 那样强制将组件代码基于选项切分开来。

我们在 Class API RFC 和内部讨论中听到过好几次这样的声音,但我认为这是一种没有必要的担忧。虽然理论上新的 API 确实制约更少,但我认为 “面条代码” 的情况不太可能发生,这里详细解释一下。

基于函数的新 API 和基于选项的旧 API 之间的最大区别,就是新 API 让抽取逻辑变得非常简单 —— 就跟在普通的代码中抽取函数一样。也就是说,我们不必只在需要复用逻辑的时候才抽取函数,也可以单纯为了更好地组织代码去抽取函数。

基于选项的代码只是看上去更整洁。一个复杂的组件往往需要同时处理多个不同的逻辑任务,每个逻辑任务所涉及的代码在选项 API 下是被分散在多个选项之中的。举例来说,从服务端抓取一份数据,可能需要用到 props, data(), mounted 和 watch。极端情况下,如果我们把一个应用中所有的逻辑任务都放在一个组件里,这个组件必然会变得庞大而难以维护,因为每个逻辑任务的代码都被选项切成了多个碎片分散在各处。

对比之下,基于函数的 API 让我们可以把每个逻辑任务的代码都整理到一个对应的函数中。当我们发现一个组件变得过大时,我们会将它切分成多个更小的组件;同样地,如果一个组件的 setup() 函数变得很复杂,我们可以将它切分成多个更小的函数。而如果是基于选项,则无法做到这样的切分,因为用 mixin 只会让事情变得更糟糕。

从这个角度看,基于选项 vs. 基于函数就好像基于 HTML/CSS/JS 组织代码 vs. 基于单文件组件来组织代码。

升级策略

新的 API 和 2.x 的 API 理论上完全兼容(只是多了一个 setup()选项) 。但是,新 API 的引入实际上会让相当一部分的旧选项长远来说变得没有必要。如果能够去掉对这些旧选项的支持,可以获得相当的代码尺寸和性能提升。

因此,3.0 我们计划提供两个不同的版本:

  • 兼容版本:同时支持新 API 和 2.x 的所有选项;

  • 标准版本:只支持新 API 和部分 2.x 选项。

在兼容版本中,setup() 可以和旧选项(比如 data()) 一起使用,但顺序上 setup() 会比旧选项优先调用。也就是说,在 setup() 中无法使用由旧选项声明的属性,但在旧选项中可以使用由 setup() 声明的属性。

2.x 的用户可以从兼容版本开始逐步地减少对旧选项的使用,直到最终切换到标准版本。

保留的选项

以下选项行为和 2.x 保持一致,并在兼容和标准版本中都会支持。标有 * 的选项可能会有进一步的调整。

  • name

  • props

  • template

  • render

  • components

  • directives

  • filters*

  • delimiters*

  • comments *

由于本提案而不再必须的选项

以下选项将会在标准版本中被移除,只在兼容版本中支持。

  • data(由 setup() + value) + state) 取代)

  • computed(由 computed 取代)

  • methods( 由 setup() 中声明的函数取代)

  • watch (由 watch() 取代)

  • provide/inject(由 provide() 和 inject() 取代)

  • mixins (由组合函数取代)

  • extends (由组合函数取代)

  • 所有的生命周期选项 (由 onXXX 函数取代)

被其它 RFC 提案废弃的选项

以下选项将会在标准版本中被移除,只在兼容版本中支持。

  • el(应用将不再由 new Vue() 来创建,而是通过新的 createApp 来创建,详见 RFC#29)

  • propsData(给 root component 的 props 通过新的 createApp API 创建的应用实例来提供。详见 RFC#29)

  • functional(3.0 函数式组件直接用函数来声明 ,详见 RFC#27)

  • model(v-model 指令参数使得该选项不再必要,详见 RFC#31)

  • inhertiAttrs (非 props 属性的继承行为改动使得该选项不再必要,详见 RFC#26)

附录

与 React Hooks 的对比

这里提出的 API 和 React Hooks 有一定的相似性,具有同等的基于函数抽取和复用逻辑的能力,但也有很本质的区别。React Hooks 在每次组件渲染时都会调用,通过隐式地将状态挂载在当前的内部组件节点上,在下一次渲染时根据调用顺序取出。而 Vue 的 setup() 每个组件实例只会在初始化时调用一次 ,状态通过引用储存在 setup() 的闭包内。这意味着基于 Vue 的函数 API 的代码:

  • 整体上更符合 JavaScript 的直觉;

  • 不受调用顺序的限制,可以有条件地被调用;

  • 不会在后续更新时不断产生大量的内联函数而影响引擎优化或是导致 GC 压力;

  • 不需要总是使用 useCallback 来缓存传给子组件的回调以防止过度更新;

  • 不需要担心传了错误的依赖数组给 useEffect/useMemo/useCallback 从而导致回调中使用了过期的值 —— Vue 的依赖追踪是全自动的。

注:React Hooks 的开创性毋庸置疑,也是本提案的灵感来源。Hooks 代码和 JSX 并置使得对值的使用更简洁也是其优点,但其设计确实存在上述问题,而 Vue 的响应式系统恰巧能够让我们绕过这些问题。

Class API 的类型问题

Class API 提案的主要目的是寻找一个能够提供更好的 TypeScript 支持的组件声明方式。但是由于 Vue 需要将来自多个选项的属性混合到同一个渲染上下文上,这使得即使用了 Class,要得到良好的类型推导也不是很容易。

以 props 的类型推导为例。要将 props 的类型 merge 到 class 的 this 上,我们有两个选择:用 class 的泛型参数,或是用 decorator。

这是用泛型参数的例子:

interface Props {

message: string

}

class App extends Component {

static props = {

message: String

}

}

专业技能

一般来说,面试官会根据你的简历内容去提问,但是技术基础还有需要自己去准备分类,形成自己的知识体系的。简单列一下我自己遇到的一些题

最近得空把之前遇到的面试题做了一个整理,包括我本人自己去面试遇到的,还有其他人员去面试遇到的,还有网上刷到的,我都统一的整理了一下,希望对大家有用。

其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等

由于文章篇幅有限,仅展示部分内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值