Skip to content

React Hook 概述

很久很久之前,函数式组件由于没有 this,故不能获取 state, refs 等,因此函数式组件不被看好。但是 React 16.8 版本引入了全新的 API,叫做 React Hooks,颠覆了以前的用法,自此函数式组件崛起。

类式组件的缺点

React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。下面是一个简单的组件类。

jsx
import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

这个组件类仅仅是一个按钮,但可以看出代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。组件类主要有以下几个缺点:

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如 render props 和高阶组件。

函数式组件

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。因此组件的最佳写法应该是函数,而不是类。早期的 React 也是支持函数组件:

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

但是早期这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。然后在 v16.8 版本后,推出 React Hooks,即加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

React Hooks

Hook 是 React 16.8.0 版本增加的新特性/新语法,可以在函数组件中使用 state,refs 以及其他的 React 特性。React 在 v16.8 版本后,建议组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。React Hooks 就是那些钩子。

所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用 use 前缀命名,便于识别。即使用 xxx 功能,钩子就命名为 useXxx。需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,开发者也可以封装自己的钩子。

React 默认提供的四个最常用的钩子:

  • useState()
  • useContext()
  • useReducer()
  • useEffect()

函数式组件

概述

组件是 React 的核心概念之一,它们是构建用户界面(UI)的基础。

React 允许将标签、CSS 和 JavaScript 组合成自定义『组件』,即应用程序中可复用的 UI 元素

定义组件

React 组件是一段可以使用标签进行扩展的 JavaScript 函数。例如:

jsx
export default function Profile() {
  return (
    <img
      src="https://i.imgur.com/MK3eW3Am.jpg"
      alt="Katherine Johnson"
    />
  )
}

导出组件

export default 前缀是一种 JavaScript 标准语法(非 React 的特性)。它允许导出一个文件中的主要函数以便可以在其他文件引入它。

定义函数

使用 function Profile() { } 定义名为 Profile 的 JavaScript 函数。

Notes: React 组件是常规的 JavaScript 函数,但组件的名称必须以大写字母开头,否则它们将无法运行!

添加标签

示例中的组件,使用了 JSX 语法,在 JavaScript 中嵌入标签,并最终返回一个带有 srcalt 属性的 <img /> 标签。返回语句可以全写在一行上,如下面组件中所示:

jsx
return <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />;

如果标签和 return 关键字不在同一行,则必须把它包裹在一对括号中,如下所示:

jsx
return (
  <div>
    <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
  </div>
);

Notes: 没有括号包裹的话,任何在 return 下一行的代码都将被忽略

使用组件

定义了 Profile 组件后,就可以在其他组件中使用它。例如,可以导出一个内部使用了多个 Profile 组件的 Gallery 组件:

jsx
function Profile() {
  return (
    <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
  );
}

export default function Gallery() {
  return (
    <section>
      <h1>了不起的科学家</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

组件的注意事项

基于上面的示例:

  • <section> 是小写的,所以 React 知道指的是 HTML 标签。
  • <Profile /> 以大写 P 开头,所以 React 知道想要使用名为 Profile 的组件。

值得注意的是,最终在浏览器中渲染的都只有组件中包含的 html 标签,而不会看到自定义的标签(组件)。

嵌套和组织组件

概述

组件是常规的 JavaScript 函数,所以可以将多个组件保存在同一份文件中。当组件相对较小或彼此紧密相关时,这是一种省事的处理方式。随着这个文件变得臃肿,也可以随时将里面部分组件移动到单独的文件中。

嵌套陷阱

组件可以渲染其他组件,但是不要嵌套他们的定义。例如:

jsx
export default function Gallery() {
  // 🔴 永远不要在组件中定义组件
  function Profile() {
    // ...
  }
  // ...
}

上面这段代码非常慢,并且会导致 bug 产生。因此应该在顶层定义每个组件:

jsx
export default function Gallery() {
  // ...
}

// ✅ 在顶层声明组件
function Profile() {
  // ...
}

建议当子组件需要使用父组件的数据时,可以通过 props 的形式进行传递,而不是嵌套定义。

I know where dry desert ends, green grass grows · MooN