React-hooks 简介

栏目: IOS · Android · 发布时间: 5年前

内容简介:不通过编写类组件的情况下,可以在组件内部使用状态(state) 和其他 React 特性(生命周期,context)的技术在之前的 React 版本中,组件分为两种:函数式组件(或无状态组件(StatelessFunctionComponent))和类组件,而函数式组件是一个比较的纯洁的state hook 提供了一种可以在 function component 中添加状态的方式。通过 state hook,可以抽取状态逻辑,使组件变得可测试,可重用。开发者可以在不改变组件层次结构的情况下,去重用状态逻辑。

什么是 Hooks?

不通过编写类组件的情况下,可以在组件内部使用状态(state) 和其他 React 特性(生命周期,context)的技术

Hooks 为什么会出现

在之前的 React 版本中,组件分为两种:函数式组件(或无状态组件(StatelessFunctionComponent))和类组件,而函数式组件是一个比较的纯洁的 props => UI 的输入、输出关系,但是类组件由于有组件自己的内部状态,所以其输出就由 propsstate 决定,类组件的输入、输出关系就不再那么纯洁。同时也会带来下列问题:

  1. 状态逻辑难以复用。很多类组件都有一些类似的状态逻辑,但是为了重用这些状态逻辑,社区提出了 render props 或者 hoc 这些方案,但是这两种模式对组件的侵入性太强。另外,会产生组件嵌套地狱的问题。
  2. 大多数开发者在编写组件时,不管这个组件有木有内部状态,会不会执行生命周期函数,都会将组件编写成类组件,后续迭代可能增加了内部状态,又增加了副作用处理,又在组件中调用了一些生命周期函数,文件代码行数日益增多,最后导致组件中充斥着无法管理的混乱的状态逻辑代码和各种副作用,各种状态逻辑散落在实例方法和生命周期方法中,维护性变差,拆分更是难上加难。
  3. 在类组件中,需要开发者额外去关注 this 问题,事件监听器的添加和移除等等。

State Hook

state hook 提供了一种可以在 function component 中添加状态的方式。通过 state hook,可以抽取状态逻辑,使组件变得可测试,可重用。开发者可以在不改变组件层次结构的情况下,去重用状态逻辑。更好的实现关注点分离。

一个简单的使用 useState 栗子

import React, { useState } from "react";

const StateHook = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>you clicked {count} times</p>
      <button type="button" onClick={() => setCount(count + 1)}>
        click me
      </button>
    </div>
  );
};

几点说明:

  1. useState 推荐一种更加细粒度的控制状态的方式,即一个状态对应一个状态设置函数,其接受的参数将作为这个状态的初始值。其返回一个长度为2的元组,第一项为当前状态,第二项为更新函数。
  2. useState 的执行顺序在每一次更新渲染时必须保持一致,否则多个 useState 调用将不会得到各自独立的状态,也会造成状态对应混乱。比如在条件判断中使用 hook,在循环,嵌套函数中使用 hook,都会造成 hook 执行顺序不一致的问题。最后导致状态的混乱。另外,所有的状态声明都应该放在函数顶部,首先声明。
  3. useStatesetState 的区别

    useStatesetState 进行覆盖式更新,而 setState 则将状态进行合并式更新。

一个不正确的栗子

import React, { useState, ChangeEvent } from "react";

const UserForm = () => {
  const [state, setUser] = useState({ name: "", email: "" });

  const { name, email } = state;

  const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { target: { value: name } } = event;
    // 这里不可以单独的设置某一个字段 新的状态必须与初始的状态类型保持一致
    // 如果只设置了其中一个字段,编译器会报错,同时其余的字段也会丢失
    setUser({ name, email });
  };

  const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { target: { value: email } } = event;
    // 这里不可以单独的设置某一个字段 新的状态必须与初始的状态类型保持一致
    setUser({ name, email });
  };

  return (
    <>
        <input value={name} onChange={handleNameChange} />
      <input value={email} onChange={handleEmailChange} />
    </>
  );
}

正确的做法

import React, { useState, ChangeEvent } from "react";

const UserForm = () => {
  // 一个状态对应一个状态更新函数
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { target: { value: name } } = event;
    // hear could do some validation
    setName(name);
  };

  const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { target: { value: email } } = event;
    // hear could do some validation
    setEmail(email);
  };

  return (
    <>
        <input value={name} onChange={handleNameChange} />
      <input value={email} onChange={handleEmailChange} />
    </>
  );
}

Effect Hook

数据获取,设置订阅,手动的更改 DOM,都可以称为副作用,可以将副作用分为两种,一种是需要清理的,另外一种是不需要清理的。比如网络请求,DOM 更改,日志这些副作用都不要清理。而比如定时器,事件监听。

一个简单使用 effect hook 去修改文档标题的栗子。

import React, { useState, useEffect } from "react";

const effectHook = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `you clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>you clicked {count} times</p>
      <button type="button" onClick={() => setCount(count + 1)}>
        click me
      </button>
    </div>
  );
};

在调用 useEffect 后,相当于告诉 React 在每一次组件更新完成渲染后,都调用传入 useEffect 中的函数,包括初次渲染以及后续的每一次更新渲染。

几点说明:

useEffect(effectCallback: () => void, deps: any[])

Custom Hook

也可以使用 useEffectuseState 实现自定义 hook。

一个给 DOM 元素添加事件监听器的栗子。

import { useRef, useEffect } from "react";

type EventType = keyof HTMLElementEventMap;
type Handler = (event: Event) => void;

const useEventListener = (
  eventName: EventType,
  handler: Handler,
  element: EventTarget = document
) => {
  // 这里使用 `useRef` 来保存传入的监听器,
  // 在监听器变更后,去更新 `useRef` 返回的对象的 `current` 属性
  const saveHandler = useRef<Handler>();

  useEffect(() => {
    saveHandler.current = handler;
  }, [handler]);

  useEffect(() => {
    const supported = element && element.addEventListener;
    if (!supported) {
      return;
    }

    const listener: Handler = (event: Event) => (saveHandler.current as Handler)(event);

    element.addEventListener(eventName, listener);

    return () => {
      element.removeEventListener(eventName, listener);
    };
  }, [eventName, element]);
}

一个使用 useReducer 来实现加、减计数器的栗子。这里虽然使用 useReducer 创建了类似 redux 的 功能,但是如果有多个组件都引用了这个 hook,那么这个 hook 提供的状态是相互独立、互不影响的,即 useReducer 只提供了状态管理,但是并没有提供数据持久化的功能。redux 却提供了一种全局维护同一个数据源的机制。所以可以利用 useReducerContext 来实现数据持久化的功能。

import React, { useReducer } from "react";

const INCREMENT = "increment";
const DECREMENT = "decrement";

const initHandle = (initCount) => {
  return { count: initCount };
};

const reducer = (state, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    case "reset":
      return { count: action.payload };
    default:
      return state;
  }
};

const Counter = ({ initialCount }) => {
  const [state, dispatch] = useReducer(reducer, initialCount, initHandle);
  const { count } = state;

  return (
    <div>
      Counter: {count}
      <button type="button" onClick={() => dispatch({ type: "reset", payload: initialCount })}>
        Reset
      </button>
      <button type="button" onClick={() => dispatch({ type: INCREMENT })}>
        +
      </button>
      <button type="button" onClick={() => dispatch({ type: DECREMENT })}>
        -
      </button>j
    </div>
  );
};

一个对封装数据请求栗子。

import { useState, useEffect } from "react";
import axios, { AxiosRequestConfig } from "axios";

interface RequestError {
  error: null | boolean;
  message: string;
}

const requestError: RequestError = {
  error: null,
  message: "",
};

/**
 * @param url request url
 * @param initValue if initValue changed, the request will send again
 * @param options request config data
 *
 * @returns a object contains response's data, request loading and request error
 */
const useFetchData = (url: string, initValue: any, options: AxiosRequestConfig = {}) => {
  const [data, saveData] = useState();
  const [loading, updateLoading] = useState(false);
  const [error, updateError] = useState(requestError);

  let ignore = false;

  const fetchData = async () => {
    updateLoading(true);

    const response = await axios(url, options);

    if (!ignore) saveData(response.data);

    updateLoading(false);
  };

  useEffect(() => {
    try {
      fetchData();
    } catch (error) {
      updateError({ error: true, message: error.message });
    }

    return () => {
      ignore = true;
    };
  }, [initValue]);

  return { data, loading, error };
};

export { useFetchData };

Rules of Hook

随来 hooks 带来了新的组件编写范式,但是下面两条规则还是要开发者注意的。

  1. 在顶部使用 hook,不要使用 hook 在条件判断,循环,嵌套函数。
  2. 只在 function component 中使用 hook,或者自定义 hook 中使用 hook, 不要在常规的 JavaScript 函数中使用 hook

新的问题

hooks 的带来,虽然解决之前存在的一些问题,但是也带来了新的问题。

  1. 异常捕获。之前的版本中,我们可以用 componentDidCatch 来捕获组件作用域内的异常,做一些提示。但是在 hooks 中 ,我们只能使用 try {} catch(){} ` 去捕获,使用姿势也比较别扭。
  2. 一个组件若有状态,则状态一旦改变,所有的子组件需要重新渲染。所以一个有状态的组件,应该是没有子组件的。即 有状态的组件不做渲染,有渲染的组件没有状态
  3. 状态变更的函数不支持回调。 this.setState() 中支持第二个参数,允许我们在状态变更后,传入回调函数做一些其他事情。但是 useState 不支持。 详见

参考链接

making-sense-of-react-hooks

rehooks

awesome-react-hooks

如何使用useEffect来获取数据

hooks 是如何工作的

更多关于 hooks 的讨论


以上所述就是小编给大家介绍的《React-hooks 简介》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Docker开发指南

Docker开发指南

[英] Adrian Mouat / 黄彦邦 / 人民邮电出版社 / 2017-4 / 79.00元

Docker容器轻量和可移植的特性尤其适用于动态和分布式的环境,它的兴起给软件开发流程带来了一场革命。本书对Docker进行了全面讲解,包括开发、生产以至维护的整个软件生命周期,并对其中可能出现的一些问题进行了探讨,如软件版本差异、开发环境与生产环境的差异、系统安全问题,等等。一起来看看 《Docker开发指南》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具