aermin/blog

React hook

aermin opened this issue · 0 comments

hook用了好一段时间,写个小总结记录下

为什么要用hook:

复用业务逻辑的已有方案比如 render props 和 高阶组件,需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”

=> React 需要为共享状态逻辑提供更好的原生途径。

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。

hook之间相互独自

Effect hook:

  • useEffect 会在每次渲染后都执行

  • 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

  • 它会在调用一个新的 effect 之前对前一个 effect 进行清理

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

function 组件hook版

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // 如同componentDidMount,componentDidUpdate里执行新的副作用函数
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); // 如同componentWillUnmount,componentDidUpdate里对已执行的副作用函数的清理函数
    };
  });

执行时间轴

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 运行第一个 effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 运行下一个 effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 运行下一个 effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect

也就是不管是首次render还是后面的更新render,每次render都会执行一次useEffect这个hook函数;componentDidMount 和 componentWillUnmount 是一次render,componentDidUpdate 也是一次render,所以如下图

image

重新render会执行新的useEffect, 执行新的useEffect之前会执行旧的useEffect的return的方法,此方法用旧的state(需要的话)。

性能优化:

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

在useEffect传第二个参数(是个array)相当于computed的作用,仅在这个参数变化时才会去执行这hook,也相当于class组件componentDidUpdate这个生命周期中比较prevState.count 和 this.state.coun不相等才执行这hook,不传就每次rerender都会执行。

如果要这个hook执行一次,那就传个[]即可

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, []); // 仅在首次render执行 只执行一次

State hook:

import React, { useState } from 'react';

function Example() {
  // 左边第一个参数是state,右边是setState
  // 右边的参数是state的初始值
  const [count, setCount] = useState(0);

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

等同于

class Example extends React.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>
    );
  }
}

因为hook之间相互独自,所以每个state都写一个useState来生成和管理

image

看我写的这个demo,能看到更新一个useState,这个函数组件会rerender,但是另外一个useState的值依旧保留之前的值不受影响。

useMemo useCallback

这两个之所以放一起说,是因为作用有点类似,都是用记忆计算结果来避免render的时候重新做一些计算,节省开销。不同的是,useMemo是记忆值,也就是返回值const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);;而useCallback是记忆函数,也就是返回一个函数,const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b],);

而且我原以为会缓存之前多次计算过的东西,没想到测试了下,只会缓存记忆上一次的,所以只有上一次和本次的依赖值相同时,才会读取上一次的计算结果,但是上一次之前的就算有跟本次的依赖值相同的,依旧要重新计算。如下图

image

点击并打开控制台查看

写自己的hook

这个可以看看这个库react-use, 里面有写了很多好用的hook

reference

react hook 官方教程文档

react hook 官方教程视屏

todo

hook源码分析?