Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9d2c1d47a | ||
|
|
cc364c4b2f | ||
|
|
f65b11ea6b | ||
|
|
35458365c6 | ||
|
|
dde997ac27 | ||
|
|
53ae9e077e | ||
|
|
30bdcf13dd | ||
|
|
12b1b02539 | ||
|
|
2a92487a5e | ||
|
|
b6487f8754 | ||
|
|
7a8e1b25dd | ||
|
|
44683fa1dc |
@@ -164,11 +164,11 @@ async function mount() {
|
||||
|
||||
```javascript
|
||||
async function mount() {
|
||||
const result = await Promise.all(
|
||||
const result = await Promise.all([
|
||||
fetch('a.json'),
|
||||
fetch('b.json'),
|
||||
fetch('c.json')
|
||||
);
|
||||
]);
|
||||
|
||||
render(...result);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ function getTokenBlockComment(restStr: string) {
|
||||
```typescript
|
||||
while (sqlStr) {
|
||||
token =
|
||||
getTokenWhitespace(sqlStr, token) | getTokenBlockComment(sqlStr, token);
|
||||
getTokenWhitespace(sqlStr, token) || getTokenBlockComment(sqlStr, token);
|
||||
|
||||
sqlStr = sqlStr.substring(token.value.length);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
重回 “手写 SQL 编辑器” 系列。这次介绍如何利用缓存优化编译器执行性能。
|
||||
|
||||
可以利用 **Frist 集** 与 **Match 节点缓存** 这两种方式优化。
|
||||
可以利用 **First 集** 与 **Match 节点缓存** 这两种方式优化。
|
||||
|
||||
本文会用到一些图做解释,下面介绍图形规则:
|
||||
|
||||
@@ -44,7 +44,7 @@ Match 节点缓存,指在运行时,缓存节点到其第一个终结符的
|
||||
|
||||
拿 `select a, b, c, d from e` 这个语句做测试:
|
||||
|
||||
| node 节点访问次数 | Frist 集优化 | First 集 + Match 节点缓存优化 |
|
||||
| node 节点访问次数 | First 集优化 | First 集 + Match 节点缓存优化 |
|
||||
| ----------------- | ------------ | ----------------------------- |
|
||||
| 784 | 669 | 652 |
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
# 1. 引言
|
||||
|
||||
Vue 3.0 的发布引起了轩然大波,让我们解读下它的 [function api RFC](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#comparison-with-react-hooks) 详细了解一下 Vue 团队是怎么想的吧!
|
||||
|
||||
首先官方回答了几个最受关注的问题:
|
||||
|
||||
**Vue 3.0 是否有 break change,就像 Python 3 / Angular 2 一样?**
|
||||
|
||||
不,100% 兼容 Vue 2.0,且暂未打算废弃任何 API(未来也不)。之前有草案试图这么做,但由于用户反馈太猛,被撤回了。
|
||||
|
||||
**Vue 3.0 的设计盖棺定论了吗?**
|
||||
|
||||
没有呀,这次精读的稿子就是 RFC(Request For Comments),翻译成中文就是 “意见征求稿”,还在征求大家意见中哦。
|
||||
|
||||
**这 RFC 咋这么复杂?**
|
||||
|
||||
RFC 是写给贡献者/维护者的,要考虑许多边界情况与细节,所以当然会复杂很多喽!当然 Vue 本身使用起来还是很简单的。
|
||||
|
||||
> Vue 本身 Mutable + Template 就注定了是个用起来简单(约定 + 自然),实现起来复杂(解析 + 双绑)的框架。
|
||||
|
||||
**这次改动很像在模仿 React,为啥不直接用 React?**
|
||||
|
||||
首先 Template 机制还是没变,其次模仿的是 Hooks 而不是 React 全部,如果你不喜欢这个改动,那你更不会喜欢用 React。
|
||||
|
||||
PS: 问这个问题的人,一定没有同时理解 React 与 Vue,其实这两个框架到现在差别蛮大的,后面精读会详细说明。
|
||||
|
||||
下面正式进入 Vue 3.0 Function API 的介绍。
|
||||
|
||||
# 2. 概述
|
||||
|
||||
Vue 函数式基本 Demo:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<span>count is {{ count }}</span>
|
||||
<span>plusOne is {{ plusOne }}</span>
|
||||
<button @click="increment">count++</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { value, computed, watch, onMounted } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// reactive state
|
||||
const count = value(0)
|
||||
// computed state
|
||||
const plusOne = computed(() => count.value + 1)
|
||||
// method
|
||||
const increment = () => { count.value++ }
|
||||
// watch
|
||||
watch(() => count.value * 2, val => {
|
||||
console.log(`count * 2 is ${val}`)
|
||||
})
|
||||
// lifecycle
|
||||
onMounted(() => {
|
||||
console.log(`mounted`)
|
||||
})
|
||||
// expose bindings on render context
|
||||
return {
|
||||
count,
|
||||
plusOne,
|
||||
increment
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
函数式风格的入口是 `setup` 函数,采用了函数式风格后可以享受如下好处:类型自动推导、减少打包体积。
|
||||
|
||||
`setup` 函数返回值就是注入到页面模版的变量。我们也可以返回一个函数,通过使用 `value` 这个 API 产生属性并修改:
|
||||
|
||||
```jsx
|
||||
import { value } from 'vue'
|
||||
|
||||
const MyComponent = {
|
||||
setup(props) {
|
||||
const msg = value('hello')
|
||||
const appendName = () => {
|
||||
msg.value = `hello ${props.name}`
|
||||
}
|
||||
return {
|
||||
msg,
|
||||
appendName
|
||||
}
|
||||
},
|
||||
template: `<div @click="appendName">{{ msg }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
要注意的是,`value()` 返回的是一个对象,通过 `.value` 才能访问到其真实值。
|
||||
|
||||
为何 `value()` 返回的是 Wrappers 而非具体值呢?原因是 Vue 采用双向绑定,只有对象形式访问值才能保证访问到的是最终值,这一点类似 React 的 `useRef()` API 的 `.current` 规则。
|
||||
|
||||
那既然所有 `value()` 返回的值都是 Wrapper,那直接给模版使用时要不要调用 `.value` 呢?**答案是否定的,直接使用即可,模版会自动 `Unwrapping`:**
|
||||
|
||||
```jsx
|
||||
const MyComponent = {
|
||||
setup() {
|
||||
return {
|
||||
count: value(0)
|
||||
}
|
||||
},
|
||||
template: `<button @click="count++">{{ count }}</button>`
|
||||
}
|
||||
```
|
||||
|
||||
接下来是 **Hooks**,下面是一个使用 Hooks 实现获得鼠标实时位置的例子:
|
||||
|
||||
```jsx
|
||||
function useMouse() {
|
||||
const x = value(0)
|
||||
const y = value(0)
|
||||
const update = e => {
|
||||
x.value = e.pageX
|
||||
y.value = e.pageY
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousemove', update)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', update)
|
||||
})
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
// in consuming component
|
||||
const Component = {
|
||||
setup() {
|
||||
const { x, y } = useMouse()
|
||||
const { z } = useOtherLogic()
|
||||
return { x, y, z }
|
||||
},
|
||||
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,`useMouse` 将所有与 “处理鼠标位置” 相关的逻辑都封装了进去,乍一看与 React Hooks 很像,但是有两个区别:
|
||||
|
||||
1. `useMouse` 函数内改变 `x`、`y` 后,不会重新触发 `setup` 执行。
|
||||
2. `x` `y` 拿到的都是 Wrapper 而不是原始值,且这个值会动态变化。
|
||||
|
||||
另一个重要 API 就是 **`watch`**,它的作用类似 React Hooks 的 **useEffect**,但实现原理和调用时机其实完全不一样。
|
||||
|
||||
`watch` 的目的是监听某些变量变化后执行逻辑,比如当 `id` 变化后重新取数:
|
||||
|
||||
```jsx
|
||||
const MyComponent = {
|
||||
props: {
|
||||
id: Number
|
||||
},
|
||||
setup(props) {
|
||||
const data = value(null)
|
||||
watch(() => props.id, async (id) => {
|
||||
data.value = await fetchData(id)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
之所以要 `watch`,因为在 Vue 中,`setup` 函数仅执行一次,所以不像 React Function Component,每次组件 `props` 变化都会重新执行,因此无论是在变量、`props` 变化时如果想做一些事情,都需要包裹在 `watch` 中。
|
||||
|
||||
后面还有 `unwatching`、生命周期函数、依赖注入,都是一些语法定义,感兴趣可以继续[阅读原文](https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#dependency-injection),笔者就不赘述了。
|
||||
|
||||
# 3. 精读
|
||||
|
||||
对于 Vue 3.0 的 Function API + Hooks 与 React Function Component + Hooks,笔者做一些对比。
|
||||
|
||||
## Vue 与 React 逻辑结构
|
||||
|
||||
React Function Component 与 Hooks,虽然在实现原理上,与 Vue3.0 存在 Immutable 与 Mutable、JSX 与 Template 的区别,但逻辑理解上有着相通之处。
|
||||
|
||||
```ts
|
||||
const MyComponent = {
|
||||
setup(props) {
|
||||
const x = value(0)
|
||||
|
||||
const setXRandom = () => {
|
||||
x.value = Math.random()
|
||||
}
|
||||
|
||||
return { x, setXRandom }
|
||||
},
|
||||
template: `
|
||||
<button @onClick="setXRandom"/>{{x}}</button>
|
||||
`
|
||||
}
|
||||
```
|
||||
|
||||
虽然在 Vue 中,`setup` 函数仅执行一次,看上去与 React 函数完全不一样(React 函数每次都执行),但其实 Vue 将渲染层(Template)与数据层(setup)分开了,而 React 合在了一起。
|
||||
|
||||
我们可以利用 React Hooks 将数据层与渲染层完全隔离:
|
||||
|
||||
```jsx
|
||||
// 类似 vue 的 setup 函数
|
||||
function useMyComponentSetup(props) {
|
||||
const [x, setX] = useState(0)
|
||||
|
||||
const setXRandom = useCallback(() => {
|
||||
setX(Math.random())
|
||||
}, [setX])
|
||||
|
||||
return { x, setXRandom }
|
||||
}
|
||||
|
||||
// 类似 vue 的 template 函数
|
||||
function MyComponent(props: { name: String }) {
|
||||
const { x, setXRandom } = useMyComponentSetup(props)
|
||||
|
||||
return (
|
||||
<button onClick={setXRandom}>{x}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
这源于 JSX 与 Template 的根本区别。JSX 使模版与 JS 可以写在一起,因此数据层与渲染层可以耦合在一起写(也可以拆分),但 Vue 采取的 Template 思路使数据层强制分离了,这也使代码分层更清晰了。
|
||||
|
||||
而实际上 Vue3.0 的 `setup` 函数也是可选的,再配合其支持的 TSX 功能,与 React 真的只有 Mutable 的区别了:
|
||||
|
||||
```jsx
|
||||
// 这是个 Vue 组件
|
||||
const MyComponent = createComponent((props: { msg: string }) => {
|
||||
return () => h('div', props.msg)
|
||||
})
|
||||
```
|
||||
|
||||
我们很难评价 Template 与 JSX 的好坏,但为了更透彻的理解 Vue 与 React,需要抛开 JSX&Template,Mutable&Immutable 去看,其实去掉这两个框架无关的技术选型,React@16 与 Vue@3 已经非常像了。
|
||||
|
||||
> Vue3.0 的精髓是学习了 React Hooks 概念,因此正好可以用 Hooks 在 React 中模拟 Vue 的 setup 函数。
|
||||
|
||||
关于这两套技术选型,已经是相对完美的组合,不建议在 JSX 中再实现类似 Mutable + JSX 的花样来(因为喜欢 Mutable 可以用 Vue 呀):
|
||||
|
||||
- Vue:Mutable + Template
|
||||
- React:Immutable + JSX
|
||||
|
||||
真正影响编码习惯的就是 Mutable 与 Immutable,使用 Vue 就坚定使用 Mutable,使用 React 就坚定使用 Immutable,这样能最大程度发挥两套框架的价值。
|
||||
|
||||
## Vue Hooks 与 React Hooks 的差异
|
||||
|
||||
先看 React Hooks 的简单语法:
|
||||
|
||||
```jsx
|
||||
const [ count, setCount ] = useState(0)
|
||||
|
||||
const setToOne = () => setCount(1)
|
||||
```
|
||||
|
||||
Vue Hooks 的简单语法:
|
||||
|
||||
```jsx
|
||||
const count = value(0)
|
||||
|
||||
const setToOne = () => count.value = 1
|
||||
```
|
||||
|
||||
之所以 React 返回的 `count` 是一个数字,是因为 Immutable 规则,而 Vue 返回的 `count` 是个对象,拥有 `count.value` 属性,也是因为 Vue Mutable 规则导致,这使得 Vue 定义的所有变量都类似 React 中 `useRef` 定义变量,因此不存 React `capture value` 的特性。
|
||||
|
||||
> 关于 capture value 更多信息,可以阅读 [精读《Function VS Class 组件》 Capute Value 介绍](https://github.com/dt-fe/weekly/blob/v2/095.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20VS%20Class%20%E7%BB%84%E4%BB%B6%E3%80%8B.md#capture-props)
|
||||
|
||||
另外,对于 Hooks 的值变更机制也不同,我们看 Vue 的代码:
|
||||
|
||||
```jsx
|
||||
const Component = {
|
||||
setup() {
|
||||
const { x, y } = useMouse()
|
||||
const { z } = useOtherLogic()
|
||||
return { x, y, z }
|
||||
},
|
||||
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
|
||||
}
|
||||
```
|
||||
|
||||
由于 `setup` 函数仅执行一次,怎么做到当 `useMouse` 导致 `x`、`y` 值变化时,可以在 `setup` 中拿到最新的值?
|
||||
|
||||
在 React 中,`useMouse` 如果修改了 `x` 的值,那么使用 `useMouse` 的函数就会被重新执行,以此拿到最新的 `x`,而在 Vue 中,将 Hooks 与 Mutable 深度结合,通过包装 `x.value`,使得当 `x` 变更时,引用保持不变,仅值发生了变化。所以 Vue 利用 Proxy 监听机制,可以做到 `setup` 函数不重新执行,但 Template 重新渲染的效果。
|
||||
|
||||
这就是 Mutable 的好处,Vue Hooks 中,不需要 `useMemo` `useCallback` `useRef` 等机制,仅需一个 `value` 函数,直观的 Mutable 修改,就可以实现 React 中一套 Immutable 性能优化后的效果,这个是 Mutable 的魅力所在。
|
||||
|
||||
## Vue Hooks 的优势
|
||||
|
||||
笔者对 RFC 中对 Vue、React Hooks 的对比做一个延展解释:
|
||||
|
||||
首先最大的不同:`setup` 仅执行一遍,而 React Function Component 每次渲染都会执行。
|
||||
|
||||
**Vue 的代码使用更符合 JS 直觉。**
|
||||
|
||||
这句话直截了当戳中了 JS 软肋,JS 并非是针对 Immutable 设计的语言,所以 Mutable 写法非常自然,而 Immutable 的写法就比较别扭。
|
||||
|
||||
当 Hooks 要更新值时,Vue 只要用等于号赋值即可,而 React Hooks 需要调用赋值函数,**当对象类型复杂时,还需借助第三方库才能保证进行了正确的 Immutable 更新。**
|
||||
|
||||
**对 Hooks 使用顺序无要求,而且可以放在条件语句里。**
|
||||
|
||||
对 React Hooks 而言,调用必须放在最前面,而且不能被包含在条件语句里,这是因为 React Hooks 采用下标方式寻找状态,一旦位置不对或者 Hooks 放在了条件中,就无法正确找到对应位置的值。
|
||||
|
||||
而 Vue Function API 中的 Hooks 可以放在任意位置、任意命名、被条件语句任意包裹的,因为其并不会触发 `setup` 的更新,只在需要的时候更新自己的引用值即可,而 Template 的重渲染则完全继承 Vue 2.0 的依赖收集机制,它不管值来自哪里,只要用到的值变了,就可以重新渲染了。
|
||||
|
||||
**不会再每次渲染重复调用,减少 GC 压力。**
|
||||
|
||||
这确实是 React Hooks 的一个问题,所有 Hooks 都在渲染闭包中执行,每次重渲染都有一定性能压力,而且频繁的渲染会带来许多闭包,虽然可以依赖 GC 机制回收,但会给 GC 带来不小的压力。
|
||||
|
||||
而 Vue Hooks 只有一个引用,所以存储的内容就非常精简,也就是占用内存小,而且当值变化时,也不会重新触发 `setup` 的执行,所以确实不会造成 GC 压力。
|
||||
|
||||
**必须要总包裹 `useCallback` 函数确保不让子元素频繁重渲染。**
|
||||
|
||||
React Hooks 有一个问题,就是完全依赖 Immutable 属性。**而在 Function Component 内部创建函数时,每次都会创建一个全新的对象,这个对象如果传给子组件,必然导致子组件无法做性能优化。** 因此 React 采取了 `useCallback` 作为优化方案:
|
||||
|
||||
```jsx
|
||||
const fn = useCallback(() => /* .. */, [])
|
||||
```
|
||||
|
||||
只有当第二个依赖参数变化时才返回新引用。但第二个依赖参数需要 lint 工具确保依赖总是正确的(关于为何要对依赖诚实,感兴趣可以移步 [精读《Function Component 入门》 - 永远对依赖诚实](https://github.com/dt-fe/weekly/blob/v2/104.%E7%B2%BE%E8%AF%BB%E3%80%8AFunction%20Component%20%E5%85%A5%E9%97%A8%E3%80%8B.md#%E6%B0%B8%E8%BF%9C%E5%AF%B9%E4%BE%9D%E8%B5%96%E9%A1%B9%E8%AF%9A%E5%AE%9E))。
|
||||
|
||||
回到 Vue 3.0,由于 `setup` 仅执行一次,因此函数本身只会创建一次,不存在多实例问题,不需要 `useCallback` 的概念,更不需要使用 [lint 插件](https://www.npmjs.com/package/eslint-plugin-react-hooks) 保证依赖书写正确,这对开发者是实实在在的友好。
|
||||
|
||||
**不需要使用 `useEffect` `useMemo` 等进行性能优化,所有性能优化都是自动的。**
|
||||
|
||||
这也是实在话,毕竟 Mutable + 依赖自动收集就可以做到最小粒度的精确更新,根本不会触发不必要的 Rerender,因此 `useMemo` 这个概念也不需要了。
|
||||
|
||||
而 `useEffect` 也需要传递第二个参数 “依赖项”,在 Vue 中根本不需要传递 “依赖项”,所以也不会存在用户不小心传错的问题,更不需要像 React 写一个 lint 插件保证依赖的正确性。(这也是笔者想对 React Hooks 吐槽的点,React 团队如何保障每个人都安装了 lint?就算装了 lint,如果 IDE 有 BUG,导致没有生效,随时可能写出依赖不正确的 “危险代码”,造成比如死循环等严重后果)
|
||||
|
||||
# 4. 总结
|
||||
|
||||
通过对比 Vue Hooks 与 React Hooks 可以发现,Vue 3.0 将 Mutable 特性完美与 Hooks 结合,规避了一些 React Hooks 的硬伤。所以我们可以说 Vue 借鉴了 React Hooks 的思想,但创造出来的确实一个更精美的艺术品。
|
||||
|
||||
但 React Hooks 遵循的 Immutable 也有好的一面,就是每次渲染中状态被稳定的固化下来了,不用担心状态突然变更带来的影响(其实反而要注意状态用不变更带来的影响),对于数据记录、程序运行的稳定性都有较高的可预期性。
|
||||
|
||||
最后,对于喜欢 Mutable 的开发者,Vue 3.0 是你的最佳选择,基于 React + Mutable 搞的一些小轮子做到顶级可能还不如 Vue 3.0。对于 React 开发者来说,坚持你们的 Immutable 信仰吧,Vue 3.0 已经将 Mutable 发挥到极致,只有将 React Immutable 特性发挥到极致才能发挥 React 的最大价值。
|
||||
|
||||
> 讨论地址是:[精读《Vue3.0 Function API》 · Issue #173 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/173)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,196 @@
|
||||
# 1. 引言
|
||||
|
||||
本周精读的源码是 [inject-instance](https://github.com/ascoders/inject-instance) 这个库。
|
||||
|
||||
这个库的目的是为了实现 Class 的依赖注入。
|
||||
|
||||
比如我们通过 `inject` 描述一个成员变量,那么在运行时,这个成员变量的值就会被替换成对应 Class 的实例。这等于让 Class 具备了申明依赖注入的能力:
|
||||
|
||||
```js
|
||||
import {inject} from 'inject-instance'
|
||||
import B from './B'
|
||||
|
||||
class A {
|
||||
@inject('B') private b: B
|
||||
public name = 'aaa'
|
||||
|
||||
say() {
|
||||
console.log('A inject B instance', this.b.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
试想一下,如果成员函数 `b` 是通过 New 出来的:
|
||||
|
||||
```js
|
||||
class A {
|
||||
private b = new B()
|
||||
|
||||
say() {
|
||||
console.log('A inject B instance', this.b.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 `b` 就不具备依赖注入的特点,因为被注入的 `b` 是外部已经初始化好的,而不是实例化 A 时动态生成的。
|
||||
|
||||
需要依赖注入的一般都是框架级代码,比如定义数据流,存在三个 Store 类,他们之间需要相互调用对方实例:
|
||||
|
||||
```js
|
||||
class A {
|
||||
@inject('B') private b: B
|
||||
}
|
||||
|
||||
class B {
|
||||
@inject('C') private c: C
|
||||
}
|
||||
|
||||
class C {
|
||||
@inject('A') private a: A
|
||||
}
|
||||
```
|
||||
|
||||
那么对于引用了数据流 A、B、C 的三个组件,**要保证它们访问到的是同一组实例 `A` `B` `C` 该怎么办呢?**
|
||||
|
||||
这时候我们需要通过 `injectInstance` 函数统一实例化这些类,保证拿到的实例中,成员变量都是属于同一份实例:
|
||||
|
||||
```js
|
||||
import injectInstance from 'inject-instance'
|
||||
|
||||
const instances = injectInstance(A, B, C)
|
||||
instances.get('A')
|
||||
instances.get('B')
|
||||
instances.get('C')
|
||||
```
|
||||
|
||||
那么框架底层可以通过调用 `injectInstance` 方式初始化一组 “正确注入依赖关系的实例”,拿 React 举例,这个动作可以发生在自定义数据流的 `Provider` 函数里:
|
||||
|
||||
```js
|
||||
<Provider stores={{ A, B, C }}>
|
||||
<Root />
|
||||
</Provider>
|
||||
```
|
||||
|
||||
那么在 `Provider` 函数内部通过 `injectInstance` 实例化的数据流,**可以保证 `A` `B` `C` 操作的注入实例都是当前 `Provider` 实例中的那一份**。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
那么开始源码的解析,首先是整体思路的分析。
|
||||
|
||||
我们需要准备两个 API: `inject` 与 `injectInstance`。
|
||||
|
||||
`inject` 用来描述要注入的类名,值是与 Class 名相同的字符串,`injectInstance` 是生成一系列实例的入口函数,需要生成最终生效的实例,并放在一个 Map 中。
|
||||
|
||||
## inject
|
||||
|
||||
`inject` 是个装饰器,它的目的有两个:
|
||||
|
||||
1. 修改 Class 基类信息,使其实例化的实例能拿到对应字段注入的 Class 名称。
|
||||
2. 增加一个字段描述注入了那些 Key。
|
||||
|
||||
```ts
|
||||
const inject = (injectName: string): any => (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => {
|
||||
target[propertyKey] = injectName
|
||||
|
||||
// 加入一个标注变量
|
||||
if (!target['_injectDecorator__injectVariables']) {
|
||||
target['_injectDecorator__injectVariables'] = [propertyKey]
|
||||
} else {
|
||||
target['_injectDecorator__injectVariables'].push(propertyKey)
|
||||
}
|
||||
|
||||
return descriptor
|
||||
}
|
||||
```
|
||||
|
||||
`target[propertyKey] = injectName` 这行代码中,`propertyKey` 是申明了注入的成员变量名称,比如 Class `A` 中,`propertyKey` 等于 `b`,而 `injectName` 表示这个值需要的对应实例的 Class 名,比如 Class `A` 中,`injectName` 等于 `B`。
|
||||
|
||||
而 `_injectDecorator__injectVariables` 是个数组,为 Class 描述了这个类参与注入的 key 共有哪些,这样可以在后面 `injectInstance` 函数中拿到并依次赋值。
|
||||
|
||||
## injectInstance
|
||||
|
||||
这个函数有两个目的:
|
||||
|
||||
1. 生成对应的实例。
|
||||
2. 将实例中注入部分的成员变量替换成对应实例。
|
||||
|
||||
代码不长,直接贴出来:
|
||||
|
||||
```ts
|
||||
const injectInstance = (...classes: Array<any>) => {
|
||||
const classMap = new Map<string, any>()
|
||||
const instanceMap = new Map<string, any>()
|
||||
|
||||
classes.forEach(eachClass => {
|
||||
if (classMap.has(eachClass.name)) {
|
||||
throw `duplicate className: ${eachClass.name}`
|
||||
}
|
||||
classMap.set(eachClass.name, eachClass)
|
||||
})
|
||||
|
||||
// 遍历所有用到的类
|
||||
classMap.forEach((eachClass: any) => {
|
||||
// 实例化
|
||||
instanceMap.set(eachClass.name, new eachClass())
|
||||
})
|
||||
|
||||
// 遍历所有实例
|
||||
instanceMap.forEach((eachInstance: any, key: string) => {
|
||||
// 遍历这个类的注入实例类名
|
||||
if (eachInstance['_injectDecorator__injectVariables']) {
|
||||
eachInstance['_injectDecorator__injectVariables'].forEach((injectVariableKey: string) => {
|
||||
const className = eachInstance.__proto__[injectVariableKey];
|
||||
if (!instanceMap.get(className)) {
|
||||
throw Error(`injectName: ${className} not found!`);
|
||||
}
|
||||
|
||||
// 把注入名改成实际注入对象
|
||||
eachInstance[injectVariableKey] = instanceMap.get(className);
|
||||
});
|
||||
}
|
||||
|
||||
// 删除这个临时变量
|
||||
delete eachInstance['_injectDecorator__injectVariables'];
|
||||
});
|
||||
|
||||
return instanceMap
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,首先我们将传入的 Class 依次初始化:
|
||||
|
||||
```ts
|
||||
// 遍历所有用到的类
|
||||
classMap.forEach((eachClass: any) => {
|
||||
// 实例化
|
||||
instanceMap.set(eachClass.name, new eachClass())
|
||||
})
|
||||
```
|
||||
|
||||
这是必须提前完成的,因为注入可能存在循环依赖,我们必须在解析注入之前就生成 Class 实例,此时需要注入的字段都是 `undefined`。
|
||||
|
||||
第二步就是将这些注入字段的 `undefined` 替换为刚才实例化 Map `instanceMap` 中对应的实例了。
|
||||
|
||||
我们通过 `__proto__` 拿到 Class 基类在 `inject` 函数中埋下的 `injectName`,配合 `_injectDecorator__injectVariables` 拿到 key 后,直接遍历所有要替换的 key, 通过类名从 `instanceMap` 中提取即可。
|
||||
|
||||
> `__proto__` 仅限框架代码中使用,业务代码不要这么用,造成额外理解成本。
|
||||
|
||||
所以总结一下,就是提前实例化 + 根据 `inject` 埋好的信息依次替换注入的成员变量为刚才实例化好的实例。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
希望读完这篇文章,你能理解依赖注入的使用场景,使用方式,以及一种实现思路。
|
||||
|
||||
框架实现依赖注入都是提前收集所有类,统一初始化,通过注入函数打标后全局替换,这是一种思维套路。
|
||||
|
||||
如果有其他更有意思的依赖注入实现方案,欢迎讨论。
|
||||
|
||||
> 讨论地址是:[精读《Inject Instance 源码》 · Issue #176 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/176)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,128 @@
|
||||
# 1. 引言
|
||||
|
||||
前端展望的文章越来越不好写了,随着前端发展的深入,需要拥有非常宽广的视野与格局才能看清前端的未来。
|
||||
|
||||
笔者根据自身经验,结合下面几篇文章发表一些总结与感悟:
|
||||
|
||||
- [A Look at JavaScript’s Future](https://www.toptal.com/javascript/predicting-javascript-future)
|
||||
- [前端开发 20 年变迁史](https://mp.weixin.qq.com/s/yNg7Q0XNLJMnqffTIJhNUg)
|
||||
- [前端开发编程语言的过去、现在和未来](https://johnhax.net/2019/fe-lang/article1)
|
||||
- [绕过技术纷争,哪些技术决定前端开发者的未来?](https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247491704&idx=1&sn=95ad66f7fe606801cdac74e296a41783)
|
||||
- [未来前端的机会在哪里?](https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247490769&idx=1&sn=7ee6e01045a6fe7e15f16aa33afcc2ad&chksm=e92921dede5ea8c8e93489271e8877d2e8688bd511b32e22c287b6c468904c5466b40f6a2bec&xtrack=1&scene=90&subscene=93&sessionid=1562200039&clicktime=1562)
|
||||
|
||||
读完这几篇文章可以发现,即便是最资深的前端从业者,每个人看前端未来也有不同的侧重点。这倒不是因为视野的局限,而是现在前端领域太多了,专精其中某几个领域就足够了,适量比全面更好。
|
||||
|
||||
同时前端底层也在逐渐封闭,虽然目睹了前端几十年变迁的开发者仍会对一些底层知识津津乐道,但通往底层的大门已经一扇扇逐渐关闭了,将更多的开发者挤到上层区域建设,所以仅学会近几年的前端知识依然能找到不错的工作。
|
||||
|
||||
然而上层建设是不封顶的,有人看到了山,有人看到了星球,不同业务环境,不同视野的人看到的东西都不同。
|
||||
|
||||
有意思是的国内和国外看到前端未来的视角也不同:国内看到的是追求更多的参与感、影响力,国外看到的是对新特性的持续跟进。
|
||||
|
||||
# 2. 精读
|
||||
|
||||
前端可以从多个角度理解,比如规范、框架、语言、社区、场景以及整条研发链路。
|
||||
|
||||
看待前端未来的角度随着视野不同也会有变化,比如 Serverless 是未来,务实的思考是:前端在 Serverless 研发链路中仅处于使用方,并不会因为用了 Serverless 而提升了技术含量。更高格局的思考是:怎么推动 Serverless 的建设,不把自己局限在前端。
|
||||
|
||||
所以当我们读到不同的人对前端理解的时候,有人站在一线前端研发的角度,有人站在全栈的角度,也有人站在业务负责人的角度。其实国内前端发展也到了这个阶段,老一辈的前端开拓者们已经进入不同的业务领域,承担着更多不同的职能分工,甚至是整个大业务线的领导者,这说明两点:
|
||||
|
||||
1. 前辈已经用行动指出了前端突破天花板的各种方向。
|
||||
2. 同是前端未来展望,不同的文章侧重的格局不同,两个标题相同的文章内容可能大相径庭。
|
||||
|
||||
笔者顺着这些文章分析角度,发表一些自己的看法。
|
||||
|
||||
## 框架
|
||||
|
||||
在前端早期,也就是 1990 年浏览器诞生的时候,JS 没有良好的设计,浏览器也没有全面的实现,框架还没出来,浏览器之间就打起来了。
|
||||
|
||||
这也给前端发展定了一个基调:凭实力说话。
|
||||
|
||||
后面诞生的 Prototype、jquery 都是为了解决时代问题而诞生的,所以有种时代造就前端框架的感觉。
|
||||
|
||||
但到了最近几年,React、Angular、Vue 大有前端框架引领新时代的势头,前端要做的不再是填坑,而是模式创新。国内出现的小程序浪潮是个意料之外的现象,虽然群雄割据为开发者适配带来了一定成本,但本质上是中国在前端底层领域争取话语权的行为,而之所以各大公司不约而同的推出自己的小程序,则是商业、经济发展到了这个阶段的自然产物。
|
||||
|
||||
在原生开发领域,像 RN、Flutter 也是比较靠谱的移动端开发框架,RN 就长在 React 上,而 Flutter 的声明式 UI 也借鉴了前端框架的思路。每个框架都想往其他框架的领域渗透,所以标准总是很相近,各自的特色并没有宣传的那么明显,这个阶段只选用一种框架是明智的选择,未来这些框架之间会有更多使用场景争夺,但更多的是融合,推动新的开发方式提高生产力。
|
||||
|
||||
在数据驱动 UI 的方式上,具有代表性的是 React 的 Immutable 模式与 Vue 的 MVVM 观察者模式,前者模式虽然新颖,但是符合 JS 语言自然运行机制,Vue 的 MVVM 模式也相当好,特别是 Vue3.0 的 API 巧妙的解决了 React Hooks 无法解决的难题。如果 Vue 继续保持蓬勃的发展势头,未来前端 MVVM 模式甚至可能标准化,那么 Vue 是作为标准化的事实规范,还是和 JQuery 一样的命运,还需观察。
|
||||
|
||||
## 语言
|
||||
|
||||
JS 语言本身有满多缺陷的,但通过 babel 前端工程师可以提前享受到大部分新特性,这在很大程度上抵消了早期语言设计带来的问题。
|
||||
|
||||
横向对比来看,我们还可以把编程语言分为:前端语言、后端语言、能编译到 JS 的语言。
|
||||
|
||||
之所以有 “能编译到 JS 的语言” 这一类,是因为 JS Runtime 几乎是前端跨平台的通用标准,能编译到 JS 就代表了可跨平台,然而现在 “能编译到 JS 的语言” 除了紧贴 JS 做类型增强的 TS 外,其他并没有火起来,有工具链生态不匹配的原因,也有各大公司之间利益争夺的原因。
|
||||
|
||||
后端语言越来越贴场景化,比如 Go 主打轻量级高并发方案,Python 以其易用性占领了大部分大数据、人工智能的运算场景。
|
||||
|
||||
与此对应的是前端语言的同质化,前端语言绑定在前端框架的趋势越来越明显,比如 IOS 平台只能用 OC 和 Swift,安卓只能用 JAVA 和 Kotlin,Flutter 只支持 Dart,与其说这些语言更适合这些平台特性,不如说背后是谷歌、苹果、微软等巨头对平台生态掌控权的争夺。Web 与移动端要解决的问题是类似的:如何高效管理 UI 状态,现在大部分都采用数据驱动的思路,通过 JSX 或 Template 的方式描述出 UI DSL(更多可参考 [前端开发编程语言的过去、现在和未来](https://johnhax.net/2019/fe-lang/article1) UI DSL 一节)、以及性能提升:渲染和计算分离(这里又分为并发与调度两种实现思路,目的和效果是类似的)。
|
||||
|
||||
所以编程语言的未来也没什么悬念,前端领域如果有的选就用 JS,没得选只能依附所在平台绑定的语言,而前端语言最近正在完成一轮升级大迁徙:JS -> TS,JAVA -> Kotlin,OC -> Swift,前端语言的特性、易用性正在逐步趋同。需要说明的是,如果仅了解这些语言的语法,对编程能力是毫无帮助的,了解平台特性,解决业务问题,提供更好的交互体验才是前端应该不断追求的目标,随着前端、Native 开发者之间的流动,前端领域语言层面差异会会来越小,大家越关注上层,越倾向抹平语言差异,甚至可能 All in JS,这不是因为 JS 有多大野心,而是因为在解决的问题趋同、业务优先的大背景下,大家都需要减少语言不通带来的障碍,最好的办法就是统一语言,从人类语言的演变就可以发现,要解决的问题趋同(人类交流)、与国家绑定的小众语言一直都有生存空间、语法大同小异,但不同语言都有一定自己的特色(比如法语表意更精确)、跨语言学习成本高,所以当国际化协作频繁时,一定会催生一套官方语言(英语),而使用基数大的语言可能会发展为通用国际语言(中文)。
|
||||
|
||||
将编程语言的割裂、统一比作人类语言来看,就能理解现状,和未来发展趋势了。
|
||||
|
||||
## 可视化
|
||||
|
||||
前面也说过,前端的底层在逐渐封闭,而可视化就是前端的上层。
|
||||
|
||||
所以笔者很少提到工程化,原因就是未来前端开发者接触工程化的机会越来越少,工程化机制也越来越完善,前端会逐渐回归到自己的本质 - 人机交互,而交互的重要媒介就是图形,无论组件库还是智能化设计稿 To Code 都为了解放简单、模式化的交互工作,专业前端将更多聚集到图形化领域。
|
||||
|
||||
图形和数据是分不开的,所以图形化还要考虑性能问题与数据转换。
|
||||
|
||||
可视化是对性能要求最高的,因此像 web worker、GPU 加速都是常见处理手段,WASM 技术也会用到可视化中。具体到某个图表或大屏的性能优化,还会涉及数据抽样算法,分层渲染等,仅仅性能优化领域就有不少探索的空间。性能问题一般还伴随着数据量大,所以数据序列化方案也要一并考虑。
|
||||
|
||||
可视化图形学是非常学术的领域,从图形语法到交互语法,从一图一做的简单场景,到可视化分析场景的灵活拓展能力,再到探索式分析的图形语法完备性要求,可视化库想要一层层支持不同业务场景的需求,要有一个清晰的分层设计。
|
||||
|
||||
仅可视化的图形学领域,就足够将所有时间投入了,未来做可视化的前端会越来越专业,提供的工具库接口也越来越有一套最佳实践沉淀,对普通前端越来越友好。
|
||||
|
||||
BI 可视化分析就是前端深造的一个方向,跟随 BI 发展阶段,对前端的要求也在不断变化:工程化、组件化、搭建技术、渲染引擎、可视化、探索式、智能化,跟上产品对技术能力的要求,其实是相当有挑战性的。
|
||||
|
||||
## 编辑器
|
||||
|
||||
编辑器方向主要有 IDE(Web IDE)、富文本编辑器。
|
||||
|
||||
**IDE 方向** 国产做的比较好的是 HBuilder,国际上做的比较好的是 VSCode,由于微软还同时推出了 Web 版 MonacoEditor,让 Web IDE 开发的门槛大大降低。
|
||||
|
||||
作为使用者,现在和未来的主流可能都是微软系,毕竟微软在操作系统、IDE 方面人才储备和经验积累很多。但随着云服务的变迁,引导着开发方式升级,IDE 游戏规则可能迎来重大改变 - 云化。云化使得作为开发者拥有更多竞争的机会,因为云上 IDE 市场现在还是蓝海,现在很多创业公司和大公司内部都在走这个方向,这标志着中国计算机技术往更底层的技术发展,未来会有更多的话语权。
|
||||
|
||||
从发展阶段来说,前端也发展到了 Web IDE 这个时代。对大公司来说,内部有许许多多割裂的工程化孤岛,不仅消耗大量优秀的前端同学去维护,也造成内部物料体系、工程体系难以打通,阻碍了内部技术流通,而云 IDE 天生的中心化环境管理可以解决这个问题,同时还能带来抹平计算机环境差异、统一编译环境、源码不落盘、甚至实现自动的多人协作也成为了可能,而云 IDE 因为在云上,也不止于 IDE,还可以很方便的集成流程,将研发全链路打通,因此在阿里内部也成为了今年四大方向之一。
|
||||
|
||||
所以今年可以明显看到的是,前端又在逐步替代低水平重复的 UI 设计,从设计稿生成代码,到研发链路上云,这种顶层设计正在进一步收窄前端底层建设,所以未来会有更多专业前端涌入可视化领域。
|
||||
|
||||
**富文本编辑器方向** 是一个重要且小众的领域,老牌做的较好的是 UEditor 系列,现在论体验和周边功能完善度,做得最好的是语雀编辑器。开源也有很多优秀的实现,比如 Quill、DraftJS、Slate 等等,但现在富文本编辑器核心能力是功能完备性(是否支持视频、脑图、嵌入)、性能、服务化功能打通了多少(是否支持在线解析 pdf、ppt 等文件)、交互自然程度(拷贝内容的智能识别)等等。如果将眼光放到全球,那国外有大量优秀富文本编辑器案例,比如 Google Docs、Word Online、iCloud Pages 等等。
|
||||
|
||||
最好用的富文本编辑器往往不开源,因为投入的技术研发成本是巨大的,本身这项技术就是一个产品,卖点就是源码。
|
||||
|
||||
富文本编辑器功能强度可以分为三个级别:L0~L2:
|
||||
|
||||
- L0:利用浏览器自带的输入框,主要指 `contenteditable` 实现。
|
||||
- L1:在 L0 的基础上通过 DOM API 自主实现增删改的功能,自定义能力非常强。
|
||||
- L2:从输入框、光标开始自主研发,完全不依赖浏览器特性,如果研发团队能力强,可以实现任何功能,典型产品比如 Google Docs。
|
||||
|
||||
无论国内外都鲜有进入 L2 强度的产品,除了超级大公司或者主打编辑器的创业公司。
|
||||
|
||||
所以编辑器方向中,无论 IDE 方向,还是富文本编辑器方向,都值得深入探索,其中 IDE 方向更偏工程化一些,考验体系化思维,编辑器方向更偏经验与技术,考验基本功和架构设计能力。
|
||||
|
||||
## 智能化
|
||||
|
||||
笔者认为智能化离前端这个工种是比较远的,智能化最终服务前后端,给前后端开发效率带来一个质的提升,而在此之前,作为前端从业者无非有两种选择:加入智能化开拓者队伍,或者准备好放弃可能被智能化替代的工作内容,积极投身于智能化解放开发者双手后,更具有挑战性的工作。这种挑战性的工作恰好包括了上面分析过的四个点:语言、框架、可视化、编辑器。
|
||||
|
||||
类比商业智能化,商业智能化包括网络协同和数据智能,也就是大量的网络协同产生海量数据,通过数据智能算法促进更好的算法模型、更高效的网络协同,形成一个反馈闭环。前端智能化也是类似,不管是自动切图、生成图片、页面,或者自动生成代码,都需要算法和前端工程师之间形成协同关系,并完成一个高效的反馈闭环,算法将是前端工程师手中的开发利器,且越规模化的使用功效越大。
|
||||
|
||||
另一种智能化方向是探索 BI 与可视化结合的智能化,通过功能完备的底层图表库,与后端通用 Cube 计算模型,形成一种探索式分析型 BI 产品,Tableau 就是典型的案例,在这个智能化场景中,需要对数据、产品、可视化全面理解的综合性人才,是前端职业生涯另一个突破点。
|
||||
|
||||
# 3. 总结
|
||||
|
||||
本文列举的五点显然不能代表前端的全貌,还遗漏了太多方面,比如工程化、组件化、Serverless 等,但 **语言、框架、可视化、编辑器、智能化** 这五个点是笔者认为前端,特别是国内前端值得持续发力,可以做深的点,成为任何一个领域的专家都足以突破前端工程师成长的天花板。
|
||||
|
||||
最后,前端是最贴近业务的技术之一,业务的未来决定了前端的未来,创造的业务价值决定了前端的价值,从现在开始锻炼自己的商业化思考能力与产品意识,看得懂业务,才能看到未来。
|
||||
|
||||
> 讨论地址是:[精读《前端未来展望》 · Issue #178 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/178)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
Reference in New Issue
Block a user