react hooks详解

简介: 分析react开发过程中,经常用到的hook方法

react hook

动机

● 嵌套地狱(the wrapper hell) - 在组件之间复用状态逻辑困难
● 逻辑分离 - 复杂组件变得难以理解
● class - 难以理解的 class

函数组件

定义函数组件时,通过函数的参数来传递属性,一般我们设为props。

// Function component
function HelloComponent(props) {
  const { name } = props;
  return <p>Hello {name}!</p>
}

<HelloComponent name="World" />

函数组件特性

● 没有状态,组件里的逻辑比较简单 (没有React Hook API之前)
● 当props得到更新时,重新渲染组件,因此计算复杂性比较高的函数,不建议放在函数组件里,(可暂存计算结果)。
● 复用性比较高

hook

利用Hook API,函数组件可以管理组件自身的状态,可以执行副作用函数。在一些特定的场景下,让函数组件更优雅地方式处理逻辑。
● useState
● useEffect
● useContext
● useReducer
● useCallback
● useMemo
● useRef
● useImperativeHandle
● useLayoutEffect
● useDebugValue

注意

● 类组件无法使用Hook API
● 函数组件支持大部分场景,但现阶段无法完全替代类组件
● 只许在函数组件里的top层执行Hooks,即不能在判断,循环,或者函数里执行hooks。
● 只许在函数组件,或者自定义hook里可以执行hooks,不可以在普通的函数里执行hooks。

useState Hook

useState Hook支持函数组件定义状态,并提供更新状态的函数。

const [state, setState] = useState(initialState);

useState的参数initialState是状态state的初始值,useState返回的值是数组,数组的第一个元素状态的初始值,第二个元素是赋值函数setState。一般利用解构赋值来给状态变量和赋值函数定义名称。
● 每当重新渲染时,赋值函数useState返回的状态值(第一个元素)是函数setState最近更新的值
● 状态state的值可以是基本类型
● 可以定义多个状态
● 当useState返回的赋值函数setState的参数,与当前的state相同时,组件不会重新渲染。相反,类组件里通过setState来更新状态时,不管state是否相同,组件都会执行渲染函数(setState的参数不是null或者undefined,没有继承PureComponent,或shouldComponentUpdate中没有添加判断条件)。

// Function Component
import React, { useState } from "react";
const Counter = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};

初始化函数 Lazy initial state

useState参数可以是一个函数,函数返回的值是状态的初始值。该初始化函数的用处是当组件只有首次渲染时,执行初始化函数,后续不会再执行,因此我们可以通过这个特性来优化组件,使减少不必要的重复计算。

const [state, setState] = useState(() => heavyComputation(props));

setState参数 Functional updates

可以选择函数作为函数setState的参数,这时,函数的参数为当前的状态state,返回的值想要更新的值。

useEffect(() => {
    const intervalId = setInterval(() => {
      setTime(timeStr => timeStr + 1000);
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
}, []);

useReducer

useReducer类似于useState,useReducer返回的第一个元素是状态state,第二个元素是dispatch。

const reducer = (state, action) => newState;

const [state, dispatch] = useReducer(reducer, initialArg, init);

● 状态state变化依赖于前一个状态
● 状态state为复杂类型
● 把复杂的逻辑放到reducer来单独管理,提高组件的复用性和可扩展性

  switch (action.type) {
    case "INCREMENT":
      return state + 1;

    case "DECREMENT":
      return state - 1;

    default:
      break;
  }
};

const Counter = () => {
  const [count, dispatch] = useReducer(reducer, 0);

  const add = () => {
    dispatch({
      type: "INCREMENT"
    });
  };

  const substract = () => {
    dispatch({
      type: "DECREMENT"
    });
  };

  return (
    <div>
      <h1>Counter {count}</h1>

      <button onClick={add}>+1</button>

      <button onClick={substract}>-1</button>
    </div>
  );
};

export default Counter;

初始化函数 Lazy Initialization

与useState一样,useReducer也可以通过初始化函数来获得初始值。
● 只有首次渲染时执行函数,返回的值是state的初始值。
● 第二个参数变为初始化函数的参数
● 第三个参数是初始化函数

function init(initialCount) {
  return {count: initialCount};
}

const [state, dispatch] = useReducer(reducer, initialCount, init);

useState Vs useReducer

useState适用场景

  1. 状态state是基本类型
  2. 组件的逻辑比较简单
  3. 组件的各个状态之间相互没有联系,并通过各自的useState来管理

useReducer适用场景

  1. 状态state是对象(组件的几个状态绑定在一起)或者数组
  2. 组件逻辑较复杂,想把逻辑封装到reducer里
  3. 对测试更友好

Effect Hook

useEffect支持函数组件可执行副作用函数或异步任务,比如调取接口,setInterval等。
● 函数组件渲染结束之后,执行useEffect
● 默认情况下,每渲染组件时都会执行useEffect
● 函数组件里可以有多个useEffect

清理工作 Cleanup

类组件中,在componentWillUnmount里做一些清理工作,类似的,在函数组件里可以做清理工作。当函数组件重新渲染或销毁前,会执行useEffect返回的函数。

// function component with cleanup
useEffect(() => {
    const intervalId = setInterval(() => {
      setTime(timeStr => timeStr + 1000);
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
});

注意:默认情况下,重新渲染导致执行useEffect里的回调函数和返回的函数。

useEffect的第二个参数

useEffect的第二个参数为空时,组件每次重新渲染都会执行useEffect里的回调函数。有时,我们不需要每次重新渲染时,执行useEffect,当useEffect里的某几个变量发生变化时,再执行useEffect,这样就可以减少无用的计算。

const { num } = props;

useEffect(() => {
    const value = getFibonacciNum(Number(num));

    setComputedValue(value);
}, [num]);

注意
● useEffect是用了Object.is()方法来判断变量是否发生变化。
● useEffect里用到的变量都需要加到第二个参数数组里才能正确执行。
● 当useEffetc的第二个参数是空数组时,只有首次渲染时执行useEffect。

Custom Hooks

有Hooks之前,组件通过高阶组件或者render属性来复用代码,这会容易导致嵌套地狱问题。利用自定义hook也可实现复用代码,而且比高阶组件更优雅。

开发人员可以创建自定义hook,利用已有的hooks,把复用的功能从组件里分离出来,并且把这些功能按需添加到函数组件。

自定义hook的函数名以"use"为开头,并在函数体里用到了react的hooks,linter插件会自动按照hook的标准检查自定义hook。

注意
● 推荐hook名称用"use"来开头
● 两个组件不会共享自定义hook里的状态
● 组件渲染完成后,执行自定义组件里的useEffect

举例 - useDataApi

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });
  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });
      try {
        const result = await axios(url);
        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };
    fetchData();
  }, [url]);

    return [state, setUrl];
};

定义Reducer

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error();
  }
};

调用useDataApi

const [{ data, isLoading, isError }, setUrl] = useDataApi(url);

useRef

useRef返回的是简单对象(plain object),且对象的current属性值为useRef的参数。直到函数组件销毁前,该对象一直存在。对象ref有点类似于类组件的this,可以用作容器来使用。当改变对象ref时,不会引起渲染。

// 保存之前状态state
function Demo() {
  const [state, setState] = useState(0);

  const prevCountRef = useRef();
  
  useEffect(() => {
    prevCountRef.current = state; 
  }, [state]);
  
  //...
}

注意
● 不要在渲染期间,更新ref对象

Hooks 性能优化

组件性能优化的思路,主要通过以下两种方法来实现
● 缓存组件 - 减少不必要的render次数,执行render时,React会触发协调(Reconciliation)。
● 缓存计算结果- 减少不必要的重复计算

useMemo - 减少不必要的重复计算

useMemo可以缓存计算结果,函数的参数不变时,useMemo返回缓存的结果,只有当函数的参数发生变化时,才会重新计算。

第二个参数是函数的依赖项,一般把函数参数加到依赖项当中,显然当依赖项为空数组时,useMemo只返回第一次计算的结果。

const memoizedValue = useMemo(() => heavyComputation(a, b), [a, b]);

也可以缓存组件

const memoizedMyComponent = useMemo(() => <MyComponent value={value} />, [value]);

React.memo - 减少不必要的render

memo是一个高阶组件,当组件的props没有发生变化时,组件不会再执行render方法,返回上一次render的结果。

const MyComponent = props => {}

export default React.memo(Mycomponent);

默认情况下,memo对比props时是浅对比(shallow compare),我们可以自定义对比函数。props相同时返回true, memo返回已缓存的组件;不同时返回false,返回更新的组件。需要注意的是,逻辑与shouldComponentUpdate正好相反。

const isEqual = (prevProps, nextProps) => {
    // compare the arguments
    if (prevProps相等于nextProps) {
        return true;
    } else {
        return false;
    }  
};

export default React.memo(Mycomponent, isEqual);

useCallback - 减少不必要的render

把函数以及依赖项作为参数传入 useCallback,将返回函数的 memoized 版本,useCallback只有当依赖项有变化的时候返回新的函数。

const callback = () => {
  doSomething(a, b);
}

const memoizedCallback = useCallback(callback, [a, b])
相关文章
|
4天前
|
前端开发 测试技术 开发工具
探索前端框架React Hooks的优势与应用
本文将深入探讨前端框架React Hooks的优势与应用。通过分析React Hooks的特性以及实际应用案例,帮助读者更好地理解和运用这一现代化的前端开发工具。
|
4天前
|
前端开发 JavaScript
react常用的hooks有哪些?
react常用的hooks有哪些?
16 0
|
4天前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
2天前
|
前端开发
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
|
2天前
|
前端开发 JavaScript
React Hooks:让你轻松掌握函数组件的状态与管理
React Hooks:让你轻松掌握函数组件的状态与管理
|
4天前
|
缓存 前端开发
Web开发:深入探讨React Hooks的使用和最佳实践
Web开发:深入探讨React Hooks的使用和最佳实践
10 0
|
4天前
|
存储 前端开发 JavaScript
React Hooks实战:从useState到useContext深度解析
React Hooks 深度解析:useState用于函数组件的状态管理,通过初始化和更新状态实现渲染控制;useContext则提供跨组件数据传递。useState的状态更新是异步的,不支持浅比较,可结合useEffect处理副作用。useContext在多层组件间共享状态,但可能导致不必要的渲染。两者结合可创建复杂应用场景,如带主题切换的计数器。了解其工作原理和优化策略,能有效提升React应用性能。
20 0
|
4天前
|
前端开发 API 开发者
React Hooks API:自定义Hooks的创建与使用
【4月更文挑战第25天】本文介绍了React自定义Hooks的创建与使用。自定义Hooks是提升React开发效率的关键工具。
|
4天前
|
前端开发
探索React Hooks:一种全新的组件逻辑管理方式
React Hooks是React 16.8版本引入的一项新功能,它改变了我们编写React组件的方式。本文将从Hooks的起源讲起,逐步分析Hooks的优势,并通过具体示例展示Hooks在组件逻辑管理中的应用,旨在帮助读者更好地理解和运用React Hooks。
|
4天前
|
前端开发 JavaScript
使用React Hooks实现简单的计数器应用
使用React Hooks实现简单的计数器应用

热门文章

最新文章