react Hook

3,136 阅读3分钟

Hook 是 React 16.8 的新增特性,是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

内置Hook

一、useState

import React, { useState } from 'react';

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

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

export default Com1;

对应的class组件实现:

import React, { Component } from 'react';

class Com2 extends Component {
    constructor(props){
        super(props);
        this.state = {
            count: 0
        };
    }

    render() {
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={() => this.setState({count: this.state.count + 1})}>
                    Click me
                </button>
            </div>
        );
    }
}

export default Com2;

二、useEffect

给函数组件增加了操作副作用(数据获取、订阅或者手动修改DOM)的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

// 它在第一次渲染之后和每次更新之后都会执行。
useEffect(() => {
    document.title = `You clicked ${count} times`;
});  

对应的class组件实现:

componentDidMount() {
  document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
  document.title = `You clicked ${this.state.count} times`;
}

** 清除effect**
return清除函数(组件卸载时执行,每次重新渲染之前都会执行)

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  // Specify how to clean up after this effect:
  return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

对应的class组件实现:

componentDidMount() {
  ChatAPI.subscribeToFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}
componentDidUpdate(prevProps) {
  // 取消订阅之前的 friend.id
  ChatAPI.unsubscribeFromFriendStatus(
    prevProps.friend.id,
    this.handleStatusChange
  );
  // 订阅新的 friend.id
  ChatAPI.subscribeToFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}
componentWillUnmount() {
  ChatAPI.unsubscribeFromFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}
handleStatusChange(status) {
  this.setState({
    isOnline: status.isOnline
  });
}

** 优化(只在state数据发生变化后执行effect) **

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

对应的class组件实现:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

注意!!!

React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

三、useContext

跨组件传值

import React, {useContext} from 'react';

const themes = {
    light: {
      foreground: "#000000",
      background: "#eeeeee"
    },
    dark: {
      foreground: "#ffffff",
      background: "#222222"
    }
  };
  
  const ThemeContext = React.createContext(themes.light);
  
  function Com00() {
    return (
      <ThemeContext.Provider value={themes.dark}>
        <Com11 />
      </ThemeContext.Provider>
    );
  }
  
  function Com11(props) {
    return (
      <div>
        <Com22 />
      </div>
    );
  }
  
  function Com22() {
    const theme = useContext(ThemeContext);
    return (
      <button style={{ background: theme.background, color: theme.foreground }}>
        I am styled by theme context!
      </button>
    );
  }

  export default Com00;

对应的class组件实现:

import React, { Component } from 'react';

const MyContext = React.createContext();

const store = {
    home: {
        index: 2
    },
    user: {
        name: 'lili'
    }
}

class Com5 extends Component {

    render() {
        return (
            <div>
                <MyContext.Provider value={store}>
                    <Com51></Com51>
                </MyContext.Provider>
            </div>
        );
    }
}

class Com51 extends Component {
    render() {
        return (
            <div>
                <MyContext.Consumer>
                    {
                        store1 => <h1>userName : {store1.user.name}</h1>
                    }
                </MyContext.Consumer>
                <Com52></Com52>
            </div>
        )
    }
}

class Com52 extends Component {
    render() {
        return (
            <div>
                <MyContext.Consumer>
                    {
                        store1 => <h1>homeIndex : {store1.home.index}</h1>
                    }
                </MyContext.Consumer>
            </div>
        )
    }
}

export default Com5;

四、useReducer

useState的替代方案

import React, {useReducer} from 'react';

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

export default Counter;

其他

  • useCallback
    把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
    useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
  • useMemo
    把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
    传入 useMemo 的函数会在渲染期间执行。
  • useRef
    指向组件或dom(不能用在函数组件上,因为没有实例)
    应用场景:
    管理焦点,文本选择或媒体播放。
    触发强制动画。
    集成第三方 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>
    </>
  );
}
  • useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用

  • useLayoutEffect
    与useEffect的区别: 它是同步执行的
  • useDebugValue
    在 React 开发者工具中显示自定义 hook 的标签。

自定义Hook

如果函数的名字以 “use” 开头并调用其他 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;
}

使用自定义Hook

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

自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。