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

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(useParamsuseHisotry 等)管理路由

比如:

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

最后更新于