React Hooks 知识点总结

Hook 简介

Hook出世以前React存在的问题

  1. 在组件之间复用状态逻辑很难html

    React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件链接到 store)。有一些解决此类问题的方案,好比 render props高阶组件。可是这类方案须要从新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。node

  2. 复杂组件变得难以理解react

    组件经常在 componentDidMountcomponentDidUpdate中获取数据。可是,同一个 componentDidMount 中可能也包含不少其它的逻辑,如设置事件监听,而以后需在 componentWillUnmount 中清除。相互关联且须要对照修改的代码被进行了拆分,而彻底不相关的代码却在同一个方法中组合在一块儿。如此很容易产生 bug,而且致使逻辑不一致。ajax

  3. 难以理解的 class算法

    class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工做方式,这与其余语言存在巨大差别。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码很是冗余。你们能够很好地理解 props,state 和自顶向下的数据流,但对 class 却束手无策。编程

Hook带来的解决方案

  1. 你可使用 Hook 从组件中提取状态逻辑,使得这些逻辑能够单独测试并复用。Hook 使你在无需修改组件结构的状况下复用状态逻辑。
  2. Hook 将组件中相互关联的部分拆分红更小的函数(好比设置订阅或请求数据),而并不是强制按照生命周期划分。你还可使用 reducer 来管理组件的内部状态,使其更加可预测。
  3. Hook 使你在非 class 的状况下可使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。

Hook API

useState

useState 是react自带的一个hook函数,它的做用就是用来声明状态变量。useState这个函数接收的参数是咱们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是能够改变状态值的方法函数。segmentfault

初始化

//返回一个 state,以及更新 state 的函数 setState(接收一个新的 state 值并将组件的一次从新渲染加入队列)
const [state, setState] = useState(initialState);
复制代码

函数式更新

//若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> </> ); } 复制代码

惰性初始 state

//若是初始 state 须要经过复杂计算得到,则能够传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
复制代码

跳过 state 更新

调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)api

useEffect

咱们写的有状态组件,一般会产生不少的反作用(side effect),好比发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。咱们以前都把这些反作用的函数写在生命周期函数钩子里,好比componentDidMountcomponentDidUpdatecomponentWillUnmount。而如今的useEffect就至关与这些声明周期函数钩子的集合体。它以一抵三。数组

简单例子

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 相似于componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 更新文档的标题
    document.title = `You clicked ${count} times`;
  });

  return (
    <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
  );
}
复制代码

清除 effect

一般,组件卸载时须要清除 effect 建立的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。如下就是一个建立订阅的例子:浏览器

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});
复制代码

为防止内存泄漏,清除函数会在组件卸载前执行。另外,若是组件屡次渲染(一般如此),则在执行下一个 effect 以前,上一个 effect 就已被清除

effect 的执行时机

componentDidMountcomponentDidUpdate 不一样的是,在浏览器完成布局与绘制以后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的反作用场景,好比设置订阅和事件处理等状况,所以不该在函数中执行阻塞浏览器更新屏幕的操做。

effect 的条件执行

默认状况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被从新建立。在某些状况下,咱们不须要在每次组件更新时都建立新的订阅,而是仅须要在 source prop 改变时从新建立。要实现这一点,能够给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。

//此时,只有当 props.source 改变后才会从新建立订阅。(要实现componentDidMount功能只须要设置第二个参数为[]便可)
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
复制代码

useContext

能够深层组件传值,父组件传给子孙组件。接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即便祖先使用 React.memoshouldComponentUpdate,也会在组件自己使用 useContext 时从新渲染。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } 复制代码

useReducer

useState 的替代方案,能够用于复杂状态处理。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(若是你熟悉 Redux 的话,就已经知道它如何工做了。)

指定初始 state

有两种不一样初始化 useReducer state 的方式,你能够根据使用场景选择其中的一种。将初始 state 做为第二个参数传入 useReducer 是最简单的方法:

//nst [state, dispatch] = useReducer(reducer, initialArg, init);
 const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
 );
复制代码

某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于以前的 state 等。而且,使用 useReducer 还能给那些会触发深更新的组件作性能优化,由于你能够向子组件传递 dispatch 而不是回调函数

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); } 复制代码

惰性初始化

你能够选择惰性地建立初始 state。为此,须要将 init 函数做为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)

这么作能够将用于计算 state 的逻辑提取到 reducer 外部,这也为未来对重置 state 的 action 作处理提供了便利:

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

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); } 复制代码

跳过 dispatch

若是 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及反作用的执行。(React 使用 Object.is 比较算法 来比较 state。)

useMemo

把“建立”函数和依赖项数组做为参数传入 useMemo,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。若是没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。memo是浅比较,意思是,对象只比较内存地址,只要你内存地址没变,管你对象里面的值变幻无穷都不会触发render。

**你能够把 useMemo 做为性能优化的手段,但不要把它当成语义上的保证。**未来,React 可能会选择“遗忘”之前的一些 memoized 值,并在下次渲染时从新计算它们,好比为离屏组件释放内存。先编写在没有 useMemo 的状况下也能够执行的代码 —— 以后再在你的代码中添加 useMemo,以达到优化性能的目的。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 
复制代码

useCallback

把内联回调函数及依赖项数组做为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给通过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将很是有用。

useMemouseCallback 相似,都是有着缓存的做用,useMemo 是缓存值的,useCallback 是缓存函数的。

useCallback(fn, deps) 至关于 useMemo(() => fn, deps)

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
复制代码

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

useEffect里面的state的值,是固定的,这个是有办法解决的,就是用useRef,能够理解成useRef的一个做用:就是至关于全局做用域,一处被修改,其余地方全更新。

本质上,useRef 就像是能够在其 .current 属性中保存一个可变值的“盒子”。你应该熟悉 ref 这一种访问 DOM 的主要方式。若是你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则不管该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。然而,useRef()ref 属性更有用。它能够很方便地保存任何可变值,其相似于在 class 中使用实例字段的方式。

请记住,当 ref 对象内容发生变化时,useRef不会通知你。变动 .current 属性不会引起组件从新渲染。若是想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则须要使用回调 ref 来实现。

const Hook =()=>{
    const [count, setCount] = useState(0)
    const btnRef = useRef(null)

    useEffect(() => {
        console.log('use effect...')
        const onClick = ()=>{
            setCount(count+1)
        }
        btnRef.current.addEventListener('click',onClick, false)
        return ()=> btnRef.current.removeEventListener('click',onClick, false)
    },[count])

    return(
        <div> <div> {count} </div> <button ref={btnRef}>click me </button> </div>
    )
}
复制代码

自定义 Hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部能够调用其余的 Hook。

例如,下面的 useFriendStatus 是咱们第一个自定义的 Hook:

import { 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;
}
复制代码

自定义一个当resize 的时候 监听window的width和height的hook

import {useEffect, useState} from "react";

export const useWindowSize = () => {
    const [width, setWidth] = useState()
    const [height, setHeight] = useState()

    useEffect(() => {
        const {clientWidth, clientHeight} = document.documentElement
        setWidth(clientWidth)
        setHeight(clientHeight)
    }, [])

    useEffect(() => {
        const handleWindowSize = () =>{
            const {clientWidth, clientHeight} = document.documentElement
            setWidth(clientWidth)
            setHeight(clientHeight)
        };

        window.addEventListener('resize', handleWindowSize, false)

        return () => {
            window.removeEventListener('resize',handleWindowSize, false)
        }
    })

    return [width, height]
}
复制代码

使用:

const [width, height] = useWindowSize()
const isOnline = useFriendStatus(id);
复制代码

参考文章

相关文章
相关标签/搜索