React-hooks 简介

什么是 Hooks?

不经过编写类组件的状况下,能够在组件内部使用状态(state) 和其余 React 特性(生命周期,context)的技术react

Hooks 为何会出现

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

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

State Hook

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

一个简单的使用 useState 栗子github

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

复制代码

几点说明:redux

  1. useState 推荐一种更加细粒度的控制状态的方式,即一个状态对应一个状态设置函数,其接受的参数将做为这个状态的初始值。其返回一个长度为2的元组,第一项为当前状态,第二项为更新函数。axios

  2. useState 的执行顺序在每一次更新渲染时必须保持一致,不然多个 useState 调用将不会获得各自独立的状态,也会形成状态对应混乱。好比在条件判断中使用 hook,在循环,嵌套函数中使用 hook,都会形成 hook 执行顺序不一致的问题。最后致使状态的混乱。另外,全部的状态声明都应该放在函数顶部,首先声明。数组

  3. useStatesetState 的区别网络

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

一个不正确的栗子async

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 中的函数,包括初次渲染以及后续的每一次更新渲染。

几点说明:

  1. useEffect(effectCallback: () => void, deps: any[]) 接收两个参数,第二个参数依赖项是可选的,表示这个 effect 要依赖哪些值。
  2. 有时候咱们并不想每次渲染 effect 都执行,只有某些值发生变化才去执行 effect,这个时候咱们能够指定这个 effect 的依赖列表,能够是一个也能够几个,当其中列表中的某一个值发生变化,effect 才会执行。
  3. 第一个参数的返回值,会在组件卸载时执行,至关于 componentWillUnmount,能够清理定时器,移除事件监听,取消一些订阅。
  4. 当第二个参数为一个空数组时,至关于 componentDidMount 和 componentWillUnmount,代表这个 effect 没有任何依赖,只在首次渲染时执行。

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 的讨论

相关文章
相关标签/搜索