前端搬砖,并不是一件很eazy的活
前端的活吧,要说难或者说简单,都不足以描述,我觉得用墨迹,麻烦,反反复复会更贴切。
究竟什么原因让前端搬砖有一种在战略上,不觉多难,真上手干了,就发觉大有玩泥巴之感呢?
我个人的观点,就是:
前端因为有了面子(UI),并且非常在意面子。
里子(逻辑)没多少,并且基本都围绕面子转。
导致因为面子,挂碍太大,里子又不强,变化受阻,从而导致越开发越费劲。
所以直觉告诉我,前端搬砖要想变得顺利:
那就别过分在意面子,多注重里子
弱化面子的挂碍,强化里子的主导能力,
让里子成为核心,尽可能的主导面子,不受其累。
"里子"主导"面子",如何实现?
先简单的说下前端搬砖大体内容是啥:
画页面
请求接口-驱动呈现
交互-请求接口-驱动呈现
然后再弄清楚以下两个问题
"面子"的场子究竟可以大到成什么程度
可以大到,什么事都在面子的场子里办了,而组成这个大场子的便是许许多多的组件。
无论是请求
还是组织请求后的数据,驱动显示
亦或是再请求,再组织,再驱动显示
甚至是组件与组件间通信等等
你能想到的前端大部分事,都会在那一亩三分地的组件里给你办了,可见面子已经大到了可怕的程度。
前端的里子是啥,能是啥?
里子简单说就是逻辑,抽象点就是可以组织,运转的能力,
通过上面对前端搬砖直白的描述,那么里子基本确定了内容:
请求接口+组织数据+驱动呈现
既然搞清楚了里子是啥,那就把里子从面子的场子里,拯救出来,做大做强。
实现策略
- 第一步:先分层
- 第二步:确定组成
- 第三步:划定职责
分层
- 里子:service
- 面子:UI
确定组成
- UI:单纯的组件堆砌
- service :集成状态管理库store +接口请求request
划定职责
- UI :就画页面做交互,其他的一概不管,具体业务全交给service层做,对UI提供action接口,UI层仅仅做触发并响应更新即可
- service :实现接口请求,组织构建状态数据树,驱动显示UI层,并为UI层提供触发接口action
这样,UI层和service层就分开了,
service层内部的stores各仓库可以互相监听,彼此独立,自主更新,互相不耦合。
技术选型
策略已经制定好了,那么围绕这个策略制定一下打法吧,开始挑选搭配技术架子吧。
- React:nextjs 和cra,服务端渲染和客户端渲染两手抓
- 状态管理库:Redux,老牌经典,贼稳贼够用
- 组件库:随意一点
- 客户端相关的,服务端渲染啥的用mui,好看,好定制,足够基础,方便整合
- 管理后台,无脑入antd,好用到无话可说
- 移动端用antd-mobile
- 小程序端用taro+taroUI
突破前端玩泥巴阶段,重点在状态管理
上面分析的策略,以及制定的强化"里子"的打法,最核心的一环就是状态管理库,串联起整个项目的业务,打通各环节流转的全得靠ta。
所以,重点强化状态管理,才是关键。
Redux什么都好,就是不好用
我是纯纯的直觉型选手,不喜欢看文档,搬砖全靠感觉,但凡不合直觉,别扭的地方,我第一想的是:设计的不合理,我要改到念头通达。
那么redux就是那种用起来,啥啥都不通达,或者说,离通达有一段距离,整体用下来,我的评价就是真不好用。
而且涉及到的库和文档巨多(就三个)
- redux
- redux-toolkit
- react-redux
我猜就算用过Redux搬砖的,能搞清楚上面三者的关系的不多,学习成本多少有点大。
然后就是ts的类型支持,这就更迷了,装载一波ts项目,你得花不少精力在ts类型支持上,使用成本真心不低。
为啥选Redux呢
- 稳:首先我要支持nextjs和cra,无论是服务端渲染场景还是纯客户端场景,我都要,那么稳定很重要,Redux这么经典,稳定我是信的。
- 强:我要实现的功能,Redux都有支持,比如我实现策略里需要仓库和仓库之间的监听,Redux就支持
以上两点就是我的主要考量,当然,我的打法固定了,选redux还是选别的状态管理库,都是不离其宗,都是为贯彻我的思路而服务的,所以,take it eazy,选一个靠谱一点redux,觉得哪里不好,就改进一波就行了。
改进Redux,直到念头通达
不通达的点
使用问题无外乎就三种:
- 获取相关:获取state,reducer和thunk
- 创建相关:包括创建store,slice,thunk等,以及复杂的TS类型支持
- 触发相关:触发action,包括同步的reducer和异步的thunk
遇到不通达的地方真不少,挑几个关键的问题,说说
获取state,reducer和thunks,并触发reducer和thunks
先贴出Redux的写法,鉴赏一下,简直槽点满满
ts
import { useState } from "react"
import { useAppSelector, useAppDispatch } from "../../app/hooks"
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from "./counterSlice"
import styles from "./Counter.module.css"
export function Counter() {
const count = useAppSelector(selectCount)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState("2")
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className={styles.button}
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
)
}
-
槽点1:我获取action,包括获取异步的thunk和同步的reducer,居然是通过import的方式
tsimport { decrement, increment, incrementByAmount, incrementAsync, incrementIfOdd, selectCount, } from "./counterSlice"
跟state的获取方式不一致,state是通过hook useAppSelector获取,action通过import。 为啥不能统一,都通过hook的方式将其获取出来,甚至state,reducer和thunk都用同一个hook一次获取出来。岂不更痛快。
-
槽点2:触发thunk action的方式太丑了
tsconst dispatch = useAppDispatch() dispatch(decrement())
dispatch(decrement())
这句话我简直无法接受,decrement执行完获得的结果传入dispatch,那么请问decrement存在执行完,不传给dispatch的场景么?我写来写去,怎么发现没有呢。。。,那么为啥不能把dispatch(decrement())
想办法变成decrement()
呢?这样不就更简洁了,我也不用每个action都dispatch
了,优雅,所以我心中的样子是:
并且,仓库和仓库之间的thunk调用,也无需引入,仅仅通过触发的方式即可
完全不用import引入过来,再dispatch,降低了模块和模块之间的依赖,非常的清爽,不耦合。
创建一个thunk,有必要这么仪式感么?
看下redux的创建thunk的写法
ts
export const incrementAsync = createAsyncThunk(
"counter/fetchCount",
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
},
)
"counter/fetchCount"
这个type好别致啊,不难猜counter是这个store(确切的说是slice)的命名空间,后面的这个就是这个thunk的代号了吧。
弱弱的思考一下,这怕不是需要手写吧?一个我勉强能忍,要是我多写几个thunk,岂不是我得写好几遍,起好几次名字? 还有这个type跟这个thunk的命名"incrementAsync"
,有必要起两个名字么,起一个不也行么?
我就是要创建thunk,你搞得也太形式感了,而我心中的样子是这样的:
dispatch触发的也太没灵魂了,TS提示呢?
Redux的dispatch写法
ts
dispatch(decrement())
苍白无力,毫无灵魂,我想要一个温柔,可爱的dispatch,她仿佛每次都可以贴心的询问我:
- 勇者,你需要操作哪个仓库?
- 勇者,你需要操作这个仓库的什么action?
- 勇者,好的,你选的这个action,需要传入的数据是这样的。
能不能做到,我想试试,所以,我心中的样子是这样的
询问我仓库是啥
询问我要做什么操作
告诉我,我需要传什么数据
无法轻易触发,重置整个Redux的行为
一个项目做下来,可能要有好几个独立的store存在,
一旦有个需求,就是重置所有仓库的状态,现有的api中并没有一个可以直接的办法重置,而我希望的样子是一个hook解决:
这样我就会在需要重置刷新redux的时候,直接一个hook都搞定了。
创建watch,让仓库之间的监听响应更自然,灵动
首先仓库之间的监听可能有很多种
有时候可能仅仅是监听另一个仓库的一个action变化,一个thunk或者一个reducer
有时候可能是创建一个matcher函数,判断多个action的匹配情况,从而响应
有时候也可能监听全局,只要变化,就执行一段逻辑,判断是否响应,进行更细粒度的判断
于是我希望的监听逻辑是这样的:
而且我希望的获取仓库的action的type不用我手写太多,仅仅通过一个函数获取,并且提供类型支持,防止我写错
并且还可以跳转,方便定位
dispatch一个thunk,获得的返回值,居然没有类型提示
原生的写法,我dispatch一个thunk,这个thunk是有返回值的
但是获取dispach之后的payload返回值,居然提示我unknow
这怎么忍啊,我心中的样子应该是这样的: 如果有返回值
那么获取的返回值,是可以提示我的
将心中所想,化为现实
为了能够实现的心中的理想,我实现了一个库,叫redux-eazy,意为让redux变简单。
Redux一下就板正了
类型支持一个setup即可
将Store的关注点分离
创建slice
创建thunks
创建api
创建监听
统一导出
项目已经开源了 github地址
其中实现了很多好用的api,并且提供了三个demo
- nextjs app模式
- nextjs page模式
- cra
基本全覆盖了,已经做了商用项目了。
其中不少类型体操,可以供大家参考,比如:
各种根据Redux提供的类型进行推导,从而实现期望的类型提示,相信能够给一些想研究ts整个开源库的小伙伴打开一点思路。
总结
事实上当我拥有了一套策略,并用redux-eazy贯彻,我几乎就不用再去看redux,redux-toolkit和react-redux文档一次,一次我都不带看的。
我就是这样:
希望用最少的力气,学最少的知识,实现足够的功能,主打一个不求甚解,够用就行。
最后的最后,聊聊我还做了啥
最近主要是搬砖,接活,为了能够更好,更稳,更高效的协作搬砖,我研究了一套ts全栈monorepo方案,包括:
- nextjs服务端渲染
- nestjs微服务后端
- antd管理后台
- vite+mui的客户端渲染
- taro3家政项目
- 问答小程序
- mui-eazy组件库
- redux-eazy状态库
等等
简单贴贴效果图
项目 | 效果图 |
---|---|
nextjs服务端渲染 | |
taro3项目 | |
antd管理后台 | |
问答小程序 | |
mui-eazy组件库 |
目前来看,驱动我的,好像就是搬砖了,为了能够行云流水的搬砖,不被玩泥巴锁住,那就得想办法进步,这样搬砖才能更顺畅,同时我也可以将任务分发出去,找小伙伴一起愉快的协作,提高效率,增加摸鱼时间,23333。
做的东西有点多,以后慢慢整理,把心得收获分享出来,供大家交流学习。
最后要是有什么问题,可以随时咨询我,我的小岛🏝️的地址:
- qq群:551406017
小岛不大,欢迎心中有爱的小伙伴上岛,这里有不少发光的小伙伴,分享收获,彼此助力,共同进步。
本岛只有一个铁律,就是尊重女性,不可瑟瑟。