ReactJS语法-React Hooks

简介

  1. Hook 是 React 16.8 的新增特性,它可在不编写class的情况下使用state以及其他的React特性(比如生命周期)。
  2. class组件相对于函数式组件优势
    1. class组件可以定义自己的state,用来保存组件自己内部的状态;函数式组件不可以,因为函数每次调用都会产生新的临时变量;
    2. class组件有自己的生命周期,可以在对应的生命周期中完成自己的逻辑;函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
    3. class组件可以在状态改变时只会重新执行render函数以及希望重新调用的生命周期函数componentDidUpdate等;函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次
  3. Class组件存在的问题
    1. 复杂组件变得难以理解:随着业务的增多,class组件会变得越来越复杂;因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
    2. 组件复用状态很难:一些状态的复用需要通过高阶组件,的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;

Hook作用

  1. 它可以让我们在不编写class的情况下使用state以及其他的React特性;
  2. Hook的出现基本可以代替之前所有使用class组件的地方;
  3. 但是如果是一个旧的项目,并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,可以渐进式的来使用它;
  4. Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;

案例

  1. 和函数式组件结合hooks实现计数器案例

     //CounterHook.jsx
     import { memo, useState } from "react";
        
     // 普通的函数, 里面不能使用hooks
     // function foo() {
     // 不能使用hooks
     //   const [ message ] = useState("Hello World")
     //   return message
     // }
        
     // 在自定义的hooks中, 可以使用react提供的其他hooks: 必须使用use开头
     // function useFoo() {
     //   const [ message ] = useState("Hello World")
     //   return message
     // }
        
     function CounterHook(props) {
       //只能在函数组件最外层调用 Hook
       const [counter, setCounter] = useState(0)
       //普通函数
       // const message = foo()
       //自定义hooks
       // const message = useFoo()
        
       return (
         <div>
           <h2>当前计数: {counter}</h2>
           <button onClick={e => setCounter(counter+1)}>+1</button>
           <button onClick={e => setCounter(counter-1)}>-1</button>
         </div>
       )
     }
     export default memo(CounterHook)
    
  2. useState来自react,需要从react中导入,它是一个hook;
    1. 参数:初始化值,如果不设置为undefined
    2. 返回值:数组,包含两个元素,
      1. 元素一:当前状态的值(第一调用为初始化值);
      2. 元素二:设置状态值的函数;
  3. 点击button按钮后,会完成两件事情
    1. 调用setCount,设置一个新的值;
    2. 组件重新渲染,并且根据新的值返回DOM结构;
  4. 使用两个额外的规则:
    1. 只能在函数组件最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
    2. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用

useState

  1. State Hook的API就是 useState
  2. useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。
  3. 一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
  4. useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined)。
  5. useState的返回值是一个数组,可以通过数组的解构,来完成赋值会非常方便。

useEffect

  1. Effect Hook 可以完成一些类似于class中生命周期的功能;
  2. 事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects)
  3. 所以对于完成这些功能的Hook被称之为 Effect Hook;
  4. useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
  5. 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;

清除Effect

  1. 在class组件的编写过程中,某些副作用的代码,需要在componentWillUnmount中进行清除:
  2. useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:这个函数B就是清楚函数
  3. 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;
  4. 如此可以将添加和移除订阅的逻辑放在一起;它们都属于 effect 的一部分;
  5. React 何时清除 effect?
    1. React 会在组件更新和卸载的时候执行清除操作

举例:

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

const App = memo(() => {
  const [count, setCount] = useState(200)
 // 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
  useEffect(() => {
    // 当前传入的回调函数会在组件被渲染完成后, 自动执行
    // 网络请求/DOM操作(修改标题)/事件监听
    document.title = count
    
    
    console.log("监听redux中数据变化, 监听eventBus中的why事件")
    
    // 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
    return () => {
      console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")
    }
  })

  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={e => setCount(count+1)}>+1</button>
    </div>
  )
})

export default App

使用多个Effect

  1. 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
    1. 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中;
  2. 使用Effect Hook,我们可以将它们分离到不同的useEffect中:
  3. React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;
import React, { memo, useEffect } from 'react'
import { useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  // 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
  useEffect(() => {
    // 1.修改document的title(1行)
    console.log("修改title")
  })

  // 一个函数式组件中, 可以存在多个useEffect
  useEffect(() => {
    // 2.对redux中数据变化监听(10行)
    console.log("监听redux中的数据")
    return () => {
      // 取消redux中数据的监听
    }
  })

  useEffect(() => {
    // 3.监听eventBus中的xxx事件(15行)
    console.log("监听eventBus的xx事件")
    return () => {
      // 取消eventBus中的why事件监听
    }
  })

  return (
    <div>
      <button onClick={e => setCount(count+1)}>+1({count})</button>
    </div>
  )
})

export default App

Effect性能优化

  1. 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问
    1. 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅);
    2. 多次执行也会导致一定的性能问题;
  2. 如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?useEffect实际上有两个参数:
    1. 参数一:执行的回调函数;
    2. 参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)
  3. 如果一个函数不希望依赖任何的内容时(即整个组件生命周期只执行一次),也可以传入一个空的数组 []:
    1. 那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了;
  4. 举例:

     import React, { memo, useEffect } from 'react'
     import { useState } from 'react'
        
     const App = memo(() => {
       const [count, setCount] = useState(0)
       const [message, setMessage] = useState("Hello World")
        
       // [count]只有cont发生改变才会执行,message改变,不会受影响
       useEffect(() => {
         console.log("修改title:", count)
       }, [count])
        
       // []不依赖任何state元素,只会执行一次
       useEffect(() => {
         console.log("监听redux中的数据")
         return () => {}
       }, [])
        
       useEffect(() => {
         console.log("监听eventBus的why事件")
         return () => {}
       }, [])
        
       useEffect(() => {
         console.log("发送网络请求, 从服务器获取数据")
        
         return () => {
           console.log("会在组件被卸载时, 才会执行一次")
         }
       }, [])
        
       return (
         <div>
           <button onClick={e => setCount(count+1)}>+1({count})</button>
           <button onClick={e => setMessage("你好啊")}>修改message({message})</button>
         </div>
       )
     })
        
     export default App
    

useRef

  1. useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
  2. 最常用的ref是用来获取react中的dom
import React, { memo, useRef } from 'react'

const App = memo(() => {
  const titleRef = useRef()
  const inputRef = useRef()
  
  function showTitleDom() {
    //获取到h2的dom
    console.log(titleRef.current)
    //获取到input的dom,点击按钮直接将input聚焦
    inputRef.current.focus()
  }

  return (
    <div>
      <h2 ref={titleRef}>Hello World</h2>
      <input type="text" ref={inputRef} />
      <button onClick={showTitleDom}>查看title的dom</button>
    </div>
  )
})

export default App

自定义Hook

  1. 自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性
  2. 自定义Hook的函数必须以use开头
  3. 需求:所有的组件在创建和销毁时都进行打印
    1. 组件被创建:打印 组件被创建了
    2. 组件被销毁:打印 组件被销毁了
  4. 举例

     import React, { memo, useEffect, useState } from 'react'
    
     // 自定义hook,必须用use开头的函数定义
     function useLogLife(cName) {
       useEffect(() => {
         console.log(cName + "组件被创建")
         return () => {
           console.log(cName + "组件被销毁")
         }
       }, [cName])
     }
        
     const Home = memo(() => {
       useLogLife("home")
        
       return <h1>Home Page</h1>
     })
        
     const About = memo(() => {
       useLogLife("about")
        
       return <h1>About Page</h1>
     })
        
     const App = memo(() => {
       const [isShow, setIsShow] = useState(true)
        
       useLogLife("app")
        
       return (
         <div>
           <h1>App Root Component</h1>
           <button onClick={e => setIsShow(!isShow)}>切换</button>
           { isShow && <Home/> }
           { isShow && <About/> }
         </div>
       )
     })
        
     export default App
    
Table of Contents