欢迎关注个人公众号睿Talk
,获取我最新的文章:
javascript
React Hooks 是从 v16.8 引入的又一开创性的新特性。第一次了解这项特性的时候,真的有一种豁然开朗,发现新大陆的感受。我深深的为 React 团队天马行空的创造力和精益求精的钻研精神所折服。本文除了介绍具体的用法外,还会分析背后的逻辑和使用时候的注意事项,力求作到知其然也知其因此然。java
这个系列分上下两篇,这里是下篇的传送门:
React Hooks 解析(下):进阶react
Hooks
的出现是为了解决 React 长久以来存在的一些问题:redux
为了解决这个问题,须要引入render props
或higher-order components
这样的设计模式,如react-redux
提供的connect
方法。这种方案不够直观,并且须要改变组件的层级结构,极端状况下会有多个wrapper
嵌套调用的状况。segmentfault
Hooks
能够在不改变组件层级关系的前提下,方便的重用带状态的逻辑。设计模式
大量的业务逻辑须要放在componentDidMount
和componentDidUpdate
等生命周期函数中,并且每每一个生命周期函数中会包含多个不相关的业务逻辑,如日志记录和数据请求会同时放在componentDidMount
中。另外一方面,相关的业务逻辑也有可能会放在不一样的生命周期函数中,如组件挂载的时候订阅事件,卸载的时候取消订阅,就须要同时在componentDidMount
和componentWillUnmount
中写相关逻辑。数组
Hooks
能够封装相关联的业务逻辑,让代码结构更加清晰。网络
JS 中的this
关键字让很多人吃过苦头,它的取值与其它面向对象语言都不同,是在运行时决定的。为了解决这一痛点,才会有剪头函数的this
绑定特性。另外 React 中还有Class Component
和Function Component
的概念,何时应该用什么组件也是一件纠结的事情。代码优化方面,对Class Component
进行预编译和压缩会比普通函数困可贵多,并且还容易出问题。app
Hooks
能够在不引入 Class 的前提下,使用 React 的各类特性。函数
Hooks are functions that let you “hook into” React state and lifecycle features from function components
上面是官方解释。从中能够看出 Hooks 是函数,有多个种类,每一个 Hook 都为Function Component
提供使用 React 状态和生命周期特性的通道。Hooks 不能在Class Component
中使用。
React 提供了一些预约义好的 Hooks 供咱们使用,下面咱们来详细了解一下。
先来看一个传统的Class Component
:
class Example 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> ); } }
使用 State Hook 来改写会是这个样子:
import React, { useState } from 'react'; function Example() { // 定义一个 State 变量,变量值能够经过 setCount 来改变 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
能够看到useState
的入参只有一个,就是 state 的初始值。这个初始值能够是一个数字、字符串或对象,甚至能够是一个函数。当入参是一个函数的时候,这个函数只会在这个组件初始渲染的时候执行:
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
useState
的返回值是一个数组,数组的第一个元素是 state 当前的值,第二个元素是改变 state 的方法。这两个变量的命名不须要遵照什么约定,能够自由发挥。要注意的是若是 state 是一个对象,setState 的时候不会像Class Component
的 setState 那样自动合并对象。要达到这种效果,能够这么作:
setState(prevState => { // Object.assign 也能够 return {...prevState, ...updatedValues}; });
从上面的代码能够看出,setState 的参数除了数字、字符串或对象,还能够是函数。当须要根据以前的状态来计算出当前状态值的时候,就须要传入函数了,这跟Class Component
的 setState 有点像。
另一个跟Class Component
的 setState 很像的一点是,当新传入的值跟以前的值同样时(使用Object.is
比较),不会触发更新。
解释这个 Hook 以前先理解下什么是反作用。网络请求、订阅某个模块或者 DOM 操做都是反作用的例子,Effect Hook 是专门用来处理反作用的。正常状况下,在Function Component
的函数体中,是不建议写反作用代码的,不然容易出 bug。
下面的Class Component
例子中,反作用代码写在了componentDidMount
和componentDidUpdate
中:
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> ); } }
能够看到componentDidMount
和componentDidUpdate
中的代码是同样的。而使用 Effect Hook 来改写就不会有这个问题:
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> ); }
useEffect
会在每次 DOM 渲染后执行,不会阻塞页面渲染。它同时具有componentDidMount
、componentDidUpdate
和componentWillUnmount
三个生命周期函数的执行时机。
此外还有一些反作用须要组件卸载的时候作一些额外的清理工做的,例如订阅某个功能:
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'; } }
在componentDidMount
订阅后,须要在componentWillUnmount
取消订阅。使用 Effect Hook 来改写会是这个样子:
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); // 返回一个函数来进行额外的清理工做: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
当useEffect
的返回值是一个函数的时候,React 会在下一次执行这个反作用以前执行一遍清理工做,整个组件的生命周期流程能够这么理解:
组件挂载 --> 执行反作用 --> 组件更新 --> 执行清理函数 --> 执行反作用 --> 组件更新 --> 执行清理函数 --> 组件卸载
上文提到useEffect
会在每次渲染后执行,但有的状况下咱们但愿只有在 state 或 props 改变的状况下才执行。若是是Class Component
,咱们会这么作:
componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } }
使用 Hook 的时候,咱们只须要传入第二个参数:
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 只有在 count 改变的时候才执行 Effect
第二个参数是一个数组,能够传多个值,通常会将 Effect 用到的全部 props 和 state 都传进去。
当反作用只须要在组件挂载的时候和卸载的时候执行,第二个参数能够传一个空数组[]
,实现的效果有点相似componentDidMount
和componentWillUnmount
的组合。
本文介绍了在 React 以前版本中存在的一些问题,而后引入 Hooks 的解决方案,并详细介绍了 2 个最重要的 Hooks:useState
和useEffect
的用法及注意事项。原本想一篇写完全部相关的内容,但发现坑有点深,只能分两次填了:)