关于React Hooks,你不得不知的事

React Hooks是React 16.8发布以来最吸引人的特性之一。在开始介绍React Hooks以前,让我们先来理解一下什么是hooks。wikipedia是这样给hook下定义的:javascript

In computer programming, the term hooking covers a range of techniques used to alter or augment the behaviour of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.html

通俗来讲,Hook(钩子)就是经过拦截软件和系统内部函数调用和消息通讯来加强原有功能的技术。而React Hooks想要加强哪些功能呢?设想你的项目中已经有一大堆组件,这些组件各自都拥有本身的状态。那么一旦你想重用某些特定的带状态逻辑,就得大幅度重构你的应用。如今有了React Hooks,你只须要抽离这些带状态的逻辑代码,而后它们能够更好地进行重用, 并且独立出来的代码也更容易进行测试和管理。有了React Hooks后,你能够在函数式组件中实现以前在带状态组件中能作到的任何事,你可以更灵活地实现你的应用代码。java

接下来,让咱们看看React Hooks在实际项目中到底怎么使用。react

状态管理

对于业务性组件来讲,状态管理确定是不可避免的。之前,咱们一般写Class组件来管理业务逻辑,或者使用redux来全局管理状态。如今咱们能够利用React Hooks新提供的State Hook来处理状态,针对那些已经写好的Class组件,咱们也能够利用State Hook很好地进行重构, 先来看下面这段代码:ios

import React from 'react';
class Person extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
          username: "scq000"
      };
  }
  
  render() {
      return (
        <div>
            <p>Welcome to homepage. {state.username}</p>
            <input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
        </div>
      );
  }
}

接下来尝试将它重构成函数式组件:web

import React, {useState} from 'react';

export const Person = () => {
  const [state, setState] = useState({username: "scq000"});
  
  return (
  	<div>
  		<p>Welcome to homepage. {state.username}</p>
		<input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input>
  	</div>
  )
}

如上面这段代码,咱们首先使用useState api 来声明一个内部状态,接着声明一个新的状态变量state,以及它的setter方法。在这里,为了减小重构的工做量我特地选择了state这个变量名,你也能够单独将每一个独立的状态提取出来使用, 好比使用代码const [username, setUsername] = userState("scq000")。在随后的组件内部,咱们就能够利用这个内部状态来处理业务逻辑了。因为是函数式组件的写法,咱们也可以避免不少this绑定,并且这部分逻辑在后续使用过程当中也能够抽离出来进行重用。不过这里有个须要注意的点是:当你使用set方法的时候,旧状态不会自动merge到新状态中去,因此你若是提取的状态是个对象,且有多个属性时,须要使用以下语法进行状态的更新:shell

setState({
    ...state,
  	username: event.target.value
});

生命周期管理

咱们都知道,组件的生命周期管理是整个react组件的灵魂所在。利用生命周期函数,咱们能够控制整个组件的加载、更新和卸载。React Hooks中提供了Effect钩子,使咱们能够在函数式组件中实现这些功能。npm

为了便于理解,接下来我将分别演示如何利用Effect钩子实现本来在Class组件中的各个生命周期方法。下面这段代码是咱们熟悉的Class组件:编程

import React from 'react';
class Person extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
          username: "scq000"
      };
  }
  
  componentDidMount() {
      console.log('componentDidMount: 组件加载后')
  }
  
  componentWillUnmount() {
      console.log('componentWillUnmount: 组件卸载, 作一些清理工做')
  }
  
  componentDidUpdate(prevProps, prevState) {
      if(prevState.username !== this.state.username) {
          console.log('componentDidUpdate: 更新usernmae')
      }
  }
  
  render() {
      return (
        <div>
            <p>Welcome to homepage. {state.username}</p>
            <input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
        </div>
      );
  }
}

如今咱们利用Effect重构一下:json

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

export const Person = () => {
  const [state, setState] = useState({username: "scq000"});
  
  useEffect(() => {
      console.log('componentDidMount: 组件加载后')
      return () => {
      	console.log('componentWillUnmount: 组件卸载, 作一些清理工做')
      }
  }, []);
  
  useEffect(() => {
      console.log('componentDidUpdate: 更新usernmae')
  }, [state.username]);
  
  return (
 <div> <p>Welcome to homepage. {state.username}</p> <input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input> </div>
  )
}

能够看到,咱们利用反作用钩子很好地实现了本来的生命周期方法。一般咱们会利用组件的生命周期函数去获取数据,操做DOM等,而这些操做都被称做反作用(side effect)。这些反作用逻辑通常都比较复杂,也是bug频发的地段。 因此咱们能够针对每一段逻辑单独使用一个Effect钩子,便于后期维护和调试。

在使用过程当中,useEffect方法须要传入两个参数,第一个参数是回调函数:这个回调函数会在每次组件渲染后执行,包括初始化渲染以及每次更新时。另外一个参数,则是状态依赖项(数组形式),一旦检测到依赖项数据变更,组件会更新,而且回调函数都会被再次执行一遍,从而实现componentDidUpdate的功能。若是你传入一个空依赖,就能实现原来componentDidMount的效果,即只会执行一次。回调函数中若是返回的是闭包,这个返回的闭包函数将会在组件从新渲染前执行,因此你能够在这个位置作一些清理操做,从而实现componentWillUnmount的功能。

还有要注意的是componentWillMountcomponentWillUpdate两个生命周期方法在新版本的React中已经不推荐使用了,具体缘由能够查看这里

至此,咱们就学会如何利用Effect钩子在函数式组件中实现全部生命周期方法,从而管理咱们的应用了。

自定义Hook

重用和抽象一直都是编程中要解决的问题。咱们能够本身封装想要的Hook, 从而实现代码逻辑的重用和抽象。

封装自定义hook其实很简单,就是包装一个自定义函数,而后根据功能将其状态和对应的effect逻辑封装进去:

export const useFetch = (url, dependencies) => {
    const [isLoading, setIsLoading] = useState(false);
    const [response, setResponse] = useState(null);
  	const [error, setError] = useState(null);
  	
  	useEffect(() => {
      	setIsLoading(true);
        axios.get(url).then((res) => {
            setIsLoading(false);
            setResponse(res);
        }).catch((err) => {
            setIsLoading(false);
            setError(err);
        });
    }, dependencies)
    
  	return [isLoading, response, error];
}

这里咱们简单地封装了一个请求数据的Hook,使用方法跟其余Hook相似,直接调用就能够了:

export const Person = () => {
  const [isLoading, response, error] = useFetch("http://example.com/getPersonInfo", []); 
  
  return (
  	<div>  {isLoading ? <div>loading...</div> : ( error ? <div> There is an error happened. {error.message} </div> : <div> Welcome, {response.userName} </div> ) } </div>
  )
}

注意事项

在使用Hooks的过程当中,须要注意的两点是:

  • 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是由于React须要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易致使调用顺序的不一致性,从而产生难以预料到的后果。

  • 只能在React函数式组件或自定义Hook中使用Hook。

为了不咱们无心中破坏这些规则,你能够安装一个eslint插件:

npm install eslint-plugin-react-hooks --save-dev

并在配置文件中使用它:

{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

这样,一旦你违法上述这些原则,就会得到相应的提示。

总结

本文介绍了React Hook的使用方式,并经过几个简单的例子演示了如何在函数式组件中进行状态管理和生命周期管理。官方目前提供了不少基础的Hook,如useContext, useReducer, useMemo等,你们能够酌情在项目中使用。

参考资料

https://reactjs.org/docs/hooks-reference.html

——本文首发于我的公众号,转载请注明出处———

 

微信扫描二维码,关注个人公众号
最后,欢迎你们关注个人公众号,一块儿学习交流。
相关文章
相关标签/搜索