# 任务 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)
})
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jimmylv.gitbook.io/tdd-frontend/coding/00-project-bookshelf/03-components-with-data-flow.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
