Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
class组件相对于函数式组件有什么优势
状态的保存
- class组件可以定义自己的state,用来保存组件自己内部的状态。
- 函数式组件不可以,因为函数每次调用都会产生新的临时变量;
生命周期
- class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑。比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次。
- 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求。
执行时机
- class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等。
- 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次。 所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。
class组件存在的问题
复杂组件变得难以理解
- 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂。比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount中移除)。
- 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度。
组件复用状态很难
- 为了一些状态的复用我们需要通过高阶组件或render props。
- Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套。 这些代码让我们不管是编写和设计上来说,都变得非常困难。
useState
- 返回一个数组,其中包含两个值,一个是state, 另一个是修改state值的函数。
- 该函数类似 class 组件的
this.setState
,但是它不会把新的 state 和旧的 state 进行合并。而是替换原来的值。 如果我们修改数据,我们就必须传入一个新的值。React中会进行新旧值的对比,发现相同就不会再次渲染组件,如果不相同才会渲染组件。
import React, { useState } from 'react' function UseEffectTest() { const [friends, setFriends] = useState(['zh', 'zhh']) // push方式 const pushWay = () => { friends.push('llm') setFriends(friends) } // 新传入一个数组 const newWay = () => { setFriends([...friends, 'llm']) } return ( <div> <p>朋友</p> <ul> {friends.map((item, index) => { return <li key={index}>{item}</li> })} </ul> <button onClick={(e) => { pushWay() }} > 添加朋友push方式 </button> <button onClick={newWay}>添加朋友新传入一个数组方式</button> </div> ) }
网络异常,图片无法展示
|
useState
唯一的参数就是初始 state。这个初始 state 参数只有在第一次渲染时会被用到。
useEffect
- 在这个hook中,我们可以写一些副作用逻辑。默认情况下,只要组件重新渲染,它就会执行。
- 它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
- React 在完成对 DOM 的更改后运行你的“副作用”函数。
- 副作用函数还可以通过返回一个函数来指定如何“清除”副作用。 类似于class组件的componentWillUnmount。默认情况下,每次触发时,都先执行清除操作。如果需要指定执行的依赖,我们可以传入一个数组作为userEffect的第二个参数。
- 通过使用该 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。
- 在该函数指定之前,dom已经完成了更新。
- 如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 useEffect。
useContext
- 接收一个 context 对象(
React.createContext
的返回值)并返回该 context 的当前值。
- 当前的 context 值由上层组件中距离当前组件最近的
<MyContext.Provider>
的value
prop 决定。
- 当组件上层最近的
<MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给MyContext
provider 的 contextvalue
值。
- 这个方法只是为了获取context值,而提供context值,依旧是使用原来的方法。先调用
createContext
来创建一个context对象,然后利用这个context对象的Provider组件提供value值。
useReducer
- 他只是useState的替代方案。而不是我们认为的redux。
- 他接受三个参数。第一个参数为reducer函数,第二个参数为state的初始值,第三个参数为一个函数,可以惰性地创建初始 state。
- 如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
useCallback
- 传入一个函数和一个数组,该数组主要是收集useCallback是否返回一个新的memoized回调函数的依赖项。
useCallback(fn, deps)
相当于useMemo(() => fn, deps)
。
- 主要的用途就是将函数传递给子组件时,做性能优化。当子组件继承自PureComponent或者传入memo高阶组件时,他是非常有必要的。如果useCallback传入的依赖项未发生改变,那么useCallback将不会返回一个新的memoized回调函数,那么子组件将不会被重新渲染。
useMemo
- 我们知道,只要是改变state,props都会重新执行组件渲染,如果一个开销非常大的计算,但是我们改变的是其他的state,该开销并没有依赖这个改变的state。由于组件会被重新渲染,所以大开销计算也会被重新执行一遍,这是没有必要的。所以我们就可以通过useMemo来做性能优化。
网络异常,图片无法展示
|
- 把“创建”函数和依赖项数组作为参数传入
useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
- 如果没有提供依赖项数组,
useMemo
在每次渲染时都会计算新的值。
useRef
useRef
返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数。
- 他的绑定规律和以前的ref一样。不能使用在函数式组件中,需要使用forwardRef包裹。
- 如果useRef中存储其他类型的值。那么在组件的整个生命周期中,他是不会变化的。
import React, { useState, useRef } from 'react' function UseEffectTest() { const [count, setCount] = useState(0) // useRef const numRef = useRef(count)// 一直都是0 return ( <div> <h1>ref: {numRef.current}</h1> <h1>count: {count}</h1> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ) }
- 变更
.current
属性不会引发组件重新渲染。所以我们可以利用这一特性保存以前的状态值。
useImperativeHandle
useImperativeHandle
可以让你在使用ref
时自定义暴露给父组件的实例值。
useImperativeHandle
应当与forwardRef 一起使用。
- 他的目的是选择性的暴露子组件实例的内容,不想让全部都暴露给父组件。
function FancyInput(props, ref) { const inputRef = useRef(); // 只暴露focus方法。 useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新。
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新。 如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
自定义Hooks
- 自定义 Hook 是一个函数,其名称以 “
use
” 开头,函数内部可以调用其他的 Hook。
- 主要的目的就是复用相同的逻辑。
Hook 使用规则
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。