React Hooks

介绍 于react 16.8版本引入,主要功能在于让你无需建立一个类定义,便可使用 state等react特性。

是什么

Hook 是一些可让你在==函数组件==里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。html

为何使用hooks

  1. 逻辑代码难以在组件间复用react

    • render propsnpm

      • 须要从新组织代码结构,使得代码更加定制化,难以维护。
    • higher-order components
  2. 非class模式下,使用更多的react特性编程

    • Hook 将组件中相互关联的部分拆分红更小的函数(好比设置订阅或请求数据)

优点

  1. 没有破坏性改动数组

    • 向后兼容
    • 可选
  2. 函数式编程

API

useState

函数声明

useState:< S >(initialState: S | (() => S))=> [S, Dispatch<SetStateAction< S >>];浏览器

  • SetStateAction< S > = S | ((prevState: S) => S);
  • 复用逻辑,不复用数据,这意味着你在多处使用使用了useState的组件,获取的数据并非同一个,全部 state 和反作用都是彻底隔离的。
  • 调用Dispatch<SetStateAction< S >>会触发函数组件的从新渲染。

useEffect

函数声明

useEffect:(effect: ()=>?()=>void, deps?: DependencyList)=> void;缓存

  • 组件销毁后,会调用effect返回的清洁函数(若是有)来取消反作用(好比订阅,计时器等)
  • deps标识effect所依赖的值数组。若为空数组[],则仅在第一次渲染后调用,并在卸载前销毁。(此时更相似componentDidMountcomponentWillUnmount
  • 每次渲染后调用,==包括==第一次(React类生命周期中,componentDidMountcomponentDidUpdate的合集)性能优化

    • 为何须要在每一次更新后执行,参考以下代码
    componentDidMount() {
            ChatAPI.subscribeToFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
          }
        
          componentWillUnmount() {
            ChatAPI.unsubscribeFromFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
          }
          //假如在在组件展现在屏幕上时,friend.id变化了。此时组件没有正常取消原来的订阅逻辑,同时在取消订阅时传递了错误的好友id,可能致使一些bug
    • 若是在class组件中
    //需添加 componentDidUpdate 来解决这个问题
    componentDidUpdate(prevProps) {
        if(prevPropsfriend.id !== this.props.friend.id){
            // 取消订阅以前的 friend.id
            ChatAPI.unsubscribeFromFriendStatus(
              prevProps.friend.id,
              this.handleStatusChange
            );
            // 订阅新的 friend.id
            ChatAPI.subscribeToFriendStatus(
              this.props.friend.id,
              this.handleStatusChange
            );
        }
    }
    • 使用Hook的函数组件
    function FriendStatus(props) {
      // ...
      useEffect(() => {
        // ...
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      },[props.friend.id]);//仅在friend.id改变时更新
  • 传递给 useEffect 的函数在每次渲染中都会有所不一样,这是刻意为之的。事实上这正是咱们能够在 effect 中获取最新的的值,而不用担忧其过时的缘由。每次咱们从新渲染,都会生成新的 effect,替换掉以前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每一个 effect “属于”一次特定的渲染。闭包

    • 这里使用匿名函数做为参数传递给effect
  • useEffect会在每次浏览器绘制后,且下一次绘制前执行函数式编程

    • 与 componentDidMount 或 componentDidUpdate 不一样,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数状况下,effect 不须要同步地执行。在个别状况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

useMemo

函数声明

useMemo<T>(factory: () => T, deps: DependencyList | undefined)=> T;

  • 保存上一次的计算结果,deps数组内依赖项改变时计算memoized
  • 传入 useMemo 的函数会在渲染期间执行,可用于缓存子节点的渲染结果。
const Button = React.memo((props) => {
  // 你的组件
});

useCallback

函数声明

useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

  • useCallback(fn, deps) 至关于 useMemo(() => fn, deps)。
  • 可用于避免匿名函数形成的重复渲染。
function MyComponent(props) {
    const clickCallback = React.useCallback(() => {
        // ...
    }, []);
    // 这里若是直接传递匿名函数,会形成每次渲染结果与上一次不一致
    // 在这个例子中:MyComponent会从新渲染,但button不会
    return <button onClick={clickCallback}>Click Me!</button>;
}

useRef

函数声明

useRef<T>(initialValue: T)=> MutableRefObject<T>;

  • useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)

    • useRef() 和自建一个 {current: ...} 对象的惟一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
    • 本质上,useRef 就像是能够在其 .current 属性中保存一个可变值的“盒子”。因为每个函数组件都是一个闭包,useRef实现了一套穿透闭包的逻辑。
  • 若是你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则不管该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
  • 能够用于获取上一轮渲染的props或者state(prePorpspreState)。
function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}

// 或者使用自定义hook
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

React 是如何把对 Hook 的调用和组件联系起来的?

  • React 保持对当先渲染中的组件的追踪。多亏了 Hook 规范,咱们得知 Hook 只会在 React 组件中被调用(或自定义 Hook —— 一样只会在 React 组件中被调用)。
  • 每一个组件内部都有一个「记忆单元格」列表。它们只不过是咱们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),而后把指针移动到下一个。这就是多个 useState() 调用会获得各自独立的本地 state 的缘由。
  • React依赖Hook调用的顺序来确保stateuseState的对应关系。

逻辑共享

按照官方描述,这个是 自定义Hook
  • 自定义 Hook 是一个函数,其名称必须以“use” 开头,函数内部能够调用其余的 Hook。

    • 不然React没法判断某个函数是否包含对其内部 Hook 的调用,React 将没法自动检查你的 Hook 是否违反了Hook 的规则。
import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
//第一处复用
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
//第二处复用
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

注意

  1. 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用。
  2. 只能在 React 的函数组件中调用 Hook。不要在其余 JavaScript 函数中调用。(还有一个地方能够调用 Hook —— 就是自定义的 Hook 中)
  3. useCallbackuseMemo主要用于性能优化,不要过早进行性能优化。不然没法比较优化结果,极可能某个不注意的角落反而会致使性能下降。
  4. eslint-linter 插件用于匹配以上规则
  5. 与 class 组件中的 setState 方法不一样,useState 不会自动合并更新对象。你能够用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

    setState(prevState => {
      // 也可使用 Object.assign
      return {...prevState, ...updatedValues};
    });
相关文章
相关标签/搜索