React Hooks useState&useEffect&自定义hook

一、作用:

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
复制代码

二、规则

只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

三、用法

1、useState

import React,{useState} from "react";
const [state, setState] = useState(initialState);
复制代码

调用useState 返回一个 包含state以及更新 state 函数的数组

在初始化时,返回的state 与传入的initialState 值相同。

setState 函数用于更新 state,接收一个新的 state 值并将组件重新渲染。

setState(newState);
在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。

下面介绍一下setState的两种用法,函数组件中的调用也如类组件中this.setState一样有两种写法。

方法一:调用setState,参数为想要更新的结果,这是我们使用useState常用的更新方法。

方法二:参数传如一个更新函数,函数的参数为上一次更新后的结果。

下面进行两种方法的示例展示。

const Counter = () => {
    const [count, setCount] = useState(0);
    const changeCount = () => {
      setCount(count + 1)
    }
    const addCount =() =>{
      setCount((prevCount) => {
          return prevCount + 1
      })
    }
    return (
      <div>
        {count}
        <button onClick={changeCount}>+</button>
        <button onClick={addCount}>+</button>
      </div>
    );
  }
复制代码

这样看起来两者似乎并没有什么不同,实现的结果也是相同的,但是我们只要稍加变化,将其包装成异步,这时候两种不同的写法却有了不同的实现结果。

const Counter = () => {
    const [count, setCount] = useState(0);
    const changeCount = () => {
      setTimeout(() => {
        setCount(count + 1)
      }, 3000);
    }
    const addCount =() =>{
      setTimeout(() => {
        setCount((prevCount) => {
          return prevCount + 1
        })
      }, 3000);
    }
    return (
      <div>
        {count}
        <button onClick={changeCount}>+</button>
        <button onClick={addCount}>+</button>
      </div>
    );
  }
复制代码

如例所示,我们在定时器3s的时间内进行连续点击,第一种写法只更新了一次,结果是1,因为定义的count并没有发生变化。

而第二种写法的结果一直保持是最新的,因为在进行更新时并不是以初始化定义的count进行加1操作,而是已上一次更新之后的结果为基础,这样保持了count的实时性。

两种写法和类组件中的setState相似,可以直接进行更新,也可以进行函数式更新。

const changeCount = () => {
    this.setState({count:this.state.count+1})
}
    
const addCount = () => {
    this.setState((prevState)=>{
       count:prevState.count+1
    })
};
复制代码

另外有一点,class组件中setState更新数据是进行合并,而hooks中更新是进行数据的替换。

    constructor(props) {
        super(props);
        this.state = { count: 0, name: \'58\' };
    }

    this.setState((state) => {
        return {count: state.count + 1}
    })

    // 运行一次后的state结果是{ count: 1, name: \'58\' }
复制代码

2、useEffect

副作用钩子,常在此钩子中进行数据获取,发布订阅。

可以把useEffect看成类组件中componentDidMount,componentDidUpdate和componentWillUnmount三个生命周期的组合。

对于useEffect的常用方法一般有两种,一种为需要清除和不需要清除的。

不需要清除
如例所示,我们使用hooks实现一个把当前点击数量展示到document的title中的功能。

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

function Example() {
  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>
  );
}
复制代码

这样的写法使我们的effect在每次组件重新渲染时都会进行执行一次,让我们不需要考虑当前是挂载还是更新阶段。

在类组件中我们需要将这些副作用函数分别在componentDidMount和componentDidUpload两个生命周期函数中进行重复使用,这样才能保证实现点击次数的实时更新。

class Example 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看起来更加简洁,也减少了一些重复代码的书写。

需要清除
在平时的工作中,我们可能会在代码中进行发布订阅操作,为了防止内存泄漏,常用的方法就是在componentDidMount中进行订阅,在componentWillUnmount中进行清除订阅。

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
复制代码

而在hooks中我们只需要在useEffect钩子中返回一个清除函数,那么react就会在组件卸载的时候执行当前函数,进行清除操作。

    useEffect(() => {
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    })
复制代码

上面两个例子的effect在每次组件重新渲染时都会重新执行,这样的话就会造成一些不必要的调用或者渲染,接下来我们对上面的例子做一些优化,使其能够在effect中依赖项未发生变化时不重新执行。

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

function Example() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  },[count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={() => setNumber(number + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

在useEffect的第二个参数中,我们可以加入一个deps数组,当数组里的任意一项发生变化时,再重新执行当前的effect,这样就避免了一些不必要的调用和渲染。

我们在调用setNumber时,count并未发生变化,所以document的title不需要进行变化,effect也就不需要重新执行。

但当我们不把count加入依赖数组时,number的每一次变化也会使得effects重新进行一次调用,而这一步是我们所不想看到的。

将count加入依赖数组时,每次组件重新渲染时,effect会判断当前是否发生变化,发生改变时才会重新执行当前effect,这一步我看来更像在shouldComponentUpdata中进行pervState.count===state.count判断一样。

3、自定义hook
在使用函数组件时,难免会遇到两个组件之间存在共同逻辑。将相同逻辑拷贝在不同组件中,这未免不是一种低效的办法,这时候就轮到自定义hook发挥作用的时候了。

通过对相同逻辑hook的封装,我们即可实现高效的办公效率和减少一些重复代码。

自定义规则
自定义hook是一个函数,名称必须以use开头,函数内部可以调用其他的hook。

在自定义hook中我们可以制定想要接受的参数,和在当前hook中需要做什么,以及hook返回的结果,这些规则都可以根据我们的需求进行改变。

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

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

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
复制代码

以上面代码为例,我们想要在不同组件中获取不同用户的登录状态,并在当前组件卸载是进行清除。对此我们把获取在线状态的hook提取出来,在组件中进行调用,只需要将当前用户ID传递就可接受一个是否在线的返回结果。

function FriendStatus(props) {
    const isOnline = useFriendStatus(props.friend.id);

    return isOnline ? \'Online\' : \'Offline\';
}
复制代码

四、小结

尽管在写这篇文章之前就已经使用过很长一段时间的hooks,但在写的时候依旧有一些不知从何处写起的感觉,还是对一些内部的实现原理说的不是很清楚。通过这次对官方文档以及相关文章的阅读,也是加强了一些对hooks逻辑整理,文章如有观点偏差,还请大家指正。

革命尚未成功,同志仍需努力.

最后推荐一篇对于了解hook实现很有帮助的文章。overreacted.io/a-complete-…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享