React进阶 会使用Hook吗?
紫棋的一句想要我帮你唱hook吗?让hook红了一把!
做为一个开发,尤其是前端开发,在电视上听到这个词还是有点小兴奋的(虽然彼hook非此hook)。玩够这个梗,是不是也要了解一下自己的react hook?
一、why hook
使用react有一段时间了,组件化编程也早已成为习惯。常用的两种编写组件的方式就是就是class组件和函数组件。
class组件:通过继承React.Component来构建组件,虽然提供了state状态和完备的生命周期函数,但是也有很多不方便的地方。
- 很多事件需要在挂载期componentDidMount和更新期componentDidUpdate重复书写。有些副作用还需要在卸载期componentWillUnmount卸载。代码重复度很高,而且难以理解,一旦忘记了就会引起不少bug。
componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }
- 通过class书写自然会碰到 this 的问题,要想使用this函数不能忘记绑定。代码十分冗余。
函数组件:通过函数直接书写的组件,看起来是简洁了许多,但是不能使用state,也没有生命周期函数、更不能使用react的一些其他特性。只能通过父组件传递进来,任人鱼肉,只能惨淡的沦为展示组件。
那么,函数组件就只能沦为展示的花瓶吗?能结合class组件能使用react特性的优点和函数组件简洁优雅的特性吗?好在react hook来了,函数组件的春天来了。
React 16.8 新增Hook特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
二、 what's hook
是不是感觉hook挺神奇的?hook是什么?怎么使用?不用惊讶,hook就是一个钩子函数,钩着react的一些api供给函数组件使用。
先看一个官方的useState例子:
import React, { useState } from 'react'; function Example() { // 声明一个叫 “count” 的 state 变量。 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
引入一个hook(useState函数),传入一个初始值0,然后函数返回了一个数组,通过数组的结构赋值,取得state, count = 0和修改count值的函数setCount。当button发生点击事件时,触发setCount函数,并且传入新的count值完成对state值的修改,并展示到页面上。代码转换成class组件如下:
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> ); } }
看明白了吗?
Hook概念
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等(context、refs)特性的函数。
React 内置了一些像 useState 这样的 Hook。你也可以创建你自己的 Hook 来复用不同组件之间的状态逻辑。
hook使用规则
一个组件中可以使用多个hook,每个hook会独立存在,内部状态不会共用。每次更新组件时,hook会按顺序从上到下执行。
function Form() { // 1. Use the name state variable const [name, setName] = useState('Mary'); // 2. Use the age state variable const [age, setAge] = useState('12'); // 3. Use the surname state variable const [surname, setSurname] = useState('Poppins'); // ... } 第一次执行结果: 设置状态name Mary; 设置状态age 12; 设置状态surname Poppins; 第二次执行结果: 设置状态name Mary; 设置状态age 12; 设置状态surname Poppins;
那么,react是如何知道哪个 state 对应 哪一个useState?很简单, React 靠的是 Hook 调用的顺序。
react hook 使用,要遵循下面两个规则:
只在最顶层使用 Hook
react依靠hook调用顺序来对应state,所以调用顺序不能变。function Form() { // 1. Use the name state variable const [name, setName] = useState('Mary'); // 2. Use the age state variable if(!name){ const [age, setAge] = useState('12'); } // 3. Use the surname state variable const [surname, setSurname] = useState('Poppins'); // ... } 第一次执行结果: 设置状态name Mary; 设置状态age 12; 设置状态surname Poppins; 第二次执行结果: 设置状态name Mary; 设置状态age 12;//判断后会被忽略 设置状态surname 12;//此时设置为12,与需求已不符
也就是说只能在顶层使用hook。
- 只在 React 函数中调用 Hook
在class组件中是无法使用hook函数的,只能在函数组件中使用。
在自定义hook中也可以使用。
内置Hook
上面使用的useState Hook是一种可以让你在函数组件内使用state的内置hook,除此之外react还提供了许多内置hook:
基础 Hook
useState useEffect useContext
额外的 Hook
useReducer useCallback useMemo useRef useImperativeHandle useLayoutEffect useDebugValue
下面看一下 useState、useEffect两个常用的hook,其他的使用频率较低,使用方法也都基本一致其他hook
三、state Hook
我们想在函数组件里加入state,点击+,完成数字累加功能:
import React , {useState} from 'react' function Item(){ const [count,setCount] = useState({name:'tom',age:11}) return <> {`${count.name}已经${count.age}岁了`} <div onClick={()=>{setCount({name:'janny',age:count.age+1})}}>+</div> </> } export default Item;
从上面代码可以看出来,useState hook提供了在函数组件里使用state的能力。十分简洁。
首先,通过react引入useState钩子函数;
接着,在函数组件内调用useState,并传入state的初始值,可以是个值,也可以是对象。useState函数的返回值个数组,这里通过数组的结构赋值取得count(变量名可以随意定义)和修改count的方法setCount(函数名也可以随意定义)。
取值state:可以通过变量count直接取值
修改state:通过给setCount函数传递值来修改
是不是很简单?以后终于可以在函数组件里使用state了,拥有控制自己主权的能力了!除了使用state,还有一种场景也是我们经常碰到的,那就是函数组件没有生命周期,一些副作用的操作没法完成!别着急,react又内置了极好的effect hook!
四、effect Hook
当我们点击+时,不仅想完成数字累加,而且想要在title里显示我们的修改,如果在class组件里,我们可以在componentDidMount生命周期里通过setState完成,但是函数组件里没法完成,即使现在有useState Hook也没有生命周期函数,没法完成!那就在来一个hook吧,引入effect Hook。
useEffect hook不仅提供了生命周期,而且useEffect Hook可以看做componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。也就是在这一个钩子函数里可以完成很多事!
1.无需清除的副作用
import React , {useState,useEffect} from 'react' function Item(){ const [count,setCount] = useState({name:'tom',age:11}) useEffect(()=>{ document.title = count.age }) return <> {`${count.name}已经${count.age}岁了`} <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div> </> } export default Item;
好好感受一下!useEffect就像是一个生命周期函数。这个功能在 class 组件中,需要在两个生命周期函数(componentDidMount、componentDidUpdate)中编写重复的代码。在函数useEffect内进行副作用操作就行了。因为每次挂载、更新后useEffect都会执行。
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。上面这种就是不需要清除的副作用。
2.需要清除的副作用
假如,我们觉得只用手指点击太累了,想弄个计时器帮我们执行。
let timer = null; function Item(){ const [count,setCount] = useState({name:'tom',age:11}) useEffect(()=>{ clearInterval(timer);//清除 timer = setInterval(()=>{ console.log(1) setCount({name:'tom',age:count.age+1}) },1000) document.title = count.age }) return <> {`${count.name}已经${count.age}岁了`} <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div> </> }
这样就很棒,换上自动挡就很舒服!但是当我们切换路由,移除这个组件后,出去看看风景回来!发现定时器没有停,自己玩的很嗨...哎呦喂!
这就是需要清除的副作用,class组件里我们可以通过在componentWillUnmount生命周期函数里清除,所以这里我们也要清除这些需要清除的副作用!
function Item(){ const [count,setCount] = useState({name:'tom',age:11}) useEffect(()=>{ clearInterval(timer) timer = setInterval(()=>{ console.log(1) setCount({name:'tom',age:count.age+1}) },1000) document.title = count.age; return function(){//add clearInterval(timer) } }) return <> {`${count.name}已经${count.age}岁了`} <div onClick={()=>{setCount({name:'tom',age:count.age+1})}}>+</div> </> }
useEffect清除副作用的方式也很简单,return 一个函数,在函数里清除就可以了。
3.优化 useEffect
上面说了useEffect hook在每次挂载、更新后都会执行。这不就是跟setState在性能方面造成的问题一样严重吗?class组件 通过在 componentDidUpdate 进行优化,useEffect怎么优化!
可以通过useEffect函数的第二个参数进行优化。
1.传递空数组,表示函数只执行一次!这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。effect 内部的 props 和 state 就会一直拥有其初始值。更接近大家熟悉的 componentDidMount 。
useEffect(()=>{ ... },[])
2.传递包含state的数组
会通过===比较count的前一次渲染的值与这一次要渲染的值,如果全等则React 会跳过这个 effect,这就实现了性能的优化。一定要将所有需要比较的state都放进数组,否则将不会进行比较,也就会直接跳过!
useEffect(()=>{ ... },[count])
五、自定义Hook
react内置了多hook,我们也可以对这些hook进行业务方面的封装,写出属于自己的hook!我们将上面的叠加计时封装成一个自定义hook:数字每秒叠加,并且修改显示在title上。
import React , {useState,useEffect} from 'react' function useTitle(){//自定义hook let timer = null; const [count,setCount] = useState({name:'tom',age:11}) useEffect(()=>{ clearInterval(timer) timer = setInterval(()=>{ setCount({name:'tom',age:count.age+1}) },1000) document.title = count.age; return function(){ clearInterval(timer) } },[count]) return count;//返回一个对象 } function Item(){//自定义hook使用 const count = useTitle() return <> {`${count.name}已经${count.age}岁了`} </> }
将一些公共代码提取出来,最后返回需要在页面显示的数据就成了
六、总结
到这里,react hook已经有所了解了吧!就是让函数组件能够像class组件一样自由自在的使用react的特性的函数。可以不影响之前任何业务,在项目中完全可选、100%向后兼容的react新特性!快在项目中用起来吧!
如有不妥!欢迎指正