> For the complete documentation index, see [llms.txt](https://jimmylv.gitbook.io/tdd-frontend/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jimmylv.gitbook.io/tdd-frontend/coding/00-project-bookshelf/03-components-with-data-flow.md).

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

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

### React 的哲学

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

![UI = f(data)](/files/-M1jB7M1r9nvthlBNUcz)

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

1. React 界面完全由数据驱动；
2. React 中一切皆可为函数（即 `function`）；

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

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

```javascript
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>
  )
}
```

## 练习目标

* [ ] 使用 Cypress 驱动出组件 UI 及页面交互行为
* [ ] 利用 Mock Server 为 Cypress 提供 API 数据（via 上一节）
* [ ] 拆分组件树后，使用 React Hook 进行数据请求和状态管理
* [ ] 至少完成用户故事 1（书架首页） 和 用户故事 2（书籍详情）

## 本次任务的练习要求

* 注意，完全不要使用 React Class 组件
* 只允许使用 React 函数式组件，配合 React 自定义 Hook 足以
* 使用 React Router 官方 Hook（`useParams`、`useHisotry` 等）管理路由

比如：

```javascript
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 来获知这个函数声明的参数？

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

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

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

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

![KISS（keep it simple, stupid）](/files/-M4KsxNwk47BuaCsvQoy)

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

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

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

```javascript
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 再次检测其输出结果即可。

```javascript
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)
})
```
