天天看点

React学习笔记 —— Hook

什么是 Hook?

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React特性。

Hook 和函数组件

函数组件又称为“无状态组件”

const Example = (props) => {
  // 你可以在这使用 Hook
  return <div />;
}
           
function Example(props) {
  // 你可以在这使用 Hook
  return <div />;
}
           

Hook 在 class 内部是不起作用的。但你可以使用它们来取代 class 。

class 中的

this.setState:更新 state 变量替换

Hook中的

useState:更新 state 变量合并

State Hook

const HookStudy = () =>{
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
           

等价于

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

调用 useState 方法:定义一个 “state 变量”。示例中变量叫 count。这是一种在函数调用时保存变量的方式。

useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

useState 需要的参数: useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以传了 0 作为变量的初始 state。(如想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。)

useState 方法的返回值:当前 state 以及更新 state 的函数。这与 class 里面 this.state.count 和 this.setState 类似,唯一区别就是需要成对的获取它们。数组结构。

let fruitStateVariable = useState('banana'); // 返回一个有两个元素的数组
let fruit = fruitStateVariable[0]; // 数组里的第一个值
let setFruit = fruitStateVariable[1]; // 数组里的第二个值
           

Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作

在 React 更新 DOM 之后运行一些额外的代码

const HookStudy = () =>{
  const [count, setCount] = useState(0);
  useEffect(()=>{
    document.title = `You clicked ${count} times`;
  })
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
           

等价于

class HookStudy extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {    document.title = `You clicked ${this.state.count} times`;  }  componentDidUpdate() {    document.title = `You clicked ${this.state.count} times`;  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
           

useEffect 做了什么: 通过使用这个 Hook,可以让 React 组件需要在渲染后执行某些操作。React 会保存传递的函数(称为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,示例设置了 document 的 title 属性,也可以执行数据获取或调用其他命令式的 API。

为什么在组件内部调用 useEffect? 将 useEffect 放在组件内部可以在 effect 中直接访问 count state 变量(或其他 props)。不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。不用再考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

清除的 effect

在某些情况下清除工作是非常重要的,可以防止引起内存泄露!

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {    function handleStatusChange(status) {      setIsOnline(status.isOnline);    }    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    // Specify how to clean up after this effect:    return function cleanup() {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
           

等价于

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }
  componentDidMount() {    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  componentWillUnmount() {    ChatAPI.unsubscribeFromFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  handleStatusChange(status) {    this.setState({      isOnline: status.isOnline    });  }
  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
           

为什么在 effect 中返回一个函数: 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

React 何时清除 effect: React 会在组件卸载的时候执行清除操作。

声明多个effect

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

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
           

React 将按照 effect 声明的顺序依次调用组件中的每一个 effect

跳过 Effect 进行性能优化

Effect 在 每次更新之后都会执行,因某些需求需要,可以通过传递数组作为 useEffect 的第二个可选参数,达到特定情况下进行更新

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

如果执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。使 React 的 effect 不依赖于 props 或 state 中的任何值,它永远都不需要重复执行。

(遵循依赖数组的工作方式)

自定义 Hook

自定义 Hook 必须以 “use” 开头。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline;
}
           
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
           
const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);  const isRecipientOnline = useFriendStatus(recipientID);
  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}
           

自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。

自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。

结合reducer方式的Hook

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

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

  return [state, dispatch];
}
           

组件中使用

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);
  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}
           

参考资料 https://react.docschina.org/docs/hooks-intro.html