2019-02-soolx-React Hooks笔记

React Hooks

React-Hooks于React 16.8版本正式发布。它为不编写类的情况下,为组件实现state管理和其他React功能提供了可能。

Hook的优势

  • 可以方便的在组件中复用逻辑
  • 可以更加灵活的拆分组件

Hook 规则

  • 只在顶层调用Hooks,不要在循环,条件或者嵌套函数中调用
  • 仅从React组件或者自定义Hook中调用Hook

Hooks类型

基本Hooks

  • useState
  • useEffect
  • useContent

    进阶Hooks

  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue

使用

useState

基本用法

1
import React, { useState } from 'react';
2
3
function Example() {
4
  // Declare a new state variable, which we'll call "count"
5
  const [count, setCount] = useState(0);
6
7
  return (
8
    <div>
9
      <p>You clicked {count} times</p>
10
      <button onClick={() => setCount(count + 1)}>
11
        Click me
12
      </button>
13
    </div>
14
  );
15
}

通过useState声明一个状态变量,array的第一项是我们要声明的状态变量,第二项是可以用来改变该变量的方法。

使用多个状态变量时:

1
const [age, setAge] = useState(42);
2
const [fruit, setFruit] = useState('banana');
3
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

Lazy initial state

如果state初始值的计算成本比较高,可以采用function的形式,这样初始值只会在初始化阶段计算一次

1
const [state, setState] = useState(() => {
2
  const initialState = someExpensiveComputation(props);
3
  return initialState;
4
});

useEffect

基本用法

1
import React, { useState, useEffect } from 'react';
2
3
function Example() {
4
  const [count, setCount] = useState(0);
5
6
  // Similar to componentDidMount and componentDidUpdate:
7
  useEffect(() => {
8
    // Update the document title using the browser API
9
    document.title = `You clicked ${count} times`;
10
  });
11
12
  return (
13
    <div>
14
      <p>You clicked {count} times</p>
15
      <button onClick={() => setCount(count + 1)}>
16
        Click me
17
      </button>
18
    </div>
19
  );
20
}

Effect Hook用来实现类似于react生命周期的方法。
useEffect Hook的作用就是componentDidMount, componentDidUpdate和componentWillUnmount的结合,会在首次渲染和组件更新之后执行。

清理阶段

Effect Hook还可以return一个方法,实现清理阶段的逻辑:

1
import React, { useState, useEffect } from 'react';
2
3
function FriendStatus(props) {
4
  const [isOnline, setIsOnline] = useState(null);
5
6
  function handleStatusChange(status) {
7
    setIsOnline(status.isOnline);
8
  }
9
10
  useEffect(() => {
11
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
12
    // Specify how to clean up after this effect:
13
    return () => {
14
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
15
    };
16
  });
17
18
  if (isOnline === null) {
19
    return 'Loading...';
20
  }
21
  return isOnline ? 'Online' : 'Offline';
22
}

return的该方法会在组件卸载和每次运行这个effect之前执行,不同于componentWillUnmount。

useEffect可以根据实际的使用情况,多次调用,拆分逻辑。

1
function FriendStatusWithCounter(props) {
2
  const [count, setCount] = useState(0);
3
  useEffect(() => {
4
    document.title = `You clicked ${count} times`;
5
  });
6
7
  const [isOnline, setIsOnline] = useState(null);
8
  useEffect(() => {
9
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
10
    return () => {
11
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
12
    };
13
  });
14
15
  function handleStatusChange(status) {
16
    setIsOnline(status.isOnline);
17
  }
18
  // ...
19
}

useEffect还可以使用第二个参数来跳过效果,优化性能

1
useEffect(() => {
2
  document.title = `You clicked ${count} times`;
3
}, [count]); // Only re-run the effect if count changes

该例子只有在count发生变更的时候才会执行,效果类似于

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

跳过效果也适用于清理阶段:

1
useEffect(() => {
2
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
3
  return () => {
4
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
5
  };
6
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

注意,第二个参数数组中的值,请使用会随时间变化,且可以被effect使用的值。

基于前一次的状态更新

1
function Counter({initialCount}) {
2
  const [count, setCount] = useState(initialCount);
3
  return (
4
    <>
5
      Count: {count}
6
      <button onClick={() => setCount(initialCount)}>Reset</button>
7
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
8
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
9
    </>
10
  );
11
}

useLayoutEffect

useEffect 相同,但在所有 DOM 修改结束后同步执行。

useContent

1
const context = useContext(Context);

context 变化时,hook 会触发一次 rerender。

useReducer

1
const [state, dispatch] = useReducer(reducer, initialArg, init);
2
3
// 传初始值
4
const [state, dispatch] = useReducer(reducer, { count: initialCount });
5
6
// lazy 初始值
7
function init(initialArg) {
8
  return { count: initialArg };
9
}
10
const [state, dispatch] = useReducer(reducer, initialArg, init);

reducer 即 redux 里的 reducer,(sate, action) => newState

有复杂的 state 逻辑时使用,

  • 多个子数据
  • 返回的 state 依赖之前的 state

把 dispatch 通过 context 往下传,可解决深层组件的更新问题。(而不是把 callback 传下去)
useReducer 不会像 Redux 一样,把 reducer 执行一遍返回初始值,但可通过 useReducer(reducer, undefined, reducer) 实现。
reducer 返回相同的值时,不会触发更新,值匹配基于 Object.is 比较算法

useRef

1
const refContainer = useRef(initialValue);

常见用法

1
function TextInputWithFocusButton() {
2
  const inputEl = useRef(null);
3
  const onButtonClick = () => {
4
    // `current` points to the mounted text input element
5
    inputEl.current.focus();
6
  };
7
  return (
8
    <>
9
      <input ref={inputEl} type="text" />
10
      <button onClick={onButtonClick}>Focus the input</button>
11
    </>
12
  );
13
}

特殊用法

useRef不只是用作DOM绑定。ref对象是一个通用容器,其current属性是可变的,可以保存任何值.

1
function Timer() {
2
  const intervalRef = useRef();
3
4
  useEffect(() => {
5
    const id = setInterval(() => {
6
      // ...
7
    });
8
    intervalRef.current = id;
9
    return () => {
10
      clearInterval(intervalRef.current);
11
    };
12
  });
13
14
  // ...
15
}

useDebugValue

1
useDebugValue(value)
2
3
// 延迟format,直到查看时执行
4
useDebugValue(date, date => date.toDateString());

用于在React DevTools中显示自定义Hook的label


参考