# React

# Hook

HookReact16.8的新增特性。可以在不编写class的情况下使用state以及其他的React特性。

Hook为已知的React概念提供了更直接的APIpropsstatecontextrefs以及生命周期。

可以使用Hook从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook可以在无需修改组件结构的情况下复用状态逻辑。这使得在组件间或社区内共享Hook变得更便捷。

  • 利于组件预编译,更易于优化
  • class组件会无意中鼓励开发者使用一些让优化措施无效的方案
  • class不能很好的压缩,并且会使热重载出现不稳定的情况

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

# State Hook

useState通过在函数组件里调用它来给组件添加一些内部 stateReact会在重复渲染时保留这个state

不同于class组件state必须为一个对象,useState可以接受一个基本类型,也可以接受引用类型。

返回一对值:当前状态state和一个可以更新它的函数setState,可以在事件处理函数中或其他一些地方调用这个函数。

useState类似于class组件的this.setState,但是它不会把新的state和旧的state进行合并。

state不一定要是一个对象,且这个初始state参数只有在第一次渲染时会被用到

具体示例如下:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

React假设当你多次调用useState的时候,你能保证每次渲染时它们的调用顺序是不变的

# Effect Hook

Effect Hook可以让你在函数组件中执行副作用操作,类似于componentDidMountcomponentDidUpdatecomponentWillUnmount这三个生命周期。

Effect Hook会在每次渲染后都执行。但是与componentDidMountcomponentDidUpdate不同,使用useEffect调度的effect不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。如果需要阻塞视图更新,可以使用useLayoutEffect Hook

React组件中有两种常见副作用操作:需要清除的和不需要清除的。需要清除的effect可以在useEffect中返回一个函数,React将会在执行清除操作时调用它。

React会在执行当前effect之前对上一个effect进行清除。

如果某些特定值在两次重渲染之间没有发生变化,可以通过传递数组作为useEffect的第二个可选参数,通知 React 跳过对 effect 的调用。

如果想执行只运行一次的effect(仅在组件挂载和卸载时执行),可以传递一个空数组[]作为第二个参数。

具体见下例:

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // 清除副作用
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Hook规则

Hook 就是JavaScript函数,但是要注意两点规则:

  • 只能在函数最外层调用Hook,不能在循环、条件判断或者子函数中调用。这样能确保Hook在每一次渲染中都按照同样的顺序被调用。让React能够在多次的useStateuseEffect调用之间保持hook状态的正确
  • 只能在React的函数组件中调用Hook,不要在其他 JavaScript函数中调用。另外自定义的Hook中也可以调用

React靠的是Hook调用的顺序来确定哪个state对应哪个useState

# 其他Hook

useContext接收一个context对象(React.createContext的返回值)并返回该context的当前值。

const value = useContext(MyContext);
1

useReduceruseState的替代方案。接收一个形如(state, action) => newStatereducer,并返回当前的state 以及与其配套的dispatch方法。

const [state, dispatch] = useReducer(reducer, initialArg, init);
1

useLayoutEffect其函数签名与useEffect相同,但它会在所有的DOM变更之后同步调用effect,会阻塞视图更新

# 自定义Hook

自定义Hook是一个函数,其名称以use开头,函数内部可以调用其他的Hook

# React的生命周期

  • componentDidMount,组件第一次被渲染到DOM中的时候
  • componentWillUnmount,组件被删除的时候

当组件实例被创建并插入DOM中时(挂载),其生命周期调用顺序如下:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

当组件的propsstate发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

当组件从DOM中移除(卸载)时会调用如下方法:

  • componentWillUnmount()

错误处理,当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError()
  • componentDidCatch()

# Fiber