大家好,我卡颂。
最近看到个写得很不错的知乎答复 Hooks 是否过誉了?前端应该跟着 React 走还是跟着 JS、TS 走?- beeplin 的答复。
在这个答复的根底上,我想引申出一个问题 —— 对于 前端状态 相干问题,如何思考比拟全面?
明天,咱们试着从多个形象层级的角度答复这个问题。
欢送退出人类高质量前端框架群,带飞
问题的起源
有相当比例的前端从业者入行是从 学习前端框架的应用 开始的。换言之,在他们的常识体系中,最底层是 前端框架如何应用,其余业务知识都是构建于此之上。
要以此为根底答复 前端状态 相干问题,并不容易。就比方你问组长:
- 为什么我的项目中用
Redux
而不必Mobx
? - 为什么要用
Hooks
而不必ClassComponent
?
很多时候失去的是一个既定的事实(就是这样,没有为什么),而不是剖析后的后果。
要剖析这类问题,咱们须要晓得一些更低形象层级的常识。
简直所有支流前端框架的实现原理,都在践行 UI = f(state)
这个公式,艰深的说 —— UI 是对状态的映射。
这应该是 前端状态 会呈现的最低形象层级了,所以咱们从这个层级登程。
前端框架的实现原理
限于篇幅无限,这里咱们以最常见的 React
与Vue
举例。
在实现 UI 是对状态的映射 过程中,两者的方向不同。
React
并不关怀状态如何变动。每当调用更新状态的办法(比方this.setState
,或者useState dispatch
…),就会对整个利用进行diff
。
所以在 React
中,传递给 更新状态的办法 的,是 状态的快照 ,换言之,是个 不可变的数据。
Vue
关怀状态如何变动。每当更新状态时,都会对 与状态关联的组件 进行diff
。
所以在 Vue
中,是间接扭转状态的值。换言之,状态是个 可变的数据。
这种底层实现的区别在独自应用框架时不会有很大区别,然而会影响下层库的实现(比方状态治理库)。
当初咱们晓得,通过前端框架,咱们能够将状态映射到UI
。那么如何治理好对应的映射关系呢?
换言之,如何将状态与 和他相干的 UI束缚在一起?
咱们再往更高一级形象看。
如何封装组件
前端开发广泛采纳 组件 作为 状态与 UI 的涣散耦合单元。
到这里咱们能够发现,如果仅仅会应用前端框架,那么只能将组件看作是 前端框架中既定的设计。
但如果从更低一层形象(前端框架的实现原理)登程,就能发现 —— 组件是为了解决框架实现原理中 UI 到状态的映射 的路径。
那么组件该如何实现,他的载体是什么呢?从软件工程的角度登程,有两个方向能够摸索:
- 面向对象编程
- 函数式编程
面向对象编程 的特点包含:
- 继承
- 封装
- 多态
其中 封装 这一特点使得 面向对象编程 很天然成为组件的首选实现形式,毕竟组件的实质就是 将状态与 UI 封装在一起的涣散耦合单元。
React
的 ClassComponent
,Vue
的Options API
都是相似实现。
但毕竟组件的实质是 状态与 UI 的涣散耦合单元 ,在思考复用性时,不仅要思考 逻辑的复用 (逻辑是指操作状态的业务代码),还要思考UI 的复用。所以 面向对象编程 的另两个个性并不适用于组件。
框架们依据本身特点,在 类面向对象编程 的组件实现上,拓展了复用性:
React
通过HOC
、renderProps
Vue2
通过mixin
通过长期实践,框架们逐步发现 —— 类面向对象编程的组件实现 中封装 带来的益处不足以对消 复用性 上的劣势。
于是 React
引入了 Hooks
,以函数作为组件封装的载体,借用 函数式编程 的理念进步复用性。相似的还有 Vue3
中的Composition API
。
不论是 ClassComponent
还是 FunctionComponent
、Options API
还是 Composition API
,他们的实质都是 状态与 UI 的涣散耦合单元。
当组件数量增多,逻辑变简单时,一种常见的解耦形式是 —— 将可复用的逻辑从组件中抽离进去,放到独自的 Model
层。UI
间接调用 Model
层的办法。
对 Model
层的治理,也就是所谓的 状态治理。
对状态的治理,是比组件中 状态与 UI 的耦合 更高一级的形象。
状态治理问题
状态治理 要思考的最根本的问题是 —— 如何与框架实现原理尽可能符合?
比方,咱们要设计一个 User Model
,如果用class
的模式书写:
class User {
name: String;
constructor(name: string) {this.name = name;}
changeName(name: string) {return this.name = name;}
}
只须要将这个 Model
的实例包装为响应式对象,就能很不便的接入Vue3
:
import {reactive} from 'vue'
setup() {const user = reactive(new User('KaSong') as User;
return () => (<button onClick={() => user.changeName('XiaoMing')}>
{user.name}
</button>
)
}
之所以这么不便,诚如本文开篇提到的 —— Vue
的实现原理中,状态是 可变的数据 ,这与User Model
的用法是符合的。
同样的 User Model
要接入 React
则比拟艰难,因为 React
原生反对的是 不可变数据 类型的状态。
要接入 React
,咱们能够将同样的User Model
设计为不可变数据,采纳 reducer
的模式书写:
const userModel = {name: 'KaSong'};
const userReducer = (state, action) => {switch (action.type) {
case "changeName":
const name = action.payload;
return {...state, name}
}
};
function App() {const [user, dispatch] = useReducer(userReducer, userModel);
const changeName = (name) => {dispatch({type: "changeName", payload: name});
};
return (<button onClick={() => changeName('XiaoMing')}>
{user.name}
</button>
);
}
如果肯定要接入 可变类型状态 ,能够为React
提供相似 Vue
的响应式更新 能力后再接入。比方借用 Mobx
提供的响应式能力:
import {makeAutoObservable} from "mobx"
function createUser(name) {return makeAutoObservable(new User(name));
}
到目前为止,不论是 可变类型状态 还是 不可变类型状态 的Model
,都带来了 从组件中抽离逻辑 的能力,对于上例来说:
- 可变类型状态 将状态与逻辑抽离到
User
中 - 不可变类型状态 将状态与逻辑抽离到
userModel
与userReducer
- 最终裸露给
UI
的都仅仅是changeName
办法
当业务进一步简单,Model
自身须要更欠缺的架构,此时又是更高一级的形象。
到这一层时曾经脱离前端框架的领域,回升到纯状态的治理,比方为 mobx
带来结构化数据的mobx-state-tree
。
此时框架实现原理对 Model
的影响曾经在更高的形象中被抹去了,比方 Redux-toolkit
是React
技术栈的解决方案,Vuex
是 Vue
技术栈的解决方案,但他们在应用形式上是相似的。
这是因为 Redux
与Vuex
的理念都借鉴自 Flux
,即便React
与Vue
在实现原理上有区别,但这些区别都被状态治理计划抹平了。
更高的形象
在此之上,对于状态还有没有更高的形象呢?答案是必定的。
对于惯例的状态治理计划,依据用处不同,能够划分出更多细分畛域,比方:
- 对于表单状态,收敛到表单状态治理库中
- 对于服务端缓存,收敛到服务端状态治理库中(
React Query
、SWR
) - 用残缺的框架收敛前后端
Model
,比方Remix
、Next.js
总结
回到咱们开篇提到的问题:
- 为什么我的项目中用
Redux
而不必Mobx
? - 为什么要用
Hooks
而不必ClassComponent
?
当初咱们曾经能清晰的晓得这两个问题的相同点与不同点:
- 相同点:都与状态相干
- 不同点:属于不同形象层级的状态相干问题
要答复这些问题须要哪些常识呢?只须要晓得问题波及的 状态的形象层级 ,以及 比该层级更低的形象层级 对应的常识即可。
比方答复:为什么我的项目中用 Redux
而不必Mobx
?
思考以后形象层级
Redux
与 Mobx
都属于 Model
的实现,前者带来一套 类 Flux 的状态治理理念 ,后者为React
带来 响应式更新 能力,在设计 Model
时我的我的项目更适宜哪种类型?
或者两种类型我都不在乎,那么要不要应用更高形象的解决方案(比方MST
、Redux Toolkit
)抹平这些差别?
思考低一级形象层级
我的项目用的 ClassComponent
还是 FunctionComponent
?Redux
、Mobx
与他们联合应用时哪个组合更能协调好 UI
与逻辑的涣散耦合?
思考再低一级形象层级
React
的实现原理决定了他原生与 不可变类型状态 更亲和。Redux
更符合 不可变数据 ,Mobx
更符合 可变数据。我的我的项目须要思考这些差别么?
当理解不同形象层级须要思考的问题后,任何宽泛的、状态相干问题都能转化成具体的、多形象层级问题。
从不同形象层级登程思考,就能更全面的答复问题。