Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React hook #67

Open
aermin opened this issue Oct 19, 2019 · 0 comments
Open

React hook #67

aermin opened this issue Oct 19, 2019 · 0 comments
Labels

Comments

@aermin
Copy link
Owner

aermin commented Oct 19, 2019

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源码分析?

@aermin aermin changed the title react hook React hook Oct 19, 2019
@aermin aermin added the react label Dec 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant