Hey,你好,我是 JimmyLv 吕靖,这是你开始 前端 TDD 练习的第 21 天,ShoppingCart 所有组件都已经实现了,随着业务需求的慢慢实现,可能你会觉得基于组件内部 state 的方式已经有些繁琐。那么,接下来你将会进行下一步的重构,注意到数据流管理本质上其实是一种重构行为。
重构到设计模式
React 太适合 TDD 了,一切皆组件,组件之间靠 props 沟通,如何设计 props 即接口就成了最重要的事情。对于 React 来说,还可以直接把 props 用于渲染某个组件时的模板,作为 component props 传进去,即可减少把 props 列表传下去,(高阶组件)。
其实,你不用直接去引入 Redux,而是通过 TDD 的方式,驱动出一个 store 出来,直接通过 Hook 引入 state,然后再解决 props 传递的问题时,引入跨组件全局 store,这时候既可以通过原生 context 实现,也可以通过 redux 实现,甚至你还可以写 dva 来实现。但这些都只是 global store 的实现细节而已,你只是想要把这一层合理抽象出来。
设计模式的一个原则就是责任分离,而 React 在 2019 年基本实现了:
ErrorBoundary 实现了异常与 UI 分离
然后回到测试驱动开发的本真,演进式架构,重构到设计模式:action 和 reducer,selector 其实就是一个命令模式,关注于命令-查询职责分离(CQRS)。而对于 React + Hook 来说,其实就是 UI=f(data)
这一个简单的抽象模型而已,一切介组件,一切介函数。
这才是 Web 前端开发本来该有的样子,其实也顺便回答了 Vue 3.0 为什么长得像 React,三大框架开始趋同发展。是不是想突然感慨一下,写前端怎么这么简单哎?
练习目标
本次任务的练习要求
也请你思考一下
如何识别代码坏味道与重构?
谈论到何谓代码的坏味道,重复代码(Duplicated Code)首当其冲。重复在软件系统是万恶的,我们熟悉的分离关注点,面向对象设计原则等都是为了减少重复提高重用,Don’t repeat yourself(DRY)。关于 DRY 原则,我们在平时开发过程中必须要严格遵守。
提取自定义 Hook 就是在 React 当中消除重复代码(Duplicated Code)的一种手段,回头看看你的代码中还有什么其他坏味道呢?
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样适用这种方式。
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。 例如,下面的 useProductList 是我们第一个自定义的 Hook:
import { useEffect } from 'react'
import { useSelector } from 'react-redux'
import { getProductList, getTotalPrice } from '../store/product'
export default function useProductList() {
const dispatch = useDispatch()
const { category } = useParams()
useEffect(() => {
if (!loading) {
dispatch({
type: 'product/fetchProducts',
payload: { category },
})
}
}, [category])
const loading = useSelector((state) => state.loading.models.product)
const productList = useSelector(getProductList)
const totalPrice = useSelector(getTotalPrice)
return { loading, productList, totalPrice }
}
import React from 'react'
import useProductList from '../hooks/useProductList'
import ProductList from '../components/ProductList'
export default function MiniCart() {
const { loading, productList, totalPrice } = useProductList()
return (
<div>
{!loading && productList.length ? <ProductList products={productList} /> : <div>购物车为空</div>}
<div>总价:¥{totalPrice}</div>
</div>
)
}
参考资料
第 3 章 代码的坏味道 71
3.1 神秘命名(Mysterious Name) 72
3.2 重复代码(Duplicated Code) 72
3.3 过长函数(Long Function) 73
3.4 过长参数列表(Long Parameter List) 74
3.5 全局数据(Global Data) 74
3.6 可变数据(Mutable Data) 75
3.7 发散式变化(Divergent Change) 76
3.8 霰弹式修改(Shotgun Surgery) 76
3.9 依恋情结(Feature Envy) 77
3.10 数据泥团(Data Clumps) 78
3.11 基本类型偏执(Primitive Obsession) 78
3.12 重复的 switch(Repeated Switches) 79
3.13 循环语句(Loops) 79
3.14 冗赘的元素(Lazy Element) 80
3.15 夸夸其谈通用性(Speculative Generality) 80
3.16 临时字段(Temporary Field) 80
3.17 过长的消息链(Message Chains) 81
3.18 中间人(Middle Man) 81
3.19 内幕交易(Insider Trading) 82
3.20 过大的类(Large Class) 82
3.21 异曲同工的类(Alternative Classes with Different Interfaces) 83
3.22 纯数据类(Data Class) 83
3.23 被拒绝的遗赠(Refused Bequest) 83
3.24 注释(Comments) 84