React Hook 的使用

React Hook


1. Hook 概述

Hook 是 React 16.8 的新增特性,可以在不写 class 的情况下使用 state 以及其他 React 特性

  1. 在不写 class 的情况下使用 state 以及其他 React 特性

  2. Hook 单词意思是 钩、挂钩、钓钩的意思

  3. 因此在 React 中,Hook 是指在函数组件中 “ 钩入” React state 以及生命周期等特性的 函数

    • 说白了:就是让 函数组件拥有私有数据和钩子函数等

    • Hook 是一个特殊的函数,它可以让你 “ 钩入” React 的特性

    • 实际上就是 React 给提供的一些 Hook 函数

  4. 注意事项

    • Hook 不能在 class 组件中使用,只能够在函数组件中使用
    • 在 React 引入 Hook 以后, React 中的组件分为 函数组件 和 类组件 两种,不能够在称为有无状态组件

通俗的将: Hook 本质就是 JavaScript 函数,可以让开发者不写 class 类的情况下使用 state 以及其他 React 特性



2. 为什么会出现 Hook

为什么使用 Hook 创建组件,不使用类组件了呢 ? 类组件有哪些缺陷呢 ?

之前是如何实现组件状态逻辑复用的 ? render props、HOC

  1. 组件之间复用状态逻辑复杂

    • React 没有提供类似 Vue 的 mixins 以及 小程序的 behavior 特性
    • 可以使用 render props 和 HOC 实现组件的复用,但是会让代码很难理解
    • 在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成 “嵌套地狱”
    • 可以使用 Hook 从组件中提取状态逻辑,不会对组件的结构进行修改的情况下实现组件复用
  2. class 难以理解

    • 事件处理的 this 指向问题
    • 繁琐的生命周期函数
    • 对于函数组件与 class 组件分歧比较大,使用时需要区分两种组件的使用场景
  3. 总结

    • Hook 解决了类组件经常被诟病的一些问题
    • Hook 让所有组件都是一个函数,不需要在创建 class 类组件
      • 使用类创建组件的方式并不会被取消,被遗弃
    • 更加充分体现 React 函数式编程思想,拥抱函数式编程,使代码的变得复用性更高,更加简洁



3. 什么时候使用 Hook

场景:使用函数组件声明 UI 结构,需要使用到 state 私有数据

  1. 传统做法

    • 将函数组件改为类组件,创建 state
  2. 现在做法

    • 直接在项目中使用 Hook 即可



4. Hook 的基本使用

  1. 首先创建一个 class 类组件,实现简易计算器

    import React, { Component } from 'react'
    
    export default class App extends Component {
      constructor() {
        super()
    
        this.state = {
          count: 1
        }
      }
    
      // 实现加 1 的方法
      addHandle = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
    
      render() {
        const { count } = this.state
        return (
          <div>
            <p>{count}</p>
            <button onClick={this.addHandle}>加 1</button>
          </div>
        )
      }
    }
    
    
  2. 使用 Hook 方法实现简易计算器

    import React, { useState } from 'react'
    
    function App() {
      // 1. 初始化数据
      // 2. 定义操作数据的方法
    
      // 调用 useState 以后,返回一个 state,以及更新 state 的函数
      // 第一个参数:返回的状态:在初始化渲染期间,state 就是 useState 传递的第一个值
      // 第二个参数:更新 state 的方法,方法名称自定义,
      const [state, setState] = useState(0)
    
      // 点击加 1 的方法
      const addHandle = () => {
        setState(state + 1)
      }
    
      return (
        <div>
          <p>{state}</p>
          <button onClick={ addHandle }>加 1</button>
        </div>
      )
    }
    
    export default App
    
    



5. State Hook 的使用


5.1 声明变量
  1. 导入 useState 函数

  2. 调用 useState 函数,并传入初始 state

    • 唯一的参数:初始的 state
    • 值可以为数字、字符串、对象
    • 如果需要存储多个不同的变量,需要调用 useState() 多次
  3. 定义变量接收返回的值

    • 返回值为:当前 state 以及更新 state 的函数
    import React, { useState } from 'react'
    
    function App() {
      // 1. 初始化数据
      // 2. 定义操作数据的方法
    
      // 调用 useState 以后,返回一个 state,以及更新 state 的函数
      // 第一个参数:返回的状态:在初始化渲染期间,state 就是 useState 传递的第一个值
      // 第二个参数:更新 state 的方法,方法名称自定义,
      const [state, setState] = useState(0)
    
      // 点击加 1 的方法
      const addHandle = () => {
        setState(state + 1)
      }
    
      return (
        <div>
          <p>{state}</p>
          <button onClick={ addHandle }>加 1</button>
        </div>
      )
    }
    
    export default App
    
    



5.2 使用变量
  1. 在函数组件中,直接使用 {} 表达式包裹住 state 值即可

    <p>{state}</p>
    



5.3 更新变量
  1. 在 useState 函数的返回值中包含了setCount 和 count 变量,直接使用即可

    const addHandle = () => {
      setState(state + 1)
    }
    



5.4 使用多个 state 变量
  1. 如果需要存储两个不同的变量,需要调用 useState() 两次

  2. 注意:不必使用多个 state 变量。State 变量可以很好地存储对象和数组

    import React, { useState } from 'react'
    
    function App() {
      // 声明多个变量
      const [state, setState] = useState(0)
      const [name, setName] = useState('亚瑟')
      const [heros, setHeros] = useState(['亚瑟', '妲己'])
      const [profile, setProfile] = useState({ name: '黑化后的亚瑟', age: 10 })
    
      const addHandle = () => {
        // 更新 state
        setState(state + 1)
      }
    
      const changeName = () => {
        // 更新 name
        setName('老亚瑟')
      }
    
      return (
        <div>
          {/* 使用变量 */}
          <p>{state}</p>
          <p>{name}</p>
          <p>{heros[1]}</p>
          <p>{profile.name}</p>
    
          <button onClick={addHandle}>加 1</button>
          <button onClick={changeName}>更改 name</button>
        </div>
      )
    }
    
    export default App
    
    



6. Effect Hook 的使用

Effect Hook 可以让函数组件执行副作用操作

可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

  1. 什么是副作用:指的是函数内部与外部互动,产生运算、渲染以外的其他结果

  2. 副作用主要体现在纯函数中,纯函数是所有函数式编程语言中使用的概念,是一个非常重要的概念

    • 什么是纯函数:一个函数的返回结果只依赖 于它的参数,并且在执行过程里面没有与函数外部的数据进行交互

    • 纯函数是指相同的输入永远会得到相同的输出,而且没有任何可观察的副作用,而副作用会让函数变的不纯,如果函数依赖外部状态就无法保证输出相同,就会带来副作用。

  3. 常见的副作用操作:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用

  4. 在 React 组件中有两种常见副作用操作:需要清除的和不需要清除



6.1 基本使用(无需清除)

案例:在 React 计算器案例中,当 React 对 DOM 进行操作之后,立即更新了 document 的 title 属性

  1. class 组件写法

    import React, { Component } from 'react'
    
    export default class App extends Component {
      constructor() {
        super()
    
        this.state = {
          count: 1
        }
      }
    
      // 实现加 1 的方法
      addHandle = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
    
      componentDidMount () {
        document.title = `count: ${this.state.count}`
      }
    
      componentDidUpdate () {
        document.title = `count: ${this.state.count}`
      }
    
      render() {
        const { count } = this.state
        return (
          <div>
            <p>{count}</p>
            <button onClick={this.addHandle}>加 1</button>
          </div>
        )
      }
    }
    
    
  2. Hook 写法

    • 导入 useEffect 方法

      • useEffect 方法在每次渲染后都会执行,包括第一次渲染以后

      • 以后渲染时候方法告诉 React 组件在渲染后需要执行哪些操作

  • 调用 useEffect 方法,传入一个函数作为参数,在函数中进行逻辑的操作

    • 在 useEffect 函数参数内部,还能够返回一个函数
    • 这个函数是在组件销毁阶段 componentWillUnmount 执行的
      • 这个返回的函数只在组件销毁的时候触发
    import React, { useState, useEffect } from 'react'
    
    function App() {
      const [state, setState] = useState(0)
    
      // 点击加 1 的方法
      const addHandle = () => {
        // 使用 setState 和 state 更新变量
        setState(state + 1)
      }
    
      useEffect(() => {
        document.title = `count: ${state}`
      })
    
      return (
        <div>
          <p>{state}</p>
          <button onClick={addHandle}>加 1</button>
        </div>
      )
    }
    
    export default App
    
    



6.2 需要清除

案例:在 React 计算器案例中,当 React 对 DOM 进行操作之后,立即更新了 document 的 title 属性

  1. class 组件写法

    import React, { Component, useState } from 'react'
    
    class App extends Component {
      constructor() {
        super()
    
        this.state = {
          count: 1
        }
      }
    
      // 实现加 1 的方法
      addHandle = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
    
      componentDidMount() {
        document.title = `count: ${this.state.count}`
    
        this.timerId = setInterval(() => {
          console.log('1')
        }, 1000)
      }
    
      componentDidUpdate() {
        document.title = `count: ${this.state.count}`
      }
    
      componentWillUnmount () {
        clearInterval(this.timerId)
      }
    
      render() {
        const { count } = this.state
        return (
          <div>
            <p>{count}</p>
            <button onClick={this.addHandle}>加 1</button>
          </div>
        )
      }
    }
    
    class Test extends Component {
    
      state = {
        isFlag: true
      }
    
      isFlagHandle = () => {
        this.setState({
          isFlag: false
        })
      }
    
      render() {
        const { isFlag } = this.state
        return (
          <div>
            {
              isFlag ? <App></App> : ''
            }
            <hr />
            <button onClick={this.isFlagHandle}>动态控制 App</button>
          </div>
        )
      }
    }
    
    export default Test
    
    
  2. Hook 写法

    import React, { useState, useEffect } from 'react'
    
    function App() {
      const [state, setState] = useState(0)
    
      // 点击加 1 的方法
      const addHandle = () => {
        // 使用 setState 和 state 更新变量
        setState(state + 1)
      }
    
      useEffect(() => {
        const timerId = setInterval(() => {
          console.log('1')
        }, 1000)
    
        return function () {
          clearInterval(timerId)
        }
      })
    
      return (
        <div>
          <p>{state}</p>
          <button onClick={addHandle}>加 1</button>
        </div>
      )
    }
    
    function Test () {
      const [isFlag, setIsFlag] = useState(true)
      const isFlagHandle = () => {
        setIsFlag(false)
      }
      return (
        <div>
          {
            isFlag ? <App></App> : ''
          }
          <hr />
          <button onClick={ isFlagHandle }>动态控制 App</button>
        </div>
      )
    }
    
    export default Test
    
    



7. usecontext Hook 的使用

  1. 调用 React.createContext 方法,产生一个 context 对象

  2. useContext 接收一个 context 对象(React.createContext 的返回值)并返回传递的数据,

  3. 接收到当前的 context 值,由定义的 MyContext.Provider 的 value prop 决定

  4. 当组件上层最近的 MyContext.Provider 更新时,该 Hook 会触发重渲染

    • 并使用最新传递给 MyContext provider 的 context value 值
    import React, { useContext } from 'react'
    
    const themes = {
      light: {
        foreground: "#000000",
        background: "#eeeeee"
      },
      dark: {
        foreground: "#ffffff",
        background: "#222222"
      }
    }
    
    const ThemeContext = React.createContext(themes.light)
    
    function App() {
      return (
        <ThemeContext.Provider value={themes.dark}>
          <Toolbar />
        </ThemeContext.Provider>
      )
    }
    
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      )
    }
    
    function ThemedButton() {
      const theme = useContext(ThemeContext);
      return (
        <button style={{ background: theme.background, color: theme.foreground }}>
          I am styled by theme context!
        </button>
      );
    }
    
    export default App
    



8. useref Hook 的使用

  1. useRef 返回一个可变的 ref 对象

  2. useRef 的 current 属性被初始化为传入的参数 initialValue

    import React, { useRef } from 'react'
    
    function App () {
      const myInput = useRef(null)
      const handle = () => {
        // myInput.current.focus()
        console.log(myInput.current.value)
      }
      return (
        <div>
          <p>
            <input ref={myInput} type="text" />
          </p>
          <button onClick={ handle }>input 高亮</button>
        </div>
      )
    }
    
    export default App
    



9. 自定义 Hook

自定义 Hook,可以将组件逻辑提取到可重用的函数中

  1. 当我们想在两个函数之间共享逻辑时,我们通常的做法是将它提取到第三个函数中,
  2. 组件和 Hook 都是函数,所以也同样适用这种方式
  3. 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性
  4. 自定义 Hook 是一个函数,函数名约定始终以 use 开头>



9.1 复用坐标点案例
  1. 自定义 Hook 是一个函数,函数名约定始终以 use 开头>

    • 自定义 Hook 必须以 use 开头 !!!
    import React, { useState, useEffect } from 'react'
    
    // 导入二哈图片
    import dog from './haha.jpg'
    
    // 封装复用的代码
    function useMouse(params) {
      const [point, setPoint] = useState({ x: 0, y: 0 })
    
      const handle = (e) => {
        const newPoint = {
          x: e.clientX,
          y: e.clientY
        }
        setPoint(newPoint)
      }
    
      // 监听鼠标坐标点的值
      useEffect(() => {
        // 监听鼠标移动事件
        window.addEventListener('mousemove', handle)
    
        // 销毁鼠标移动事件
        return function (params) {
          window.removeEventListener('mousemove', handle)
        }
      })
    
      return point
    }
    
    // 坐标点组件
    function Point() {
      // 获取返回的数据
      const mouse = Mouse()
    
      return (
        <div>
          { mouse.x } -- { mouse.y }
        </div>
      )
    }
    
    // 跟随鼠标移动的哈士奇
    function CatMouse(params) {
      // 获取返回的数据
      const mouse = Mouse()
    
      return (
        <img src={dog} style={{
          width: '100px',
          height: '100px',
          position: 'absolute',
          borderRadius: '50%',
          top: mouse.y - 50,
          left: mouse.x - 50
        }} alt=""/>
      )
    }
    
    function App() {
      return (
        <div>
          <Point />
          <CatMouse />
        </div>
      )
    }
    
    export default App
    
    



10. Hook 规则

  1. 只在最顶层使用 Hook

    • 不要在循环、条件判断或嵌套函数中调用 Hook,
    • 遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用
  2. 只在 React 函数组件中调用 Hook

    • 不要在普通的 JavaScript 函数中调用 Hook
    • 可以在 React 的函数组件中调用 Hook
    • 可在自定义 Hook 中调用其他 Hook

.

posted @ 2021-02-16 01:11  卦师  阅读(102)  评论(0)    收藏  举报