深刻理解 React Hooks

HOOKS

HookReact 16.8 中的新增功能。它们容许您在不编写类的状况下使用状态和其余 React 功能。HOOKS 只能在函数组件中使用react

memo

React.memo 是一个高阶的组件。它相似于 React.PureComponent 也就是说若是组件的 props 没有改变,是不会被从新渲染的。git

function Foo (props) {
    ...
  }
  export default React.memo(Foo)

复制代码

React.PureComponent 如何实现性能优化的

咱们都知道类组件的中有个 shouldComponentUpdate 生命周期函数。当这个函数返回 false 时,表示组件不会从新渲染;当返回 true 时,表示组件会从新渲染。PureComponent 组件就是在 shouldComponentUpdate 函数中对组件接受的 props 进行比较(propsnextProps 进行比较)若是发生变化就返回 true。同时还有自身的 state 数据也会进行一个比较(statenextState),若是发生变化就返回 true。若是上面两种数据的个自比较都返回 false,那么组件就不会发生渲染,减小没必要要的渲染,从而达到性能的优化。推荐一篇文章React PureComponent 源码解析你们本身看看github

memo 同 React.PureComponent 有什么区别

React.memo 为高阶组件。它与 React.PureComponent 很是类似,但它适用于函数组件,但不适用于 class 组件。若是你的函数组件接受了一个彻底相同的 props 那么 memo 就不会使函数组件发生从新渲染。可是 memo 并不会对函数组件自身的 state 数据进行一个浅比较(useReduceruseState 钩子返回的数据)。因此二者在做用上仍是有一些区别的。segmentfault

useState

相似于类组件中的state,不一样的是 useState 接受一个任意类型的值 string, array, object, bool... 做为参数并返回一个数组,且 useState 只会在组件初始化的时候执行数组

// 初始化的时候,age的值就是useState中参数的值
  const [ age, setAge ] = useState(20);
  const [ visible, setVisible ] = useState(props.visible);

复制代码

数组中的第一个元素是状态值,组件在运行过程当中会保留这个状态值,相似于 this.state 数组中的第二个元素是改变这个状体值的函数,相似于 this.setState()性能优化

function Hooks(props) {
    const [ age, setAge ] = useState(20);
    const [ visible, setVisible ] = useState(props.visible);

    return (
      <div className=""> <p>个人年龄是{age}岁</p> <button onClick={() => setAge(age + 1)}>点击</button> <p>{`${visible}`}</p> </div>
    );
  };
复制代码

咱们能够在函数组件中屡次使用 useState,来建立多个状态值供咱们使用。可是,必须在函数做用域的最顶层使用 useState,不能嵌套在循环内部或者其余函数做用域内部或者是块级做用域中。app

useEffect

对于使用过类组件的同窗来讲,咱们能够理解为 useEffect 是类组件中 componentDidMountcomponentDidUpdate 两个生命周期的一个集合。每次当函数组件挂载成功或者从新渲染完成后都会调用 useEffect 。 可是也有不一样的地方,useEffect 不彻底同类组件中的 componentDidMountcomponentDidUpdate 生命周期函数同样,useEffect 有延迟,在父组件 didMount 或 didUpdate 后,但在任何新渲染以前触发。useEffect 能够在组件中使用屡次, 和 useState 使用同样。dom

useEffect 还能够返回一个函数,并在组件即将销毁时调用这个返回函数,没错,就是和类组件的 componentWillUnmount 同样。咱们经过它来取消在 useEffect 中绑定的事件监听等行为。ide

通常状况下,咱们能够将一个行为事件的绑定和取消绑定放在同一个 useEffect 中,这样代码的可读性和维护行会更强一些。函数

function Hooks(props, ref) {
    const boxRef = useRef(null);

    useEffect(() => {
      function handle() {
        console.log(123456)
      }
      boxRef.current.addEventListener('click', handle, false);
      return () => {
        boxRef.current.removeEventListener('click', handle, false);
      }
    }, []);
    return (
      <div ref={boxRef}> 12344556 </div>
    );
  }
复制代码

经过下面的这张图,能够看出来 useEffect 的有一个延迟

useEffect延迟执行

useEffect也能够接收一个数组做为第二个参数

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

上面这个示例中,咱们传入 [count] 做为第二个参数。这个参数是什么做用呢?若是 count 的值是 5,并且咱们的组件重渲染的时候 count 仍是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。由于数组中的全部元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。

当渲染时,若是 count 的值更新成了 6,React 将会把前一次渲染时的数组 [5] 和此次渲染的数组 [6] 中的元素进行对比。此次由于 5 !== 6,React 就会再次调用 effect。若是数组中有多个元素,即便只有一个元素发生变化,React 也会执行 effect

若是参数中有多个元素 [ age, props.visible ] ,组件渲染时经过比较后只要有一个元素发生变化,useEffect 就会执行。若是参数是一个空数组 [] ,那么这个时候 useEffect 就和类组件中的 componentDidMount 同样,只在组件挂载成功后调用一次。 useEffect 函数中 return 的函数,不受第二个参数的影响,仍在组件即将销毁的时候调用。

其实,当第二个参数不是空的数组时,useEffect 也会在组件挂载成功后调用一次,这一点不能忘记。

不要在循环条件或嵌套函数中调用 Hook。相反,始终在 React 函数的顶层使用 Hooks。经过遵循此规则,您能够确保每次组件呈现时都以相同的顺序调用Hook 。这就是React 容许多个 useStateuseEffect 调用之间正确保留 Hook 状态的缘由。

useLayoutEffect

useEffect 使用原理相同,可是惟一的区别在于 useLayoutEffect 不会延迟触发,和类组件的 componentDidMountcomponentDidUpdate 这两个生命周期函数基本处于同步的状态。

customize hooks

自定义 Hook 是一个 JavaScript 函数,其名称以 "use" 开头,能够调用其余 Hook。构建本身的 Hook 能够将组件逻辑提取到可重用的函数中 ,确保只在自定义 Hook 的顶层无条件地调用其余 Hook。与 React 组件不一样,自定义 Hook 不须要具备特定签名。咱们能够决定它做为参数须要什么,以及它应该返回什么(若是有的话)

// useVisibleStatus 是一个自定义的钩子,咱们在函数中调用的useEffect
  function useVisibleStatus(isShow) {
    const [ visible, setVisible ] = useState(isShow);
    useEffect(() => {
      setVisible(isShow);
    }, [ isShow ]);
    return visible;
  };

  function Hooks(props) {
    const [ count ] = useState(props.count);
    const visible = useVisibleStatus(props.visible);

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>点击</button> <h2>{`${visible} ${props.count}`}</h2> </div>
    );
  }
复制代码

咱们也能够将一些复杂或者重复的逻辑提取提取到自定义的 Hook 函数中,从而简化咱们的代码。其实自定义 hook 和函数组件没有多大区别。

useReducer

useState 复杂的状态逻辑涉及多个子值或下一个状态取决于前一个状态时,一般 useReducer 更可取。useReduce 还可让您优化触发深度更新的组件的性能

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

咱们看看 useReducer 具体的实现(自定义一个 useReducer 钩子):

function useReducer(reducer, initialState) {
    const [state, setState] = useState(initialState);

    function dispatch(action) {
      const nextState = reducer(state, action);
      setState(nextState);
    }

    return [state, dispatch];
  }
复制代码

useImperativeHandle

能够经过 useImperativeHandle ,给ref上绑定一些自定的事件,前提是咱们必须联合 forwardRef 一块儿使用,注意全部的事件都是绑定在 refcurrent 属性上。 看下面的例子

// hook.js
  function Hooks(props, ref) {
    const [ count, setCount ] = useState(props.count);
    useImperativeHandle(ref, () => ({
      // 自定义一些事件
      click: () => {
        setCount(count + 1);
      },
    }));

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>点击</button> </div>
    );
  };
  export default React.forwardRef(Hooks);

  // Application.js
  export default class App extends PureComponent {
    componentDidMount() {
      this.ref = React.createRef();
    }

    return (
      <div onClick={() => this.ref.current.click()} > // ... <Hooks ref={this.ref} count={this.state.count} visible={this.state.visible}/> // ... </div> ); } 复制代码

或者

function FancyInput(props, ref) {
    // 获取真是DOM节点
    const inputRef = React.useRef();
    useImperativeHandle(ref, () => ({
      // 自定义一些事件
      focus: () => {
        // 在DOM节点执行一些操做均可以
        inputRef.current.focus();
      }
    }));
    return <input ref={inputRef} />; } FancyInput = React.forwardRef(FancyInput); 复制代码

useRef

useRef 返回一个可变的ref对象,其 current 属性值为初始化传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。和类组件中的实例属性很像

const ref = usRef(20);
  console.log(ref.current) // 20
  // 能够从新赋值
  ref.current = 200;

复制代码

固然最多见的就是访问一个元素节点

function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    const onButtonClick = () => {
      // `current` points to the mounted text input element
      inputEl.current.focus();
    };
    return (
      <>
        <input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>Focus the input</button>
      </>
    );
  }
复制代码

useMemo

使用的场景:函数组件中,咱们定义了一些方法,可是咱们并不但愿每次组件更新的时候都从新执行一次个函数,那么这个时候咱们就可使用 useMemo。有个地方须要注意点那就是,useMemo 是在 useLayoutEffect 以前执行,这和类组件中的 componentWillMountcomponentWillUpdate 相似。可是若是 useMemo 所接收的条件是 props 中的相关属性的话,那么咱们将 useMemo 看成类组件的 componentWillReceiveProps(nextProps) 使用,能够查看的咱们demo

// 组件初始化的时候会调用 `Func`,相似 `componentWillMount``
  // 当数组中的元素的值发生改变,那么就会调用 `Func`,这个条件 a 和 b 有一个发生变化的时候 就会触发 `useMemo`
  useMemo(() => Func(a, b), [a, b]);
复制代码

在看这个带返回值的

function Hooks(props) {
    const [ count, setCount ] = useState(props.count);
    useLayoutEffect(() => {
      console.log('useLayoutEffect 后执行');
      setCount(props.count);
    }, [ props.count ]);

    const dom = useMemo(() => {
      console.log('useMemo 优先执行');
      return <h2>{count * 10}</h2>;
    }, [count]);

    return (
      <div className=""> <h2>{count}</h2> <button onClick={() => setCount(count + 1)}>点击</button> {dom} </div>
    );
  }
复制代码

注意:传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操做,诸如反作用这类的操做属于 useEffec 的适用范畴,而不是 useMemo

useCallback

useCallback 的使用和 useMemo 是同样的,且 useCallback(fn, deps) 至关于 useMemo(() => fn, deps)

这是个人demo

useContext

const value = useContext(MyContext);
复制代码

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

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext providercontext value 值。

调用了 useContext 的组件总会在 context 值变化时从新渲染

const Thems = {
    light: {
      color: '#f90',
    },
    dack: {
      color: '#222',
    }
  }

  const ThemsContext = React.createContext({
    them: Thems.light,
    toggleTheme: () => {},
  })

  class Detail extends PureComponent {
    state = {
      context: {
        them:  Thems.light.color,
        toggleTheme: this.handle,
      }
    }

    handle = () => {
      const { context: { them } } = this.state;
      let color = Thems.light.color;
      if (them === Thems.light.color) {
        color = Thems.dack.color
      }
      this.setState({
        context: {
          ...this.state.context,
          them: color,
        },
      });
    }

    render() {
      return (
        <ThemsContext.Provider value={this.state.context}> <Hooks/> </ThemsContext.Provider> ); } } function Hooks(props, ref) { const themContext = React.useContext(ThemsContext); return ( <div style={{ background: themContext.them }} onClick={() => { themContext.toggleTheme() }} > 1234567890 </div> ); } 复制代码

上面的这个 demo 中,当 ThemsContext.Providervalue props 发生变化时 Hooks 组件就会发生从新渲染。因此说这个时候咱们的目的就已经达到了。

以上就是我在工做中对 Hooks 的一个总结,若有问题请麻烦纠正,谢谢。

相关文章
相关标签/搜索