yuadhのinterview_note yuadhのinterview_note
前端基础
软件框架
算法|数据结构
基础学科
系统|工具
项目
面试综合
专栏
GitHub (opens new window)
前端基础
软件框架
算法|数据结构
基础学科
系统|工具
项目
面试综合
专栏
GitHub (opens new window)
  • vscode

  • vue

  • react

    • heima

      • react快速开始
      • react受控和非受控组件
      • react组件通信
      • react生命周期
      • react路由
      • react的hooks
        • Hooks
        • hooks优势
        • hooks基本使用
          • useState ()
          • 使用规则
          • useEffect ()
          • 清理副作用
          • 发送网络请求
        • useRef ()
        • useContext ()
        • React-memo ()
          • 浅层对比
      • react生态redux
      • react生态router
    • react官方

  • 软件框架
  • react
  • heima
yuadh
2022-12-03
目录

react的hooks

# Hooks

Hooks 是 React v16.8 新增功能

作用:为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能, Hooks 为函数组件钩入 class 组件的特征

R16.8 组件开发模式的对比

  • 之前: class组件(提供状态) + 函数组件(展示内容)

  • 之后:

    • class组件(提供状态) + 函数组件(展示内容)
    • Hooks (提供状态) + 函数组件(展示内容)

    混用了以上两种方式

    注意: 有了 hooks ,不能再把函数组件成为无状态组件,hooks 为函数提供了状态

# hooks优势

  • 组件的逻辑逻辑复用,HOCs、render-props重构组件结构,导致组件形成了 JSX 嵌套地狱的问题
  • class 组件自身的问题
    • class中的this指向问题
    • 相互关联且需要对照修改的代码被拆分到不同生命周期函数中
    • 不利于代码压缩和优化,也不利于 TS 的类型推导
  1. Hooks 只能在函数组件中使用,避免了 class 组件的问题
  2. 复用组件状态逻辑,而无需更改组件层次结构
  3. 根据功能而不是基于生命周期方法强制进行代码分割
  4. 抛开 React 赋予的概念来说,Hooks 就是一些普通的函数
  5. 具有更好的 TS 类型推导,tree-shaking 友好

# hooks基本使用

# useState ()

  • 使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hooks
  • 作用:为函数组件提供状态

使用示例

import {useState} from 'react'
const Count = ()=>{
    const stateArray = useState(0)
    const [state,setState] = stateArray[0]
    return (
     <div>
        state
     	<button onClick={()=>setState(state+1)}>click</button>   
     </div>
    )
}

JS
1
2
3
4
5
6
7
8
9
10
11
12
13

状态的读取和修改

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

修改状态: setCount 是一个函数,参数表示:新的状态值

组件更新的过程

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

组件第一次渲染

  1. 从头开始是执行该组件的代码逻辑
  2. 调用 useState(0) 将传入的参数作为状态初始值
  3. 渲染组件时,此时,获取到的状态值为

选进第二次渲染

  1. 点击按钮,调用 setCount(count+1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
  2. 组件重新渲染时,会再次执行该组件中的代码逻辑
  3. 再次调用 useState(0) , 此时 React 内部会拿到最新值而非初始值
  4. 再次渲染组件,此时获取到的组件的状态值为最新值

# 使用规则

注意:React Hooks 只能直接出现在 函数组件 中,不能嵌套在 if/for 其它 函数中

React Hooks` 必须每次组件渲染时,按照相同的顺序来调用所有的 `Hooks
1
  • 为什么会有这样的规则?因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook ,如果每次调用的顺序不一样,导致 React 无法知道哪一个是 Hook

# useEffect ()

作用:处理函数组件中的副作用

对 React 组件来说,著作用就是根据数据流渲染 UI ,除此之外的都是副作用 . 如:数据请求 、动手修改 DOM 、localSorage 操作等

基本使用

import {useEffect} from 'react'
useEffect(()=>{
  document.litle = `当前点击次数 ${count} 次`
}, , )

JS
1
2
3
4
5
6

参数1:回调函数,就是在该函数中写会产生副作用的代码

执行实际:该 effect 会在组件渲染后以及组件更新后执行

相当于 : componentDidMount + componentDidUpdate

依赖项

描述:如果组件中有另一个状态,另一个状态状态更新,函数中的回调函数也会执行

性能优化:跳过不必要的执行,只在 count 变化时,才执行相应的 effect

useEffect(()=>{
  document.litle = `当前点击次数 ${count} 次`
},[count])

JS
1
2
3
4
5

参数2 : 可选的,可以传入一个数组,数组中的元素为副作用函数的依赖性 . 表示只有在依赖性改变时,才会重新执行 effect

空数组:该 effect 只会在组件第一次渲染后执行 ,相当于componentDidMount

// 触发时机:1 第一次渲染会执行 2 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {})

// componentDidMount
// 触发时机:只在组件第一次渲染时执行
useEffect(() => {}, [])

// componentDidMount + componentDidUpdate(判断)
// 触发时机:1 第一次渲染会执行 2 当 count 变化时会再次执行
useEffect(() => {}, [count])

JS
1
2
3
4
5
6
7
8
9
10
11
12
13

注意: 不要对 useEffect 的依赖项撒谎

const App = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title = '点击了' + count + '次'
  }, [])
  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <hr />
    </div>
  )
}

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

会在点击时没有效果产生

useEffect完全指南:https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/

# 清理副作用

在我们只想在 React 更新 DOM 之后运行一些额外的代码 ,比如 订阅外部数据源,开启定时器看,注册时间。。这种情况下,清除工作是非常重要的,可以防止内存泄露

useEffect(() => {
  const handleResize = () => {}
  window.addEventListener('resize', handleResize)
  return () => window.removeEventListener('resize', handleResize)
}, [])

JS
1
2
3
4
5
6
7

effect 回调函数的返回值,可选的,返回的是一个清理函数,用来执行事件解绑清理等操作

执行时机

  • 组件卸载之前
  • 重新执行 effect 回调函数之前

相当于 componentDidMount+ componentWillUnmount

推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect

优势:根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法来拆分代码

# 发送网络请求

useEffect(() => {
  // 是否取消本次请求
  let didCancel = false

  async function fetchMyAPI() {
    let url = 'http://something/' + productId
    let config = {}
    const response = await myFetch(url)
    // 如果开启其他请求,就忽略本次(过时)的请求结果
    if (!didCancel) {
      console.log(response)
    }
  }

  fetchMyAPI()
  return () => { didCancel = true } // 取消本次请求
}, [productId])

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • effect 只能是一个同步函数,不能使用 async
  • 因为 effect 的返回值应该是一个清理函数,react 会在组件卸载或者 effect 的依赖项变化时重新执行,但是如果 effect 是 async 的,此时返回值对象是 promise ,无法保证清理函数被立即调用
  • 为了使用 async/await 语法,可以在 effect 内部创建 async 函数并调用

注意

如果网络请求函数内访访问数据,可能会导致网络请求函数无线循环调用的问题,因为 effect 的执行是在数据发生更新时候去执行的

# useRef ()

使用场景:在 React 中进行 DOM 操作时,用来获取 DOM

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

const inputRef = useRef(null)

JS
1
2
3

使用

const App = () => {
  const inputRef = useRef(null)
  const add = () => {
    console.log(inputRef.current.value)
  }
  return (
    <section className="todoapp">
      <input type="text" placeholder="请输入内容" ref={inputRef} />{' '}
      <button onClick={add}>添加</button>
    </section>
  )
}

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意:useRef 不仅仅可以用于操作 DOM , 还可以操作组件

# useContext ()

使用场景:跨组件共享通信

Context 作用:实现跨组件传递数据,而不必在每个级别手动传递 props,简化组件之间的数据传递过程

const Context = createContext(defaultValue)
const value = useContext(conText)

JS
1
2
3
4

Context 对象包含了两个组件

  • <Context.Provider value> 通过 value 属性提供数据
  • <Context.Consumer> 通过 render-props 模式,在 JSX 中获取Context 提供的数据

在函数组件中,获取 Context 中的值,需需要配合 Context 一起使用而 useContext 和 Context.Consumer 的区别在于:获取数据的位置不同

示例

import React, { useState, useContext } from 'react'
const Context = React.createContext()
const App = () => {
  const [color, setColor] = useState('red')
  return (
    <Context.Provider value={color}>
      <div>
        <h1>我是根组件</h1>
        <div>颜色:{color}</div>
        <button onClick={() => setColor('yellow')}>修改</button>
        <Father></Father>
      </div>
    </Context.Provider>
  )
}

const Father = () => {
  return (
    <div>
      <h3>我是父组件</h3>
      <Child></Child>
    </div>
  )
}

const Child = () => {
  const color = useContext(Context)
  return (
    <div>
      <h5>我是子组件--{color}</h5>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# React-memo ()

使用场景:在父组件的状态更新的时候,子组件就会无条件的一起更新

渲染过程

  • 子组件 props 变化时更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM) -> 渲染(变化后的内容)
  • 子组件无变化更新过程:代码组件执行 -> JSX Diff (配合虚拟DOM)

为了提升性能,优化不必要的无变化子组件更新,这种情况就可以使用 React.memo 高阶组件

import { useState } from 'react'
import ReactDOM from 'react-dom'

const Child2 = ({ count }) => {
  console.log('Child2 子组件代码执行了')
  return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
}

const Child1 = () => {
  console.log('Child1 子组件代码执行了')
  return <div style={{ backgroundColor: '#def' }}>子组件1</div>
}

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

  return (
    <div style={{ backgroundColor: 'pink', padding: 10 }}>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <hr />

      {/* 子组件 */}
      <Child1 />
      <br />
      <Child2 count={count} />
    </div>
  )
}

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

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

此示例,在子组件2更新时, 子组件1也会执行更新

memo 作用:记忆上一次的更新渲染结果,在 props 没有变化时复用该结果,避免函数不必要的更新

使用示例

import { useState, memo } from 'react' //导入
const Child2 = memo(({ count }) => {
  console.log('Child2 子组件代码执行了')
  return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
})

const Child1 = memo(() => {
  console.log('Child1 子组件代码执行了')
  return <div style={{ backgroundColor: '#def' }}>子组件1</div>
})

JS
1
2
3
4
5
6
7
8
9
10
11
12
  • 参数:需要被记忆的组件,不必要更新的组件
  • 返回值: React 记住的 Child 组件

原理:通过检查对比更新前后 props 是否相同,来决定是否复用上一次的渲染结果

注意: 不是所有的组件都适用 memo,需要经常更新渲染的组件使用 memo 性能反而会降低

# 浅层对比

默认情况下,React.memo 只会对更新前后的 props 进行浅对比

也就是说,对于对象类型的 prop 来说,只会比较引用(地址)

import { useState, memo } from 'react'
import ReactDOM from 'react-dom'

const Child2 = memo(({ count }) => {
  console.log('Child2 子组件代码执行了')
  return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
})

const Child1 = memo(({ obj }) => {
  console.log('Child1 子组件代码执行了')
  return (
    <div style={{ backgroundColor: '#def' }}>
      子组件1 id:{obj.id} name:{obj.name} age:{obj.age}
    </div>
  )
})

const App = () => {
  const [count, setCount] = useState(0)
  const obj = {
    name: 'yd',
    id: 11,
    age: 22,
  }
  return (
    <div style={{ backgroundColor: 'pink', padding: 10 }}>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <hr />
      {/* 子组件 */}
      <Child1 obj={obj} />
      <br />
      <Child2 count={count} />
    </div>
  )
}

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

此处传入的参数为 引用数据类型 ,所以在会浅层的对比地址是否发生变化所以在每次更新组件时,创建的 obj 的地址是不同的所以在更新 DOM 也会更新此组件

可以使用 React.memo 的第二个参数手动控制比较

React.memo(Child,function areEqual(prevProps,nextProps){
  return preProps === nextProps
})

JS
1
2
3
4
5

如果返回为 true 表示记住不渲染组件,如果返回 false 表示渲染该组件

更好的解决方法

  • useCallback Hook:记住函数的引用,在组件每次更新时返回相同引用的函数。
  • useMemo Hook:记住任意数据(数值、对象、函数等),在组件每次更新时返回相同引用的数据【功能之一】
编辑 (opens new window)
上次更新: 2023/02/07, 15:11:55
react路由
react生态redux

← react路由 react生态redux→

最近更新
01
青训营真题day01
02-07
02
01day01-html与css
02-07
03
day02-js
02-07
更多文章>
Theme by Vdoing | Copyright © 2022-2023 yuadh | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式