React进阶之路(三)-- Hooks

文章目录

Hooks概念理解

什么是Hooks

Hooks的本质:一套能够使函数组件更强大,更灵活的"钩子"

注意点:

  1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
  2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
  3. hooks只能在函数组件中使用

Hooks解决了什么问题

Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题

  1. 组件的逻辑复用:在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
  2. class组件自身的问题:class组件就像一个厚重的'战舰' 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇'

useState

基础使用

作用:

useState为函数组件提供状态(state)

使用步骤:

  1. 导入 useState 函数
  2. 调用 useState 函数,并传入状态的初始值
  3. 从useState函数的返回值中,拿到状态和修改状态的方法
  4. 在JSX中展示状态
  5. 调用修改状态的方法更新状态

代码实现:

javascript 复制代码
import { useState } from 'react'

function App() {
  // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
  // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}
export default App

状态的读取和修改

读取状态

该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用

修改状态

  1. setCount是一个函数,参数表示最新的状态值
  2. 调用该函数后,将使用新值替换旧值
  3. 修改状态后,由于状态发生变化,会引起视图变化

注意事项

  1. 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型

组件的更新过程

函数组件使用 useState hook 后的执行过程,以及状态值的变化

  • 组件第一次渲染
    • 从头开始执行该组件中的代码逻辑
    • 调用 useState(0) 将传入的参数作为状态初始值,即:0
    • 渲染组件,此时,获取到的状态 count 值为: 0
  • 组件第二次渲染
    • 点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
    • 组件重新渲染时,会再次执行该组件中的代码逻辑
    • 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
    • 再次渲染组件,此时,获取到的状态 count 值为:1

重点一句话:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值

javascript 复制代码
import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  // 在这里可以进行打印测试
  console.log(count)
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}
export default App

使用规则

  • useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态 (也就是每个状态互不影响)

    javascript 复制代码
    function List(){
      // 以字符串为初始值
      const [name, setName] = useState('cp')
      // 以数组为初始值
      const [list,setList] = useState([])
    }
  • useState 注意事项

    • 只能出现在函数组件或者其他hook函数中

    • 不能嵌套在if/for/其它函数中,只能在函数组件的最外层去调用(react按照hooks的调用顺序识别每一个hook)

      javascript 复制代码
      let num = 1
      function List(){
        num++
        if(num / 2 === 0){
           const [name, setName] = useState('cp') 
        }
        const [list,setList] = useState([])
      }
      // 俩个hook的顺序不是固定的,这是不可以的!!!

回调函数作为参数

使用场景

如果初始 state 需要通过计算才能获得,则可以传入一个函数。参数(也就是回调函数)只会在组件的初始渲染中起作用,后续渲染时会被忽略。

语法:

javascript 复制代码
const [name, setName] = useState(()=>{   
  // 编写计算逻辑    return '计算之后的初始值'
})

语法规则

  1. 回调函数return出去的值将作为 name 的初始值
  2. 回调函数中的逻辑只会在组件初始化的时候执行一次

语法选择

  1. 如果就是初始化一个普通的数据 直接使用 useState(普通数据) 即可

  2. 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{})

例如我们来一个需求:

javascript 复制代码
import { useState } from 'react'

function Counter(props) {
  const [count, setCount] = useState(() => {
    return props.count
  })
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

function App() {
  return (
    <>
      <Counter count={10} />
      <Counter count={20} />
    </>
  )
}

export default App

useEffect

什么是函数副作用

什么是副作用?

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)

常见的副作用

  1. 数据请求 ajax发送
  2. 手动修改dom
  3. localstorage操作

useEffect函数的作用就是为react函数组件提供副作用处理的!

你可以把useEffect看作是React生命周期方法的一种替代,useEffect可以模拟componentDidMount,componentDidUpdate,和componentWillUnmount的行为。你可以在一个组件中使用多个useEffect,以便将不同的副作用分开处理。useEffect是一个Hook,所以你只能在组件的顶层或者你自定义的Hook中调用它,不能在循环或条件语句中调用它。如果你不需要与外部系统同步,你可能不需要使用useEffect。

基础使用

作用

为react函数组件提供副作用处理

使用步骤

  1. 导入 useEffect 函数
  2. 调用 useEffect 函数,并传入回调函数
  3. 在回调函数中编写副作用处理(例如:dom操作)
  4. 修改数据状态
  5. 检测副作用是否生效
javascript 复制代码
import { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
 
  useEffect(()=>{
    // dom操作
    document.title = `当前已点击了${count}次`
  })
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}

export default App

依赖项控制执行时机

1.不添加依赖项

组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行

  1. 组件初始渲染
  2. 组件更新 (不管是哪个状态引起的更新)
javascript 复制代码
useEffect(()=>{
    console.log('副作用执行了')
})

2.添加空数组

组件只在首次渲染时执行一次

javascript 复制代码
useEffect(()=>{
	 console.log('副作用执行了')
},[])

3.添加特定依赖项

副作用函数在首次渲染时执行,在依赖项发生变化时重新执行

javascript 复制代码
function App() {  
    const [count, setCount] = useState(0)  
    const [name, setName] = useState('zs') 
    
    useEffect(() => {    
        console.log('副作用执行了')  
    }, [count])  
    
    return (    
        <>      
         <button onClick={() => { setCount(count + 1) }}>{count}</button>      
         <button onClick={() => { setName('cp') }}>{name}</button>    
        </>  
    )
}

注意事项:

  • useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
  • useEffect回调是在dom渲染之后执行
  • useEffect和vue里的watch有点像,但是执行时机是不同的

清理副作用

如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑。

注意执行时机为:

  1. 组件卸载时自动执行
  2. 组件更新时,下一个useEffect副作用函数执行之前自动执行
javascript 复制代码
import { useEffect, useState } from "react"


const App = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const timerId = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => {
      // 用来清理副作用的事情
      clearInterval(timerId)
    }
  }, [count])
  return (
    <div>
      {count}
    </div>
  )
}

export default App

发送网络请求

使用场景

如何在useEffect中发送网络请求,并且封装同步 async await操作

语法要求

useEffect中不能是一个异步函数的原因是,useEffect的第一个参数应该是一个返回undefined或者一个清理函数的函数,而不是一个返回Promise的函数。异步函数会返回一个Promise,这个Promise不能被当作一个清理函数来调用。这样做会导致useEffect的行为不符合预期,可能会出现内存泄漏或者数据不一致的问题。

正确写法

如果你想在useEffect中使用异步函数,你有两种方法:

  • 在useEffect内部定义一个异步函数,并立即调用它。
  • 在useEffect外部定义一个异步函数,并在useEffect内部调用它。但是这样做的话,你需要把这个异步函数放到useEffect的依赖数组中,并且用useCallback来包裹它,以防止不必要的调用。
javascript 复制代码
useEffect(() => {
  // 定义一个异步函数
  const fetchData = async () => {
    // 你可以在这里使用await
    const response = await MyAPI.getData(someId);
    // ...
  }
  // 立即调用这个异步函数
  fetchData();
}, [someId]); // 或者[]如果副作用不需要props或state
javascript 复制代码
// 在useEffect外部定义一个异步函数,并用useCallback包裹它
const fetchData = useCallback(async () => {
  // 你可以在这里使用await
  const response = await MyAPI.getData(someId);
  // ...
}, [someId]); // 这里的依赖项要和useEffect的一致

useEffect(() => {
  // 在useEffect内部调用这个异步函数
  fetchData();
}, [fetchData]); // 这里的依赖项要包含这个异步函数

useRef

使用场景

在函数组件中获取真实的dom元素对象或者是组件对象

使用步骤

  1. 导入 useRef 函数
  2. 执行 useRef 函数并传入null,返回值为一个对象内部有一个current属性存放拿到的dom对象(组件实例)
  3. 通过ref 绑定要获取的元素或者组件

代码实现:

获取dom

javascript 复制代码
import { useEffect, useRef } from 'react'
function App() {  
    const h1Ref = useRef(null)  
    useEffect(() => {    
        console.log(h1Ref)  
    },[])  
    return (    
        <div>      
            <h1 ref={ h1Ref }>this is h1</h1>    
        </div>  
    )
}
export default App

获取组件实例

函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件

javascript 复制代码
class Foo extends React.Component {  
    sayHi = () => {    
        console.log('say hi')  
    }  
    render(){    
        return <div>Foo</div>  
    }
}
    
export default Foo
javascript 复制代码
import { useEffect, useRef } from 'react'
import Foo from './Foo'
function App() {  
    const h1Foo = useRef(null)  
    useEffect(() => {    
        console.log(h1Foo)  
    }, [])  
    return (    
        <div> <Foo ref={ h1Foo } /></div>  
    )
}
export default App

UseContext

UseContext的作用是让你在组件中使用Context,Context是一种在组件树中传递数据的方式,它可以让你在不使用props的情况下,让深层嵌套的组件访问一些全局的状态。使用UseContext的步骤如下:

  • 首先,你需要使用createContext创建一个Context对象,并给它一个默认值。
  • 然后,你需要使用Context.Provider组件包裹你的组件树,并给它一个value属性,这个value就是你要传递的数据。
  • 最后,你可以在任何子组件中使用useContext(Context)来获取这个value,这样你就可以在组件中使用这个数据了。

使用UseContext的好处是,你不需要通过props来一层一层地传递数据,这样可以避免一些不必要的渲染和复杂的逻辑。你也可以在Context中存储一些函数,以便在组件中调用它们。使用UseContext的注意事项是,你需要保证你使用的Context对象是同一个,否则你可能会得到undefined的值。你也需要注意useContext的依赖关系,如果你的Context的value发生变化,那么所有使用了这个Context的组件都会重新渲染,除非你使用了memo或者useMemo来优化性能。

代码实现:

javascript 复制代码
import { createContext, useContext } from 'react'
// 1.创建Context对象
const Context = createContext()

function Foo() {  
    return <div>Foo <Bar/></div>
}

function Bar() {  
    // 3.底层组件通过useContext函数获取数据  
    const name = useContext(Context)  
    return <div>Bar {name}</div>
}

function App() {  
    return (    
        // 2.顶层组件通过Provider 提供数据    
        <Context.Provider value={'this is name'}>     
            <div><Foo/></div>    
        </Context.Provider>  
    )
}

export default App
相关推荐
y先森32 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy32 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891135 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端