React-Hooks进阶用法

本篇文章主要学习内容

  1. useState回调函数形式的参数
  2. useEffect清理副作用
  3. useRef操作DOM
  4. useContext组件通讯

Hooks的注意事项(熟记)

  • react提供的hooks只能在 函数组件内自定义Hooks中使用
  • 自定义Hooks其实就是 函数,但是和普通函数的区别就是,命名使用use开头

1. useState回调函数形式的参数

useState 的两种格式

格式一: 传入值
useState(0) or useState('abc')

格式二: 传入回调
useState((=>{ return 初始值}))

  1. 回调函数的返回值就是作为状态的当前值
  2. 回调函数只能触发一次
    一般情况下我们都是直接传入值,但是你也可以传入一个回调函数,在回调函数中,对值进行一个处理后,作为当前的状态
使用场景

格式一: 传入值

  • 如果状态就是一个普通的数据(比如, 字符串 , 数字 , 数组等) 都可以直接使用useState(普通数据)

格式二: 传入回调

  • 初始状态需要经过一些计算得到 , useState(()=>{这里可以进行计算 , return 结果})
import React, { useState } from 'react'
import ReactDOM from 'react-dom'

export default function App () {
   // 示例
  const [count, setCount] = useState(() => 1 + 1)
  console.log(setCount)
  return <div>通过回调函数计算处理后的值-count:{count}</div>
}

ReactDOM.render(<App />, document.getElementById('root'))

2. useEffect副作用

useEffect发送请求

正确示范:

import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'

export default function App () {
  useEffect(() => {
    const fn = async () => {
      const res = await axios({
        url: 'xxxxx',
        method: 'GET'
      })
      console.log(res)
    }
    fn() // 调用函数发送请求
  }, [])
  return <div />
}

ReactDOM.render(<App />, document.getElementById('root'))

错误示范:

// 不要给 effect 添加 async
useEffect(async () => {
    const res = awiat xxx()
    return ()=> {
        
    }
}, [])

注意: effect 只能是一个同步函数,不能使用 async

3. useEffect-副作用函数的返回值-清理副作用

副作用的返回值
useEffect(() => {
  // 副作用函数的内容

  return 副作用函数的返回值
}, [])

副作用函数的返回值是可选的,可省略。也可以返回一个清理函数,用来清理副作用,类似类组件中生命周期钩子函数的 componentWillUnMount()

useEffect(() => {
  // 副作用函数的内容

  return ()=>{ /* 做清理工作*/ } // 清理函数
}, [])

清理函数的执行时机:

  • 清理函数在组件卸载时以及下一次副作用函数调用之前执行
-用代码来验证一下它的执行时机-

在这里插入图片描述

import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'

export function Com () {
  useEffect(() => {
    console.log('子组件的副作用函数执行....')
    return () => {
      console.log('子组件的副作用函数的清理函数执行...')
    }
  }, [])
  return <div />
}

export function App () {
  const [isShow, setIsShow] = useState(true)
  const [count, SetCount] = useState(0)
  useEffect(() => {
    console.log('父组件的副作用函数的内容')

    return () => {
      console.log('父组件的副作用函数的清理函数的内容')
    }
  }, [count])
  return (
      <>
      {isShow && <Com/>}
      count:{count}
      <button onClick={() => { setIsShow(!isShow) }}>子组件销毁/创建</button>
      <button onClick={() => { SetCount(count + 1) }}>count+1</button>
     </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

子组件

  • 当页面刷新第一次加载的时候,执行一次 副作用函数
    在这里插入图片描述

  • 当删除了 子组件后,触发了子组件副作用函数中的清理函数,输出结果
    在这里插入图片描述

父组件

  • 同样, 当页面刷新创建时, 第一次执行了 副作用函数
    在这里插入图片描述
  • 当我点击 Count+1 的按钮时,改变了 Count的值时(依赖项Count) , 先执行了一次 副作用函数的清理函数, 随后执行了副作用函数
    在这里插入图片描述
  • 因为改变了Count的值, 又导致触发了一次副作用函数
    清理函数会在组件卸载时以及下一次副作用函数调用之前执行
总结

情况1:不带第二个参数。执行时机:初始执行,每次更新之后都要执行。

模拟 componentDidmount() + componentDidUpdate()

useEffect(() => {
  // 副作用函数的内容
})

情况2:带第二个参数,参数是空数组。执行时机:只执行第一次。

模拟 componentDidMount

useEffect(() => {
  // 副作用函数的内容
}, [])

使用场景:1 事件绑定 2 发送请求获取数据 等。

情况3:带第二个参数(数组格式),并指定了依赖项。
执行时机:1.初始执行一次 2. 依赖项的值变化了,执行一次

useEffect(() => {
  // 副作用函数的内容
}, [依赖项1,依赖项2,....])

情况4:依赖项为空,没有具体的副作用函数,有副作用函数的清理函数

模拟:componentWillUnMount()

useEffect(() => {
  
  return () => {
  	// 副作用函数的清理函数,模拟 componentWillUnMount
  }
  
}, [])
下面通过一个小的案例,来测试一下,是否掌握了清理副作用函数

道题导入
删除子组件之后,事件还存在吗?

import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'

function Com1 () {
  useEffect(() => {
    window.addEventListener('mousemove', (e) => {
      console.log(e.pageX, e.pageY)
    })
  }, [])
  return <div>子组件</div>
}
export default function App () {
  const [isShow, setIsShow] = useState(true)
  return (
    <div>
      {isShow && <Com1 />}
      <button onClick={() => setIsShow(!isShow)}>切换</button>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

很明显,就算子组件被销毁了,事件还会存在 , 所以要清除副作用
在这里插入图片描述
解决

import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'

function Com1 () {
  useEffect(() => {
    const fn = (e) => {
      console.log(e.pageX, e.pageY)
    }
    window.addEventListener('mousemove', fn)
    return () => { // 在清理副作用函数中,清除mousemove事件
      window.removeEventListener('mousemove', fn)
    }
  }, [])
  return <div>子组件</div>
}
export default function App () {
  const [isShow, setIsShow] = useState(true)
  return (
    <div>
      {isShow && <Com1 />}
      <button onClick={() => setIsShow(!isShow)}>切换</button>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

在这次的删除子组件后,滑动鼠标,就没有在打印鼠标位置了, 说明事件已经被移除了
在这里插入图片描述

4. useRef-操作DOM

作用:返回一个带有 current 属性的可变对象,通过该对象就可以进行 DOM 操作了

语法 : const inputRef = useRef(null)

  • 参数:在获取 DOM 时,一般都设置为 null (或者直接不填,为空)
  • 返回值:包含 current 属性的对象。
  • 注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)。
  • 注意:useRef不仅仅可以用于操作DOM,还可以操作组件
示例
import React, { useRef } from 'react'
import ReactDOM from 'react-dom'

export default function App () {
  const inputRef = useRef(null)
  return (
      <>
      <input ref={inputRef} type="text" />
      <button onClick={() => {
        console.log(inputRef) // 打印inputRef对象
        console.log(inputRef.current.value) // 输出value值
      }}>点击获取input的value值</button>
      </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

在这里插入图片描述

打印 inputRef
在这里插入图片描述

5. useRef-在多次渲染之间共享数据-停止定时器

函数组件虽然非常直观,简化了思考 UI 实现的逻辑,但是比起 Class 组件,还缺少了一个很重要的能力:在多次渲染之间共享数据。

问题导入
import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
  const [count, setCount] = useState(0)
  let timeId = null
  console.log(timeId)
  useEffect(() => {
    timeId = setInterval(() => {
      setCount((count) => count + 1)
    }, 1000)
    console.log(timeId)
  }, [])

  const hClick = () => {
    console.log(timeId)
    clearInterval(timeId)
  }

  return (
    <div>
      count:{count}
      <button onClick={hClick}>点击停止定时器</button>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

执行以上的代码,当我们执行 停止定时器的时候,发现,定时器并没有被停止,为什么会这样呢?

分析

setCount会导致组件重新渲染,而重新渲染时,会重复执行如下代码
let timeId = null
所以,无法保存timeId

解决
import React, { useEffect, useRef, useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
  const [count, setCount] = useState(0)
  const timeRef = useRef(null)
  console.log(timeRef.current)
  useEffect(() => {
    timeRef.current = setInterval(() => {
      setCount((count) => count + 1)
    }, 1000)
    console.log(timeRef.current)
  }, [])

  const hClick = () => {
    console.log(timeRef.current)
    clearInterval(timeRef.current)
  }

  return (
    <div>
      count:{count}
      <button onClick={hClick}>点击停止定时器</button>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

6. useContext-全局状态

目标

在这里插入图片描述
要求: 在根组件中点击按钮 , 将所有组件用到red 的值都改变

代码

index.js

import React, { useState} from 'react'
import Father from './Father'
import ReactDOM from 'react-dom'

export default function App () {
  const [color, setColor] = useState('red')
  return (
      <div style={{ border: '1px solid #ccc', margin: 10 }}>
        根组件:color:{color}
        <button
          onClick={() => {
            setColor('green')
          }}>
          改成 green
        </button>
        <Father />
      </div>
  )
}
ReactDOM.render(<App />, document.getElementById('root'))

father.js

import Son from './Son.js'
const Father = () => {
  return (
    <div style={{ border: '1px solid #ccc', margin: 10 }}>
      父组件
      <Son />
    </div>
  )
}

export default Father

Son.js

const Son = () => {
    return (
        <div style={{border:'1px solid #ccc', margin:10}}>
        子组件: color: {'red'}</div>)
}

export default Son
useContext步骤

共3步:

  1. 导入并调用createContext方法,得到Context对象,导出
import { createContext } from 'react'
export const Context = createContext()
  1. 使用 Provider 组件包裹根组件,并通过 value 属性提供要共享的数据
return (
  <Context.Provider value={ 这里放要传递的数据 }>
  	<根组件的内容/>
  </Provider>
)
  1. 在任意后代组件中,如果希望获取公共数据:
    导入useContext;调用useContext(第一步中导出的context) 得到value的值
import React, { useContext } from 'react'
import { Context } from './index'
const 函数组件 = () => {
    const 公共数据 = useContext(Context)
    return ( 函数组件的内容 )
}
改造后的代码

index.js

import React, { useState, createContext } from 'react'
import Father from './Father'
import ReactDOM from 'react-dom'

export const Content = createContext()
export default function App () {
  const [color, setColor] = useState('red')
  return (
    <Content.Provider value={color}>
      <div style={{ border: '1px solid #ccc', margin: 10 }}>
        根组件:color:{color}
        <button
          onClick={() => {
            setColor('green')
          }}>
          改成 green
        </button>
        <Father />
      </div>
    </Content.Provider>
  )
}
ReactDOM.render(<App />, document.getElementById('root'))

father.js

import Son from './Son.js'
const Father = () => {
  return (
    <div style={{ border: '1px solid #ccc', margin: 10 }}>
      父组件
      <Son />
    </div>
  )
}

export default Father

Son.js

import { useContext } from 'react'
import { Content } from './index'
const Son = () => {
  const color = useContext(Content)
  return (
    <div style={{ border: '1px solid #ccc', margin: 10 }}>
      子组件: color: {color}
    </div>
  )
}

export default Son

成功 实现在根组件中改变所有组件的值
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值