前端搬砖这件事,讲究一个行云流水,不能总玩泥巴

前端搬砖,并不是一件很eazy的活

前端的活吧,要说难或者说简单,都不足以描述,我觉得用墨迹,麻烦,反反复复会更贴切。

究竟什么原因让前端搬砖有一种在战略上,不觉多难,真上手干了,就发觉大有玩泥巴之感呢?

我个人的观点,就是:

前端因为有了面子(UI),并且非常在意面子。

里子(逻辑)没多少,并且基本都围绕面子转。

导致因为面子,挂碍太大,里子又不强,变化受阻,从而导致越开发越费劲。

所以直觉告诉我,前端搬砖要想变得顺利:

那就别过分在意面子,多注重里子

弱化面子的挂碍,强化里子的主导能力,

让里子成为核心,尽可能的主导面子,不受其累。

"里子"主导"面子",如何实现?

先简单的说下前端搬砖大体内容是啥:

画页面

请求接口-驱动呈现

交互-请求接口-驱动呈现

然后再弄清楚以下两个问题

"面子"的场子究竟可以大到成什么程度

可以大到,什么事都在面子的场子里办了,而组成这个大场子的便是许许多多的组件。

无论是请求

还是组织请求后的数据,驱动显示

亦或是再请求,再组织,再驱动显示

甚至是组件与组件间通信等等

你能想到的前端大部分事,都会在那一亩三分地的组件里给你办了,可见面子已经大到了可怕的程度。

前端的里子是啥,能是啥?

里子简单说就是逻辑,抽象点就是可以组织,运转的能力,

通过上面对前端搬砖直白的描述,那么里子基本确定了内容:

请求接口+组织数据+驱动呈现

既然搞清楚了里子是啥,那就把里子从面子的场子里,拯救出来,做大做强。

实现策略

  • 第一步:先分层
  • 第二步:确定组成
  • 第三步:划定职责

分层

  • 里子:service
  • 面子:UI

确定组成

  • UI:单纯的组件堆砌
  • service :集成状态管理库store +接口请求request

划定职责

  • UI :就画页面做交互,其他的一概不管,具体业务全交给service层做,对UI提供action接口,UI层仅仅做触发并响应更新即可
  • service :实现接口请求,组织构建状态数据树,驱动显示UI层,并为UI层提供触发接口action

这样,UI层和service层就分开了,

service层内部的stores各仓库可以互相监听,彼此独立,自主更新,互相不耦合。

技术选型

策略已经制定好了,那么围绕这个策略制定一下打法吧,开始挑选搭配技术架子吧。

  • React:nextjscra,服务端渲染和客户端渲染两手抓
  • 状态管理库: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的方式

    ts 复制代码
    import {
        decrement,
        increment,
        incrementByAmount,
        incrementAsync,
        incrementIfOdd,
        selectCount,
      } from "./counterSlice"

    跟state的获取方式不一致,state是通过hook useAppSelector获取,action通过import。 为啥不能统一,都通过hook的方式将其获取出来,甚至state,reducer和thunk都用同一个hook一次获取出来。岂不更痛快。

  • 槽点2:触发thunk action的方式太丑了

    ts 复制代码
    const 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

小岛不大,欢迎心中有爱的小伙伴上岛,这里有不少发光的小伙伴,分享收获,彼此助力,共同进步。

本岛只有一个铁律,就是尊重女性,不可瑟瑟

相关推荐
生椰拿铁You几秒前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生11 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
baiduopenmap26 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish34 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_1 小时前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
guokanglun1 小时前
空间数据存储格式GeoJSON
前端
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium
猫爪笔记2 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html
brief of gali2 小时前
记录一个奇怪的前端布局现象
前端