# 基础 Hook:
# 1.useState
# 基础用法
// 1 . 初次渲染时,返回值 state 和 initialState 的值相同
// 2. 在后续的重新渲染中吗,useState 返回的第一个值将始终是更新后最新的 state
const [count, setCount] = useState( initialState )
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 对象。
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
})
2
3
4
5
6
# 清除Effect
// 举个🌰,例如我们在组件卸载时需要清除effect创建的 订阅或者计时器 ID 等... 想要实现这一点,useEffect 函数需要返回一个清除函数。清除函数可以是 命名函数 也可以是 匿名的箭头函数
useEffect( ()=> {
const subscription = props.source.subscribe()
// 清除订阅
return function cleanup() {
subscription.unsubscription()
}
})
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])
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 不使用组件作用域中的任何变量
}
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。
如何使用:
注意点:
当前的 context 值由上层组件汇总距离当前组件最近的
<MyContext.providers>
的value
prop 决定调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。