♻️
前端 TDD(测试驱动开发)
  • README
  • 总-课程目标
  • 总-课程安排
  • Coding
    • 第一题:FizzBuzz 数字游戏
      • 什么是 FizzBuzz?
      • 任务 0:练功前的热身
      • FizzBuzz 项目剖析
      • 任务 1:TDD 初体验
      • 任务 2:先利其器
      • 任务 3:再撸一遍
      • FizzBuzz 项目总结
      • 附录 1:Jest 测试基础及要点
      • 附录 2:Testing Library 组件测试基础
    • 第二题:MarsRover 火星车
      • 任务 1:Tasking 任务分解
      • 火星车实战
      • 火星车 Tasking 任务分解
      • 任务 2:MarsRover 实战演练
      • MarsRover Coding 演示
      • 任务 3:MarsRover 练习
      • MarsRover 项目总结
    • 第三题:CommentBox 留言板
      • CommentBox 项目剖析
      • 任务 1:Cypress E2E 测试
      • 任务 2:测试驱动组件单元接口
      • 任务 3:组件级别的快速反馈
      • 任务 4:由外到内的前端 TDD
      • CommentBox 项目总结
    • 第四题:Bookshelf 魔法书架
      • Bookshelf 项目剖析
      • 任务 1:练习 API 契约测试
      • 任务 2:组件化与数据流管理
      • 任务 3:Redux 数据流测试
      • 任务 4:简化 Redux 项目结构
      • Bookshelf 项目总结
      • 附录 1:什么才是真正的 RESTful 架构?
      • 附录 2:【译】Redux 和 命令模式
      • 附录 3:【译】什么是 Flux 架构?(兼谈 DDD 和 CQRS)
    • 第五题:ShoppingCart 购物车
      • ShoppingCart 项目剖析
      • React 哲学:Thinking in React
      • 任务 1:任务分解 - 驱动组件树拆分
      • 任务 2:综合应用 - 驱动组件接口设计
      • 任务 3:综合应用 - 驱动数据流管理
      • 任务 4:综合应用 - 驱动组件样式开发
      • ShoppingCart 项目总结
由 GitBook 提供支持
在本页
  • React 的哲学
  • 练习目标
  • 本次任务的练习要求
  • 也请你思考一下
  • 一切皆函数意味着什么呢?
  • 纯函数与模块易测性的关系?
  1. Coding
  2. 第四题:Bookshelf 魔法书架

任务 2:组件化与数据流管理

上一页任务 1:练习 API 契约测试下一页任务 3:Redux 数据流测试

最后更新于5年前

Hey,你好,我是 JimmyLv 吕靖,这是你开始 前端 TDD 练习的第 16 天。今天你会进入前两个页面的业务需求 TDD 开发当中去,期望你能够从 API 接口设计出发,由外到内得把整个 TDD 流程串通起来。

React 的哲学

学习 React 的基础,简单说来用这个公式就可以表示:UI = f(data)。

要真正理解 React,最关键的就是以下两点:

  1. React 界面完全由数据驱动;

  2. React 中一切皆可为函数(即 function);

如果要渲染界面,不要直接去操纵 DOM 元素,而是修改数据,由数据去驱动 React 来修改界面。可以理解为用户界面 UI 就是由一个个函数/组件组成的,然后呢,借助于 React Hook,甚至连不是 UI 的功能都是组件,比如获取数据这样的副作用,都可以包装在一个函数里面,因此代码结构会更加容易理解和维护。

React Hooks 的意思就是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。

import React, { useState } from 'react'

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

练习目标

本次任务的练习要求

  • 注意,完全不要使用 React Class 组件

  • 只允许使用 React 函数式组件,配合 React 自定义 Hook 足以

  • 使用 React Router 官方 Hook(useParams、useHisotry 等)管理路由

比如:

import { useHistory } from 'react-router-dom'

function HomeButton() {
  let history = useHistory()

  function handleClick() {
    history.push('/home')
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  )
}

也请你思考一下

当你使用 React 组件时,你更关心数据,还是更关心页面 DOM 操作?相比于 jQuery 的优势在哪里?

声明式编程(Declarative Programming) over 命令式编程(Imperative Programming)。

一切皆函数意味着什么呢?

在 JavaScript 的世界里,一切皆是对象,甚至连一个函数都是一个对象。

比如,你是否知道,你可以访问一个函数的 length 来获知这个函数声明的参数?

function foo(a, b) {
  return a + b
}

console.log(foo.length) // 输出为2

因为函数本身就是对象,所以,在 JavaScript 中,函数拥有一等公民的地位。而在 React 中一切皆为组件,函数就是一等公民。

有一条简单的原则,我相信你也会受用,Unix 哲学告诉你:做一件事,做好它。

纯函数与模块易测性的关系?

纯净的输入输出,所带来的是简易的测试准备。比如购物车“计算总价格”这样的一个功能,其实测试本身根本不用关注其内部实现:你可以用 reduce 实现,也可以自己写 for 循环实现。只要测试输入没有变,输出就不应该变。这个特性,是测试支撑重构的基础。因为重构指的是,在不改变软件外部可观测行为的基础上,调整软件内部的实现。

而对于 UI 组件来说,其 props 不变,相应的 UI 界面输出就不应该变,从而对于 UI 组件的静态输出(即 virtual DOM -> DOM)来说,你只需要关心它在不同 props 的输入条件下,所展现出来的不同结构及样式,即前文所提到的 UI stories 穷举,利用 Storybook 进行组件驱动开发,会获得更容易验证,以及更快的开发反馈等好处。

export default { title: 'Counter' }
export const enabled = () => <Counter text="Enabled" />
export const disabled = () => <Counter disabled text="Disabled" />

当然,除了组件 props 以外,还有一类隐含的输入,即 User Interaction 用户交互,当页面上进行点击,组件会监听其事件进行响应,此时你就更关心组件的行为。利用 Testing Library 你可以测试 User Event,根据不同的操作行为,组件会有不同的响应结果,即输出。有时候是简单的一个界面变化,比如 Toggle,有时则会触发新的数据请求,或是输入数据进行提交,当然也会有可能引发页面跳转等交互行为。此时,你只需要根据 Testing Library 再次检测其输出结果即可。

import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('should toggle dark mode', () => {
  const handleToggle = jest.fn()
  const comp = render(<Toggle yes="白天" no="黑夜" onToggle={handleToggle} />)

  userEvent.clcik(comp.getByText('白天'))

  expect(comp.getByText('黑夜')).toBeInTheDocument()
  expect(handleToggle).toBeCalledWith(false)
})
UI = f(data)
KISS(keep it simple, stupid)