React Hook 学习笔记(二)

2022/6/8 源码学习React

# 基础 Hook:

# 1.useState

# 基础用法

// 1 . 初次渲染时,返回值 state 和 initialState 的值相同
// 2. 在后续的重新渲染中吗,useState 返回的第一个值将始终是更新后最新的 state
const [count, setCount] = useState( initialState )
1
2
3

# 函数式更新

// 可以将函数传递给 setCount , 该函数将接受先前的state,并返回一个更新后的值。、
setCount(prevCount => prevCount + 1)

// 这个 setCount 和 class 组件中的 setState 方法不同,useState不会自动合并更新对象。但我们可以用函数式的 setCount 结合展开运算符来达到合并更新对象的效果
const [userInfo , setUserInfo] = useState({ name: 'qile' })
setUserInfo((prevUserInfo) => {
  return { ...prevUser ,  ...{age: 27} }
}) 

// useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。
1
2
3
4
5
6
7
8
9
10

# 惰性初始State

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始state需通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用

# 跳过 state 更新

如果前一个和更新后的 state 相同,则会跳过子组件的渲染并且不会触发 effect 的执行 React 用的是 Object.is() 来比较 state

# 2.useEffect

# 基础用法

// 第一个参数是接收一个包含命令式,且可能有副作用代码的函数。
// 默认情况下 useEffect 它会在第一次渲染之后和每次更新之后都会执行。
// 其实就类似于 class 组件中 componentDidMount 和 componentDidUpdate 这两个生命周期钩子的效果
useEffect(() => {
  document.title = 1
})
1
2
3
4
5
6

# 清除Effect

// 举个🌰,例如我们在组件卸载时需要清除effect创建的 订阅或者计时器 ID 等... 想要实现这一点,useEffect 函数需要返回一个清除函数。清除函数可以是 命名函数 也可以是 匿名的箭头函数

useEffect( ()=> {
  const subscription = props.source.subscribe()
  // 清除订阅
  return function cleanup() {
    subscription.unsubscription()
  }
}
1
2
3
4
5
6
7
8
9

清除函数一般会在组件卸载前执行,如果组件多次渲染,则在执行下一个effect之前,上一个effect就会被清除。但这也会带来一个问题,就是每次更新都会创建新的订阅,那如何避免每次更新都触发effect的执行。这就需要借助 useEffect 的第二个参数了,第二个参数需要传递一个 数组,它是effect所依赖的值的集合。

// 此时只有 props.source 改变时才会重新创建订阅
useEffect( ()=> {
  const subscription = props.source.subscribe()
  // 清除订阅
  return function cleanup() {
    subscription.unsubscription()
  }
}, [props.source]
1
2
3
4
5
6
7
8

useEffect 的第二个参数如果传递一个 空数组,则只会执行一次effect (仅在组件挂载和卸载时执行),原理就是告诉 React 你的 effect 不依赖与 props 或 state 中任何的值,所以它永远都不需要重复执行。并且effect内部的props和state就会一直持有其初始值。

# 用途

只要是副作用,都可以使用 useEffect() 引入。 它的常见用途有下面几种。

  • 获取数据
  • 事件监听或订阅
  • 改变 DOM
  • 输出日志

# effect的执行时机

React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect ,并非所有 effect 都可以被延迟执行。比如,对于用户可见的DOM变更就必在浏览器执行下一次绘制前被同步更新,这样才不会影响用户的视觉体验,React 提供了额外的 Hook , useLayoutEffect 来处理这类effect。它与 useEffect 的结构相同,区别只是调用的时机不同,会早于 useEffect。

需要注意的是 useLayoutEffect() 会阻塞页面的渲染。

# effect 的依赖频繁变化,该怎么办?

其实通过上面的笔记,我们可以想想,如果我们给 useEffect 的第二个参数传入一个 空数组 [] ,就可以让useEffect 中 effect 只执行一次。但这又带来了新问题,如果我们需要让 effect 中的state更新。那此时是没法做到的。上代码来说明如果解决这个问题

// 我们可以使用 setState 的函数式更新形式。
function Counter () {
  const [count , setCount] = useState(0)
  useEffect( ()=> {
    const timerId = setInterval(() => {
      setCount( c => c + 1) // 在这不依赖于外部的 `count` 变量
    }, 1000)
    return () => clearInterval(timerId)
  }, []) // 我们的 effect 不使用组件作用域中的任何变量
}
1
2
3
4
5
6
7
8
9
10

# 注意点

使用 useEffect() 时,有一点需要注意。如果有多个副作用,应该调用多个 useEffect(), 而不应该合并写在一起。

# 3.useContext

  • 在父组件通过 createContext 创建 context

    • const MyContext = createContext(initialValue)
  • 在子孙组件中使用,但别忘记 useContext 的参数必须是 context 对象本身:

    • useContext(MyContext)
    • useContext(MyContext) 其实相当于 context API 中的 <MyContext.Consumer>
    • useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

如何使用:

注意点:

  1. 当前的 context 值由上层组件汇总距离当前组件最近的 <MyContext.providers>value prop 决定

  2. 调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。

Last Updated: 2022/8/13 下午6:30:49